diff --git a/src/admin/ui/DataTable.tsx b/src/admin/ui/DataTable.tsx index 65d44bc..ec39fd3 100644 --- a/src/admin/ui/DataTable.tsx +++ b/src/admin/ui/DataTable.tsx @@ -1,5 +1,9 @@ import type { ReactNode } from "react"; import type { SxProps, Theme } from "@mui/material/styles"; +import { useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; @@ -60,10 +64,109 @@ export default function DataTable({ sortDir, onSort, }: DataTableProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); if (import.meta.env.DEV && columns.some((c) => c.sortKey) && !onSort) { console.warn("DataTable: columns with sortKey require an onSort prop."); } if (rows.length === 0 && empty) return <>{empty}; + + // On phones a wide multi-column table would force horizontal scrolling and + // truncate every cell. Instead render each row as a stacked "label: value" + // card so all content is readable; the actions column (key "actions") drops + // to a button row at the bottom. + if (isMobile) { + const actionCol = columns.find((c) => c.key === "actions"); + const dataCols = columns.filter((c) => c.key !== "actions"); + return ( + + {rows.map((row) => { + const extra = rowSx?.(row); + return ( + onRowClick(row) : undefined} + sx={[ + { + border: 1, + borderColor: "divider", + borderRadius: 2, + p: 1.5, + display: "flex", + flexDirection: "column", + gap: 0.75, + bgcolor: "background.paper", + }, + onRowClick ? { cursor: "pointer" } : {}, + rowDanger?.(row) + ? { borderColor: "error.main", bgcolor: "error.light" } + : {}, + rowInactive?.(row) ? { opacity: 0.55 } : {}, + ...(Array.isArray(extra) ? extra : extra ? [extra] : []), + ]} + > + {dataCols.map((c) => ( + + + {c.header} + + + {c.render(row)} + + + ))} + {actionCol && ( + e.stopPropagation()} + sx={{ + display: "flex", + flexWrap: "wrap", + justifyContent: "flex-end", + gap: 0.5, + pt: 0.75, + mt: 0.25, + borderTop: 1, + borderColor: "divider", + }} + > + {actionCol.render(row)} + + )} + + ); + })} + + ); + } // 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.