fix(supplier-invoices): fit the list inside the content column (#2262) (#2281)

The Leverantörsfakturor table was 1133px wide at every desktop size while
the content column is at most 960px (max-w-5xl minus px-8) and 948px on a
1280-wide laptop. With nowrap cells every column adds its widest header
or cell to the table's minimum width, so the overflow-x-auto wrapper
scrolled sideways: Leverantör collapsed to its header width (129px) and
the Status chips were cut at the right edge, with the Godkänn column off
screen. Measured with the real page rendered under /sandbox at 1280,
1366, 1440, 1536 and 1920 wide.

The list carried two date columns plus "Kvar att betala" on top of what
the customer invoice list shows, and #2091's always-visible sort control
added ~18px to each of seven headers, which tipped an already tight
budget over the column. Viewport breakpoints cannot help because the
column is capped at 960px regardless of screen size.

- Drop the Fakturadatum column from the list: förfaller is the payer's
  date and the default order, and the invoice date lives in the detail
  view (the customer invoice list has no invoice-date column either).
  The sort comparator keeps invoice_date as its tie-break; only the
  header goes.
- Shorten the sv header "Kvar att betala" to "Kvar": the label was 163px
  for a column whose numbers need ~120px.
- Leave a column-budget comment on the table and one sentence in the
  dry-table design rule, since there is no shared list component to fix:
  every page-level list hand-writes the overflow-x-auto wrapper, and the
  three overflow reports had three different causes.

After the change the table measures 948/960px (equal to its wrapper) with
worst-case data (16-char invoice numbers, seven-digit amounts, two chips
on one row), and Leverantör keeps 142-154px even then.

Fixes #2262


Claude-Session: https://claude.ai/code/session_015qgLgdt4mLmha1ZLFMwq1u

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-04 17:18:50 +02:00
committed by GitHub
parent 88a3b5fb0f
commit 68fee7dbe7
4 changed files with 10 additions and 16 deletions
+1 -1
View File
@@ -97,7 +97,7 @@ Compact metric cards (e.g. dashboard tiles, salary KPI row) use `p-4`. Detail ca
| Need | Component | Notes |
|---|---|---|
| Page title + action | `components/ui/page-header.tsx` `PageHeader` | Use this, not bespoke `<h1>` + `<p>` blocks. Drop the `description` prop when it just paraphrases the title. |
| Data table, **page-level list** | `components/ui/dry-table.tsx` `TH_CLASS` / `TD_CLASS` on a plain `<table className="w-full border-collapse text-[13px]">`, rows `hover:bg-secondary/35` | The concept list table: borderless, straight on the panel, 13px rows, hairline heads. This is what every migrated list page uses. Add `tabular-nums` to numeric cells. Hover-revealed row controls use `HOVER_REVEAL_CLASS` from the same file, never a hand-rolled `opacity-0 group-hover:opacity-100` (coarse pointers never hover, so the control would be unreachable on touch). |
| Data table, **page-level list** | `components/ui/dry-table.tsx` `TH_CLASS` / `TD_CLASS` on a plain `<table className="w-full border-collapse text-[13px]">`, rows `hover:bg-secondary/35` | The concept list table: borderless, straight on the panel, 13px rows, hairline heads. This is what every migrated list page uses. Add `tabular-nums` to numeric cells. Hover-revealed row controls use `HOVER_REVEAL_CLASS` from the same file, never a hand-rolled `opacity-0 group-hover:opacity-100` (coarse pointers never hover, so the control would be unreachable on touch). Budget the columns before adding one: the content column is at most 960px (948px on a 1280-wide laptop) at every desktop size, so a nowrap column that does not fit makes the wrapper scroll sideways and squeezes the flexible name column to its header width (#2125, #2262); viewport breakpoints cannot buy room, drop or shorten a column instead. |
| Data table, **dialog or report view** | `components/ui/table.tsx` `Table / TableHeader / TableHead / TableRow / TableCell` | Header style is baked in: `text-[11px] font-medium uppercase tracking-wider text-muted-foreground`. Wrap in `<CardContent className="p-0">` when the table is a card's primary content. `TableCell` is `px-4 py-3` on `text-sm`, so a page-level list built from this primitive comes out ~15% taller with a different hover tint: use the dry-table row above instead. |
| Status indicator | `components/ui/badge.tsx` `<Badge variant>` | Chips mark exceptions only: normal states (Aktiv, Bokförd, Betald-i-tid) render as muted text (`text-muted-foreground text-xs`); Badge is reserved for rows that deviate (Utkast, Förfallen, Ej bokförd). A table where every row carries the same chip is wrong. Variants: `default / secondary / success / warning / destructive / outline`. **Never** use raw Tailwind colors (`bg-blue-100`, `bg-emerald-500/10`, etc.) for status. Map status → variant via a small `Record` per feature. |
| No-data state | `components/ui/empty-state.tsx` `EmptyState` | Don't hand-roll `<div className="flex flex-col items-center py-12">…</div>`. Preset variants exist (`EmptyCustomers`, `EmptyByraClients`). |
+8 -12
View File
@@ -613,6 +613,14 @@ export default function SupplierInvoicesPage() {
}
/>
) : (
/* Column budget (#2262): the content column is at most 960px (max-w-5xl
minus px-8) and 948px on a 1280-wide laptop, at every desktop size,
so viewport breakpoints cannot buy room. Every nowrap column adds its
widest header or cell to the table's minimum width; past the budget
the wrapper scrolls sideways, Leverantör collapses to its header
width and Status is cut at the edge. That is why the list carries
one date (förfaller: the payer's date and the default order) and a
short Kvar header; fakturadatum lives in the detail view. */
<div className="overflow-x-auto">
<table className="w-full border-collapse text-[13px]">
<thead>
@@ -633,15 +641,6 @@ export default function SupplierInvoicesPage() {
sort={sort}
onSort={updateSort}
/>
<SortableHeader
label={t('th_invoice_date')}
sortLabel={t('sort_by', { column: t('th_invoice_date') })}
column="invoice_date"
sort={sort}
onSort={updateSort}
className="hidden text-right md:table-cell"
align="right"
/>
<SortableHeader
label={t('th_due_date')}
sortLabel={t('sort_by', { column: t('th_due_date') })}
@@ -763,9 +762,6 @@ export default function SupplierInvoicesPage() {
{inv.supplier_invoice_number}
</Link>
</td>
<td className={cn(TD_CLASS, 'hidden whitespace-nowrap text-right tabular-nums text-muted-foreground md:table-cell')}>
{formatDate(inv.invoice_date)}
</td>
<td className={cn(TD_CLASS, 'hidden whitespace-nowrap text-right tabular-nums text-muted-foreground sm:table-cell')}>
{formatDate(inv.due_date)}
</td>
-1
View File
@@ -803,7 +803,6 @@
"th_arrival": "Arrival",
"th_supplier": "Supplier",
"th_invoice_number": "Invoice no.",
"th_invoice_date": "Invoice date",
"th_due_date": "Due",
"th_amount": "Amount",
"th_remaining": "Remaining",
+1 -2
View File
@@ -803,10 +803,9 @@
"th_arrival": "Ankomst",
"th_supplier": "Leverantör",
"th_invoice_number": "Fakturanr",
"th_invoice_date": "Fakturadatum",
"th_due_date": "Förfaller",
"th_amount": "Belopp",
"th_remaining": "Kvar att betala",
"th_remaining": "Kvar",
"th_status": "Status",
"sort_by": "Sortera efter {column}",
"status_registered": "Registrerad",