wcz-layout
Version:
148 lines (137 loc) • 5.2 kB
Markdown
---
name: data-grid
description: "Use when: building or configuring MUI X DataGrid tables, editable columns, toolbar actions, row selection, or TanStack DB-backed grids."
---
- Wrap DataGrid in `Fullscreen` when it is the only content on the page.
- Type columns as `Array<GridColDef<RowType>>` with the row interface.
- Use translation for all `headerName` values.
- Use `rows`, `columns`, `showToolbar`, `loading`, `ignoreDiacritics`, `cellSelection`, and `disableRowSelectionOnClick` on grids.
- For enums use `type: "singleSelect"` with `{ value, label }` options array used from enumObject.
- For editable columns, pair `editable: true` with `renderHeader: EditableColumnHeader`.
- Use `ChipInputCell`, `EditableColumnHeader`, and router button/grid action components from `wcz-layout/components` when they fit.
```
@mui/x-data-grid-premium — GridColDef, DataGridPremium
src/db-collections/ - TanStack DB collections to get data
@tanstack/react-db - useLiveQuery, useLiveSuspenseQuery
src/server/actions/ - server functions and enums
wcz-layout/components - ChipInputCell, EditableColumnHeader, Fullscreen
wcz-layout/hooks - useTranslation
```
```tsx
// src/routes/<feature>s/index.tsx
const { t } = useTranslation();
const { confirm, alert } = useDialogs();
const navigate = useNavigate();
const [cellSelectionModel, setCellSelectionModel] = useState<GridCellSelectionModel>({});
const { data, isLoading } = useLiveQuery((q) =>
q.from({ feature: featureCollection }).orderBy(({ feature }) => feature.name, "asc"),
);
const columns: Array<GridColDef<Feature>> = [
{
field: "name",
headerName: t("Feature.Name"),
width: 200,
editable: true,
renderHeader: EditableColumnHeader,
},
{
field: "tags",
headerName: t("Feature.Tags"),
width: 200,
renderCell: (params) => <ChipInputCell params={params} />,
},
{
field: "status",
headerName: t("Feature.Status"),
width: 200,
type: "singleSelect",
valueOptions: featureStatusEnum.enumValues.map((status) => ({
value: status,
label: t(`FeatureStatus.${status}`),
})),
},
];
const handleOnDelete = createOptimisticAction<Array<string>>({
onMutate: (ids) => {
ids.forEach((id) => {
featureCollection.delete(id);
});
},
mutationFn: async (ids) => {
deleteFeatures({ data: ids });
await featureCollection.utils.refetch();
},
});
return (
<Fullscreen>
<DataGridPremium
rows={data}
columns={columns}
showToolbar
loading={isLoading}
ignoreDiacritics
onRowDoubleClick={({ row }) => navigate({ to: "/features/$id", params: { id: row.id } })}
cellSelection
disableRowSelectionOnClick
cellSelectionModel={cellSelectionModel}
onCellSelectionModelChange={(newModel) => setCellSelectionModel(newModel)}
slots={{ toolbar: DataGridToolbar }}
slotProps={{
toolbar: {
title: t("Feature.Features"),
actions: [
<Tooltip key="create" title={t("Create")}>
<RouterIconButton to="/features/create">
<Add fontSize="small" />
</RouterIconButton>
</Tooltip>,
Object.keys(cellSelectionModel).length === 1 && (
<Tooltip key="edit" title={t("Edit")}>
<RouterIconButton
to="/features/edit/$id"
params={{ id: Object.keys(cellSelectionModel)[0].toString() }}
>
<Edit fontSize="small" />
</RouterIconButton>
</Tooltip>
),
Object.keys(cellSelectionModel).length > 0 && (
<Tooltip key="delete" title={t("Delete")}>
<IconButton
onClick={async () => {
const confirmed = await confirm(
t("DeleteConfirmation", { count: Object.keys(cellSelectionModel).length }),
);
if (confirmed) {
try {
const transaction = handleOnDelete(
Object.keys(cellSelectionModel).map((id) => id.toString()),
);
await transaction.isPersisted.promise;
setCellSelectionModel({});
} catch (error) {
if (error instanceof Error) await alert(error.message);
}
}
}}
>
<Badge
badgeContent={Object.keys(cellSelectionModel).length}
invisible={Object.keys(cellSelectionModel).length <= 1}
color="error"
>
<Delete fontSize="small" />
</Badge>
</IconButton>
</Tooltip>
),
],
},
}}
/>
</Fullscreen>
);
```