From 68fee7dbe721407362a588b23c1a057548f579e5 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg Date: Fri, 4 Sep 2026 17:18:50 +0200 Subject: [PATCH] fix(supplier-invoices): fit the list inside the content column (#2262) (#2281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude/rules/design.md | 2 +- app/(dashboard)/supplier-invoices/page.tsx | 20 ++++++++------------ messages/en.json | 1 - messages/sv.json | 3 +-- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/.claude/rules/design.md b/.claude/rules/design.md index 0b8aaa4a..d0f78fcf 100644 --- a/.claude/rules/design.md +++ b/.claude/rules/design.md @@ -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 `

` + `

` 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 ``, 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 `
`, 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 `` 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` `` | 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 `
`. Preset variants exist (`EmptyCustomers`, `EmptyByraClients`). | diff --git a/app/(dashboard)/supplier-invoices/page.tsx b/app/(dashboard)/supplier-invoices/page.tsx index 6da5ef8b..ec79babe 100644 --- a/app/(dashboard)/supplier-invoices/page.tsx +++ b/app/(dashboard)/supplier-invoices/page.tsx @@ -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. */
@@ -633,15 +641,6 @@ export default function SupplierInvoicesPage() { sort={sort} onSort={updateSort} /> - - diff --git a/messages/en.json b/messages/en.json index 978d547c..8b3c15bb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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", diff --git a/messages/sv.json b/messages/sv.json index 5d146220..f986e939 100644 --- a/messages/sv.json +++ b/messages/sv.json @@ -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",
- {formatDate(inv.invoice_date)} - {formatDate(inv.due_date)}