refactor(quotes): give the detail page's items table the full width

The detail page was a 3-column grid with the Items card in a
lg:col-span-2 and the admin panels beside it. That left the items
table ~430px for a table whose natural width is ~790px, so pricing a
quote meant scrolling the table sideways.

Stacks the page instead: the items table (the actual work surface)
takes the full width, and Attachments / Linked Orders / Activity sit
3-up underneath, where they read fine at a third of the width because
they're reference panels rather than things you act on while pricing.

Two width trims make the table fit outright rather than merely fit
better:
  - Description's min-width 360px → 280px. It's a floor, not a target;
    the table is table-layout:auto so Description still absorbs any
    width the fixed columns leave. 360 was forcing overflow on its own.
  - The "Processed" header is whitespace-nowrap, so the word set a
    ~78px floor on a column whose content is a 2-3 character badge.
    Renamed to "Done" (title="Processed") at w-[64px].

Measured at a 1044px viewport: the table went from 110px of horizontal
overflow to 0. Mobile is unaffected — it uses the stacked card list,
and the admin grid collapses to one column.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-08-29 09:17:07 +02:00
parent 42179229aa
commit 53f9aaaee4

View file

@ -2219,12 +2219,17 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri
</Card>
)}
<div className="grid gap-6 lg:grid-cols-3">
{/* LEFT COLUMN — primary content (Description / Items / Notes).
The Items card lives here because it's the page's hero; the
admin panels on the right are quieter so the eye lands on
the data first. */}
<div className="space-y-6 lg:col-span-2">
{/* Single-column stack. This used to be a 3-column grid with the
Items card in a `lg:col-span-2` and the admin panels beside
it, which left the items table ~430px of room for a table
whose natural width is ~790px — so pricing a quote meant
scrolling the table sideways. The items table is the page's
work surface; it gets the full width, and the admin panels
(reference / config, not things you act on while pricing) sit
3-up underneath. */}
<div className="space-y-6">
{/* PRIMARY — Items + Notes. */}
<div className="space-y-6">
{/*
The "Description" card used to live here showing
`quote.description` — which for most quotes is the
@ -2300,14 +2305,18 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri
per line while Qty sat in 360px of empty
space.
`min-w-[360px]` guarantees Description gets
the room it needs when Qty + Markup +
Processed + Update-price columns take
their fixed widths. Without this the
table-layout algorithm squeezes
Description down to ~250px on a
1025px container. */}
<TableHead className="min-w-[360px]">Description</TableHead>
`min-w-[280px]` is a FLOOR, not a target.
The table is `table-layout: auto`, so
Description absorbs whatever width the
fixed columns don't use — on a full-width
card that lands around 380px. The floor
only matters on narrow viewports, where it
stops the layout algorithm collapsing
Description to a couple of words per line.
Keep it low enough that the table's
natural width still fits the card, or the
whole thing scrolls sideways again. */}
<TableHead className="min-w-[280px]">Description</TableHead>
<TableHead className="text-center w-[60px]">Qty</TableHead>
<TableHead>Supplier</TableHead>
<TableHead className="text-right">Cost</TableHead>
@ -2318,7 +2327,11 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri
Markup
</span>
</TableHead>
<TableHead className="text-center w-[80px]">Processed</TableHead>
{/* "Done" rather than "Processed": the header
is `whitespace-nowrap`, so the longer word
set a ~78px floor on a column whose
content is a 2-3 character badge. */}
<TableHead className="text-center w-[64px]" title="Processed">Done</TableHead>
<TableHead className="text-right w-[56px]"></TableHead>
</TableRow>
</TableHeader>
@ -2452,16 +2465,17 @@ export default function QuoteDetailPage({ params }: { params: Promise<{ id: stri
)}
</div>
{/* RIGHT COLUMN — admin panels. The eye should land on the Items
card first; the panels on the right are reference / config
and sit quietly. Customer info lives in the strip below the
header (lifted out of this column during the v3 redesign).
Mobile stacks everything below the items.
Three cards now (down from six):
{/* ADMIN PANELS — reference / config, laid out 3-up BELOW the
items table rather than beside it, so the table gets the
page's full width. The eye still lands on the items first
because these now come after it in the flow.
`items-start` stops the three cards stretching to a shared
height when one has far more content than the others.
Three cards (down from six):
1. Attachments — tabbed (Outbound / Internal)
2. Linked Orders — its own card (specific, deserves focus)
3. Activity — tabbed (History / Email Status) */}
<div className="space-y-6">
<div className="grid gap-6 lg:grid-cols-3 items-start">
{/*
Attachments — single tabbed card with Outbound and Internal
tabs. Both write to the same `quote_attachments` PB