feat(mui): fixed-width DataTable columns + restore Projects next-number hint

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) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-06 23:02:40 +02:00
parent 1b372b93ea
commit 26f12b01a0
2 changed files with 45 additions and 3 deletions

View File

@@ -245,6 +245,7 @@ export default function Projects() {
{ {
key: "project_number", key: "project_number",
header: "Číslo", header: "Číslo",
width: "12%",
sortKey: "project_number", sortKey: "project_number",
mono: true, mono: true,
render: (p) => p.project_number, render: (p) => p.project_number,
@@ -252,6 +253,7 @@ export default function Projects() {
{ {
key: "name", key: "name",
header: "Název", header: "Název",
width: "16%",
sortKey: "name", sortKey: "name",
bold: true, bold: true,
render: (p) => p.name || "—", render: (p) => p.name || "—",
@@ -259,16 +261,19 @@ export default function Projects() {
{ {
key: "customer", key: "customer",
header: "Zákazník", header: "Zákazník",
width: "13%",
render: (p) => p.customer_name || "—", render: (p) => p.customer_name || "—",
}, },
{ {
key: "responsible", key: "responsible",
header: "Zodpovědná osoba", header: "Zodpovědná osoba",
width: "14%",
render: (p) => p.responsible_user_name || "—", render: (p) => p.responsible_user_name || "—",
}, },
{ {
key: "status", key: "status",
header: "Stav", header: "Stav",
width: "10%",
sortKey: "status", sortKey: "status",
render: (p) => ( render: (p) => (
<StatusChip <StatusChip
@@ -280,6 +285,7 @@ export default function Projects() {
{ {
key: "order", key: "order",
header: "Objednávka", header: "Objednávka",
width: "12%",
render: (p) => render: (p) =>
p.order_id ? ( p.order_id ? (
<Box <Box
@@ -300,18 +306,21 @@ export default function Projects() {
{ {
key: "start_date", key: "start_date",
header: "Začátek", header: "Začátek",
width: "8%",
mono: true, mono: true,
render: (p) => formatDate(p.start_date), render: (p) => formatDate(p.start_date),
}, },
{ {
key: "end_date", key: "end_date",
header: "Konec", header: "Konec",
width: "8%",
mono: true, mono: true,
render: (p) => formatDate(p.end_date), render: (p) => formatDate(p.end_date),
}, },
{ {
key: "actions", key: "actions",
header: "Akce", header: "Akce",
width: "7%",
align: "right", align: "right",
render: (p) => ( render: (p) => (
<Box sx={{ display: "flex", gap: 0.5, justifyContent: "flex-end" }}> <Box sx={{ display: "flex", gap: 0.5, justifyContent: "flex-end" }}>
@@ -443,7 +452,7 @@ export default function Projects() {
nextNumberQuery.data?.number ?? nextNumberQuery.data?.number ??
nextNumberQuery.data?.next_number ?? nextNumberQuery.data?.next_number ??
"" ""
}`} } (přiděleno automaticky)`}
submitText="Vytvořit" submitText="Vytvořit"
loading={creating} loading={creating}
> >

View File

@@ -17,6 +17,13 @@ export interface DataColumn<T> {
bold?: boolean; bold?: boolean;
/** If set, the header becomes a clickable sort control (requires onSort). */ /** If set, the header becomes a clickable sort control (requires onSort). */
sortKey?: string; 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; render: (row: T) => ReactNode;
} }
@@ -47,9 +54,28 @@ export default function DataTable<T>({
console.warn("DataTable: columns with sortKey require an onSort prop."); console.warn("DataTable: columns with sortKey require an onSort prop.");
} }
if (rows.length === 0 && empty) return <>{empty}</>; 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 ( return (
<TableContainer sx={{ borderRadius: 2 }}> <TableContainer sx={{ borderRadius: 2, overflowX: "auto" }}>
<Table size="small" sx={{ "& td, & th": { borderColor: "divider" } }}> <Table
size="small"
sx={{
...(hasWidths ? { tableLayout: "fixed", minWidth: 720 } : {}),
"& td, & th": { borderColor: "divider" },
}}
>
{hasWidths && (
<colgroup>
{columns.map((c) => (
<col key={c.key} style={{ width: colWidth(c.width) }} />
))}
</colgroup>
)}
<TableHead> <TableHead>
<TableRow> <TableRow>
{columns.map((c) => ( {columns.map((c) => (
@@ -101,6 +127,13 @@ export default function DataTable<T>({
...(c.mono ...(c.mono
? { fontFamily: "'DM Mono', Menlo, monospace" } ? { fontFamily: "'DM Mono', Menlo, monospace" }
: {}), : {}),
...(hasWidths
? {
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}
: {}),
}} }}
> >
{c.render(row)} {c.render(row)}