Skip to content

Commit

Permalink
feat(ui-common): add GroupedDataTable
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Aug 10, 2023
1 parent e88fb69 commit 3a1a789
Show file tree
Hide file tree
Showing 10 changed files with 770 additions and 0 deletions.
1 change: 1 addition & 0 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"form.submit.inProgress": "The form is being submitted. Are you sure you want to leave the page?",
"form.asyncDefaultValues.error": "Failed to get values",
"form.field.required": "Field required",
"form.field.duplicate": "Value already exists: {{0}}",
"form.field.minLength": "{{0}} character(s) minimum",
"form.field.minValue": "The minimum value is {{0}}",
"form.field.maxValue": "The maximum value is {{0}}",
Expand Down
1 change: 1 addition & 0 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"form.submit.inProgress": "Le formulaire est en cours de soumission. Etes-vous sûr de vouloir quitter la page ?",
"form.asyncDefaultValues.error": "Impossible d'obtenir les valeurs",
"form.field.required": "Champ requis",
"form.field.duplicate": "Cette valeur existe déjà: {{0}}",
"form.field.minLength": "{{0}} caractère(s) minimum",
"form.field.minValue": "La valeur minimum est {{0}}",
"form.field.maxValue": "La valeur maximum est {{0}}",
Expand Down
86 changes: 86 additions & 0 deletions webapp/src/components/common/DynamicDataTable/TableRowGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
TableRow,
TableCell,
IconButton,
Box,
Typography,
} from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import { ChangeEvent, useMemo, useState } from "react";
import TableRowItem from "./TableRowItem";
import { Item, Column, calculateColumnResults } from "./utils";

interface Props {
itemsByGroup: { group?: string; items: Item[] };
columns: Column[];
selected: string[];
onClick: (e: ChangeEvent<HTMLInputElement>, id: string) => void;
}

function TableRowGroup({
itemsByGroup: { group, items },
columns,
selected,
onClick,
}: Props) {
const [openRow, setOpenRow] = useState(false);
const columnResults = useMemo(
() => calculateColumnResults(columns, items),
[columns, items]
);

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<>
{group && (
<TableRow
sx={{
borderBottom: "2px solid rgba(224, 224, 224, 0.3)",
}}
>
{/* Merge the first two columns into one. The first column, which is always "name",
* does not contain an operation so no value will be displayed on the TableRowGroup header. */}
<TableCell colSpan={2} sx={{ py: 0 }}>
<Box sx={{ display: "flex", alignItems: "center", my: 1 }}>
<IconButton size="small" onClick={() => setOpenRow(!openRow)}>
{openRow ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
<Typography sx={{ ml: 2 }}>{group}</Typography>
</Box>
</TableCell>
{/* Skip the first column since it's already included in the merged TableCell above. */}
{columns.slice(1).map((column) => (
<TableCell key={column.name} align="center">
{column.operation && (
<Typography
variant="body2"
sx={{
color: "rgba(255, 255, 255, 0.5)",
}}
>
{columnResults[column.name]}
</Typography>
)}
</TableCell>
))}
</TableRow>
)}
{openRow &&
items.map((item) => (
<TableRowItem
key={item.id}
item={item}
columns={columns}
selected={selected}
onClick={onClick}
/>
))}
</>
);
}

export default TableRowGroup;
66 changes: 66 additions & 0 deletions webapp/src/components/common/DynamicDataTable/TableRowItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { TableCell, Checkbox, Chip, TableRow } from "@mui/material";
import { ChangeEvent, memo, useCallback } from "react";
import { Item, Column } from "./utils";

interface Props {
item: Item;
columns: Column[];
selected: string[];
onClick: (e: ChangeEvent<HTMLInputElement>, name: string) => void;
}

function TableRowItem({ item, columns, selected, onClick }: Props) {
const isSelected = selected.includes(item.id);

////////////////////////////////////////////////////////////////
// Event handlers
////////////////////////////////////////////////////////////////

const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
onClick(e, item.id);
},
[item.id, onClick]
);

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<TableRow
sx={{ "& > *": { borderBottom: "none !important" } }}
selected={isSelected}
>
<TableCell padding="none">
<Checkbox
color="primary"
checked={isSelected}
onChange={handleChange}
/>
</TableCell>
{columns.map((column) => {
const cellValue = item.columns[column.name];
return (
<TableCell
key={column.name}
sx={{ py: 0 }}
align={typeof cellValue === "number" ? "center" : "left"}
>
{column.chipColorMap && typeof cellValue === "string" ? (
<Chip
label={cellValue}
size="small"
color={column.chipColorMap[cellValue]}
/>
) : (
cellValue
)}
</TableCell>
);
})}
</TableRow>
);
}

export default memo(TableRowItem);
54 changes: 54 additions & 0 deletions webapp/src/components/common/DynamicDataTable/TableToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Toolbar,
alpha,
Typography,
Tooltip,
IconButton,
Fade,
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { useTranslation } from "react-i18next";

interface Props {
numSelected: number;
handleDelete: () => void;
}

function TableToolbar({ numSelected, handleDelete }: Props) {
const { t } = useTranslation();

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<Fade in={numSelected > 0} timeout={300}>
<Toolbar
sx={{
...(numSelected > 0 && {
bgcolor: (theme) =>
alpha(
theme.palette.primary.main,
theme.palette.action.activatedOpacity
),
}),
}}
>
{numSelected > 0 && (
<>
<Typography sx={{ flex: 1 }}>
{numSelected} {t("global.selected")}
</Typography>
<Tooltip title={t("global.delete")}>
<IconButton onClick={handleDelete}>
<DeleteIcon />
</IconButton>
</Tooltip>
</>
)}
</Toolbar>
</Fade>
);
}

export default TableToolbar;
Loading

0 comments on commit 3a1a789

Please sign in to comment.