From 26f12b01a0bd0c418b4afce0e69c52abb2d36787 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 23:02:40 +0200 Subject: [PATCH] feat(mui): fixed-width DataTable columns + restore Projects next-number hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataTable: optional per-column width; when any column sets one, the table uses table-layout:fixed with a colgroup so columns keep a stable size instead of reflowing as rows are filtered (overflow clipped with ellipsis). Tables without widths keep auto layout. Projects: assign column widths; restore the original '(přiděleno automaticky)' suffix on the next-number hint. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/Projects.tsx | 11 ++++++++++- src/admin/ui/DataTable.tsx | 37 ++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx index 20c3653..a439fc7 100644 --- a/src/admin/pages/Projects.tsx +++ b/src/admin/pages/Projects.tsx @@ -245,6 +245,7 @@ export default function Projects() { { key: "project_number", header: "Číslo", + width: "12%", sortKey: "project_number", mono: true, render: (p) => p.project_number, @@ -252,6 +253,7 @@ export default function Projects() { { key: "name", header: "Název", + width: "16%", sortKey: "name", bold: true, render: (p) => p.name || "—", @@ -259,16 +261,19 @@ export default function Projects() { { key: "customer", header: "Zákazník", + width: "13%", render: (p) => p.customer_name || "—", }, { key: "responsible", header: "Zodpovědná osoba", + width: "14%", render: (p) => p.responsible_user_name || "—", }, { key: "status", header: "Stav", + width: "10%", sortKey: "status", render: (p) => ( p.order_id ? ( formatDate(p.start_date), }, { key: "end_date", header: "Konec", + width: "8%", mono: true, render: (p) => formatDate(p.end_date), }, { key: "actions", header: "Akce", + width: "7%", align: "right", render: (p) => ( @@ -443,7 +452,7 @@ export default function Projects() { nextNumberQuery.data?.number ?? nextNumberQuery.data?.next_number ?? "…" - }`} + } (přiděleno automaticky)`} submitText="Vytvořit" loading={creating} > diff --git a/src/admin/ui/DataTable.tsx b/src/admin/ui/DataTable.tsx index bdc1def..9ca02d4 100644 --- a/src/admin/ui/DataTable.tsx +++ b/src/admin/ui/DataTable.tsx @@ -17,6 +17,13 @@ export interface DataColumn { bold?: boolean; /** If set, the header becomes a clickable sort control (requires onSort). */ sortKey?: string; + /** + * Fixed column width (number = px, string = any CSS width incl. "%"). When + * ANY column sets a width the table switches to `table-layout: fixed`, so + * columns keep a stable size regardless of cell content (e.g. while + * filtering). Overflowing text is clipped with an ellipsis. + */ + width?: number | string; render: (row: T) => ReactNode; } @@ -47,9 +54,28 @@ export default function DataTable({ console.warn("DataTable: columns with sortKey require an onSort prop."); } if (rows.length === 0 && empty) return <>{empty}; + // When any column declares a width, lock the table to fixed layout so column + // widths come from the colgroup (stable) instead of from cell content (which + // jumps as rows are filtered). Tables that set no widths keep auto layout. + const hasWidths = columns.some((c) => c.width != null); + const colWidth = (w: number | string | undefined) => + w == null ? undefined : typeof w === "number" ? `${w}px` : w; return ( - - + +
+ {hasWidths && ( + + {columns.map((c) => ( + + ))} + + )} {columns.map((c) => ( @@ -101,6 +127,13 @@ export default function DataTable({ ...(c.mono ? { fontFamily: "'DM Mono', Menlo, monospace" } : {}), + ...(hasWidths + ? { + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + } + : {}), }} > {c.render(row)}