forked from DIRACGrid/diracx-web
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: replace mui-x-data-grid with mui-core table
- Loading branch information
Showing
6 changed files
with
496 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
import * as React from "react"; | ||
import Box from "@mui/material/Box"; | ||
import Table from "@mui/material/Table"; | ||
import TableBody from "@mui/material/TableBody"; | ||
import TableCell from "@mui/material/TableCell"; | ||
import TableContainer from "@mui/material/TableContainer"; | ||
import TableHead from "@mui/material/TableHead"; | ||
import TablePagination from "@mui/material/TablePagination"; | ||
import TableRow from "@mui/material/TableRow"; | ||
import TableSortLabel from "@mui/material/TableSortLabel"; | ||
import Paper from "@mui/material/Paper"; | ||
import Checkbox from "@mui/material/Checkbox"; | ||
import { visuallyHidden } from "@mui/utils"; | ||
|
||
/** | ||
* Descending comparator function | ||
* @param a - the first value to compare | ||
* @param b - the second value to compare | ||
* @param orderBy - the key to compare | ||
* @returns -1 if b is less than a, 1 if b is greater than a, 0 if they are equal | ||
* @template T - the type of the values to compare | ||
*/ | ||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { | ||
if (b[orderBy] < a[orderBy]) { | ||
return -1; | ||
} | ||
if (b[orderBy] > a[orderBy]) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
|
||
type Order = "asc" | "desc"; | ||
|
||
/** | ||
* Get the comparator function for a given key and order | ||
* @param order - the order to sort by | ||
* @param orderBy - the key to sort by | ||
* @returns a comparator function | ||
* @template Key - the type of the key to sort by | ||
*/ | ||
function getComparator<Key extends keyof any>( | ||
order: Order, | ||
orderBy: Key, | ||
): ( | ||
a: { [key in Key]: number | string }, | ||
b: { [key in Key]: number | string }, | ||
) => number { | ||
return order === "desc" | ||
? (a, b) => descendingComparator(a, b, orderBy) | ||
: (a, b) => -descendingComparator(a, b, orderBy); | ||
} | ||
|
||
/** | ||
* Stable sort function | ||
* @param array - the array to sort | ||
* @param comparator - the comparator function | ||
* @returns the sorted array | ||
* @template T - the type of the array to sort | ||
*/ | ||
function stableSort<T>( | ||
array: readonly T[], | ||
comparator: (a: T, b: T) => number, | ||
) { | ||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); | ||
stabilizedThis.sort((a, b) => { | ||
const order = comparator(a[0], b[0]); | ||
if (order !== 0) return order; | ||
return a[1] - b[1]; | ||
}); | ||
return stabilizedThis.map((el) => el[0]); | ||
} | ||
|
||
/** | ||
* The head cells for the table. | ||
* Components using this table should provide a list of head cells. | ||
* @property {number | string} id - the id of the cell | ||
* @property {string} label - the label of the cell | ||
*/ | ||
export interface HeadCell { | ||
id: number | string; | ||
label: string; | ||
render?: ((value: any) => JSX.Element) | null; | ||
} | ||
|
||
/** | ||
* Enhanced table props | ||
* @property {HeadCell[]} headCells - the head cells for the table | ||
* @property {number} numSelected - the number of selected rows | ||
* @property {function} onRequestSort - the function to call when sorting is requested | ||
* @property {function} onSelectAllClick - the function to call when all rows are selected | ||
* @property {Order} order - the order to sort by | ||
* @property {string} orderBy - the key to sort by | ||
* @property {number} rowCount - the number of rows | ||
*/ | ||
interface EnhancedTableProps { | ||
headCells: HeadCell[]; | ||
numSelected: number; | ||
onRequestSort: ( | ||
event: React.MouseEvent<unknown>, | ||
property: string | number, | ||
) => void; | ||
onSelectAllClick: ( | ||
event: React.ChangeEvent<HTMLInputElement>, | ||
checked: boolean, | ||
) => void; | ||
order: Order; | ||
orderBy: string; | ||
rowCount: number; | ||
} | ||
|
||
/** | ||
* Data table head component | ||
* @param {EnhancedTableProps} props - the props for the component | ||
*/ | ||
function DataTableHead(props: EnhancedTableProps) { | ||
const { | ||
headCells, | ||
onSelectAllClick, | ||
order, | ||
orderBy, | ||
numSelected, | ||
rowCount, | ||
onRequestSort, | ||
} = props; | ||
const createSortHandler = | ||
(property: string | number) => (event: React.MouseEvent<unknown>) => { | ||
onRequestSort(event, property); | ||
}; | ||
|
||
return ( | ||
<TableHead> | ||
<TableRow> | ||
<TableCell padding="checkbox"> | ||
<Checkbox | ||
color="primary" | ||
indeterminate={numSelected > 0 && numSelected < rowCount} | ||
checked={rowCount > 0 && numSelected === rowCount} | ||
onChange={onSelectAllClick} | ||
inputProps={{ "aria-label": "select all items" }} | ||
/> | ||
</TableCell> | ||
{headCells.map((headCell) => ( | ||
<TableCell | ||
key={headCell.id} | ||
sortDirection={orderBy === headCell.id ? order : false} | ||
> | ||
<TableSortLabel | ||
active={orderBy === headCell.id} | ||
direction={orderBy === headCell.id ? order : "asc"} | ||
onClick={createSortHandler(headCell.id)} | ||
> | ||
{headCell.label} | ||
{orderBy === headCell.id ? ( | ||
<Box component="span" sx={visuallyHidden}> | ||
{order === "desc" ? "sorted descending" : "sorted ascending"} | ||
</Box> | ||
) : null} | ||
</TableSortLabel> | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
</TableHead> | ||
); | ||
} | ||
|
||
/** | ||
* Data table props | ||
* @property {HeadCell[]} columns - the columns for the table | ||
* @property {any[]} rows - the rows for the table | ||
*/ | ||
interface DataTableProps { | ||
columns: HeadCell[]; | ||
rows: any[]; | ||
rowIdentifier: string; | ||
isMobile: boolean; | ||
} | ||
|
||
/** | ||
* Data table component | ||
* @param {DataTableProps} props - the props for the component | ||
* @returns a DataTable component | ||
*/ | ||
export function DataTable(props: DataTableProps) { | ||
const { columns, rows, rowIdentifier, isMobile } = props; | ||
const [order, setOrder] = React.useState<Order>("asc"); | ||
const [orderBy, setOrderBy] = React.useState<string | number>(rowIdentifier); | ||
const [selected, setSelected] = React.useState<readonly number[]>([]); | ||
const [page, setPage] = React.useState(0); | ||
const [rowsPerPage, setRowsPerPage] = React.useState(25); | ||
|
||
const handleRequestSort = ( | ||
event: React.MouseEvent<unknown>, | ||
property: string | number, | ||
) => { | ||
const isAsc = orderBy === property && order === "asc"; | ||
setOrder(isAsc ? "desc" : "asc"); | ||
setOrderBy(property); | ||
}; | ||
|
||
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
if (event.target.checked) { | ||
const newSelected = rows.map((n: any) => n[rowIdentifier]); | ||
setSelected(newSelected); | ||
return; | ||
} | ||
setSelected([]); | ||
}; | ||
|
||
const handleClick = (event: React.MouseEvent<unknown>, id: number) => { | ||
const selectedIndex = selected.indexOf(id); | ||
let newSelected: readonly number[] = []; | ||
|
||
if (selectedIndex === -1) { | ||
newSelected = newSelected.concat(selected, id); | ||
} else if (selectedIndex === 0) { | ||
newSelected = newSelected.concat(selected.slice(1)); | ||
} else if (selectedIndex === selected.length - 1) { | ||
newSelected = newSelected.concat(selected.slice(0, -1)); | ||
} else if (selectedIndex > 0) { | ||
newSelected = newSelected.concat( | ||
selected.slice(0, selectedIndex), | ||
selected.slice(selectedIndex + 1), | ||
); | ||
} | ||
|
||
setSelected(newSelected); | ||
}; | ||
|
||
const handleChangePage = (event: unknown, newPage: number) => { | ||
setPage(newPage); | ||
}; | ||
|
||
const handleChangeRowsPerPage = ( | ||
event: React.ChangeEvent<HTMLInputElement>, | ||
) => { | ||
setRowsPerPage(parseInt(event.target.value, 10)); | ||
setPage(0); | ||
}; | ||
|
||
const isSelected = (name: number) => selected.indexOf(name) !== -1; | ||
|
||
// Calculate the number of empty rows needed to fill the space | ||
const emptyRows = | ||
rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage); | ||
|
||
return ( | ||
<Box sx={{ width: "100%" }}> | ||
<Paper sx={{ width: "100%", mb: 2 }}> | ||
<TableContainer sx={{ maxHeight: "75vh" }}> | ||
<Table | ||
stickyHeader | ||
sx={{ minWidth: isMobile ? "undefined" : "90vw" }} | ||
aria-labelledby="tableTitle" | ||
size={"medium"} | ||
> | ||
<DataTableHead | ||
headCells={columns} | ||
numSelected={selected.length} | ||
order={order} | ||
orderBy={orderBy.toString()} | ||
onSelectAllClick={handleSelectAllClick} | ||
onRequestSort={handleRequestSort} | ||
rowCount={rows.length} | ||
/> | ||
<TableBody> | ||
{stableSort(rows, getComparator(order, orderBy)) | ||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) | ||
.map((row, index) => { | ||
const isItemSelected = isSelected( | ||
row[rowIdentifier] as number, | ||
); | ||
const labelId = `enhanced-table-checkbox-${index}`; | ||
|
||
return ( | ||
<TableRow | ||
hover | ||
onClick={(event) => | ||
handleClick(event, row[rowIdentifier] as number) | ||
} | ||
role="checkbox" | ||
aria-checked={isItemSelected} | ||
tabIndex={-1} | ||
key={row[rowIdentifier]} | ||
selected={isItemSelected} | ||
> | ||
<TableCell padding="checkbox"> | ||
<Checkbox | ||
color="primary" | ||
checked={isItemSelected} | ||
inputProps={{ "aria-labelledby": labelId }} | ||
/> | ||
</TableCell> | ||
{columns.map((column) => { | ||
const cellValue = row[column.id]; | ||
return ( | ||
<TableCell key={column.id}> | ||
{column.render | ||
? column.render(cellValue) | ||
: cellValue} | ||
</TableCell> | ||
); | ||
})} | ||
</TableRow> | ||
); | ||
})} | ||
{emptyRows > 0 && ( | ||
<TableRow style={{ height: 53 * emptyRows }}> | ||
<TableCell colSpan={columns.length + 1} /> | ||
</TableRow> | ||
)} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
<TablePagination | ||
rowsPerPageOptions={[25, 50, 100, 500, 1000]} | ||
component="div" | ||
count={rows.length} | ||
rowsPerPage={rowsPerPage} | ||
page={page} | ||
onPageChange={handleChangePage} | ||
onRowsPerPageChange={handleChangeRowsPerPage} | ||
labelRowsPerPage={isMobile ? "" : "Rows per page"} | ||
/> | ||
</Paper> | ||
</Box> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.