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)}