UNPKG

@etsoo/materialui

Version:

TypeScript Material-UI Implementation

298 lines (297 loc) 13.4 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { GridAlignGet, GridSizeGet } from "@etsoo/react"; import { DataTypes } from "@etsoo/shared"; import React from "react"; import { DataGridRenderers } from "./DataGridRenderers"; import Table from "@mui/material/Table"; import { useTheme } from "@mui/material/styles"; import Paper from "@mui/material/Paper"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import TableCell from "@mui/material/TableCell"; import Checkbox from "@mui/material/Checkbox"; import TableSortLabel from "@mui/material/TableSortLabel"; import TableBody from "@mui/material/TableBody"; import TablePagination from "@mui/material/TablePagination"; /** * Extended table min width for width-unset column */ export const TableExMinWidth = 180; /** * Extended Table * @param props Props * @returns Component */ export function TableEx(props) { // Theme const theme = useTheme(); // Destruct const { alternatingColors = [theme.palette.action.hover, undefined], autoLoad = true, columns, defaultOrderBy, headerColors = [undefined, undefined], idField = "id", loadBatchSize, loadData, maxHeight, mRef, onSelectChange, rowHeight = 53, otherHeight = 110, threshold, ...rest } = props; const selectable = onSelectChange != null; // Rows per page let rowsPerPageLocal; if (maxHeight != null) { if (loadBatchSize != null) rowsPerPageLocal = GridSizeGet(loadBatchSize, maxHeight); else rowsPerPageLocal = Math.floor((maxHeight - otherHeight) / rowHeight); } else if (typeof loadBatchSize === "number") { rowsPerPageLocal = loadBatchSize; } else { rowsPerPageLocal = 10; } // Rows const [rows, updateRows] = React.useState([]); const setRows = (rows) => { state.loadedItems = rows.length; updateRows(rows); }; // States const stateRefs = React.useRef({ queryPaging: { currentPage: 0, orderBy: defaultOrderBy, batchSize: rowsPerPageLocal }, autoLoad, loadedItems: 0, hasNextPage: true, isNextPageLoading: false, selectedItems: [], idCache: {} }); const state = stateRefs.current; // Reset the state and load again const reset = (add) => { const { queryPaging, ...rest } = add ?? {}; const resetState = { autoLoad: true, loadedItems: 0, hasNextPage: true, isNextPageLoading: false, lastLoadedItems: undefined, lastItem: undefined, ...rest }; Object.assign(state, resetState); Object.assign(state.queryPaging, { currentPage: 0, ...queryPaging }); }; React.useImperativeHandle(mRef, () => ({ delete(index) { const item = rows.at(index); if (item) { const newRows = [...rows]; newRows.splice(index, 1); setRows(newRows); } return item; }, insert(item, start) { const newRows = [...rows]; newRows.splice(start, 0, item); setRows(newRows); }, /** * Refresh data */ refresh() { loadDataLocal(); }, /** * Reset */ reset, scrollToRef(scrollOffset) { // Not implemented }, scrollToItemRef(index) { // Not implemented } }), []); // Load data const loadDataLocal = () => { // Prevent multiple loadings if (!state.hasNextPage || state.isNextPageLoading) return; // Update state state.isNextPageLoading = true; // Parameters const { queryPaging, data, isMounted } = state; const loadProps = { queryPaging, data }; loadData(loadProps).then((result) => { state.isMounted = true; if (result == null || isMounted === false) { return; } const newItems = result.length; state.lastLoadedItems = newItems; state.hasNextPage = newItems >= queryPaging.batchSize; state.isNextPageLoading = false; // Update rows setRows(result); }); }; const handleChangePage = (_event, newPage) => { state.hasNextPage = true; state.queryPaging.currentPage = newPage; loadDataLocal(); }; const handleChangeRowsPerPage = (event) => { const batchSize = parseInt(event.target.value); reset({ queryPaging: { batchSize } }); }; const handleSelect = (item, checked) => { const selectedItems = state.selectedItems; const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === item[idField]); if (checked) { if (index === -1) selectedItems.push(item); } else { if (index !== -1) selectedItems.splice(index, 1); } if (onSelectChange != null) { onSelectChange(selectedItems); } }; const handleSelectAll = (checked) => { const selectedItems = state.selectedItems; rows.forEach((row) => { const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === row[idField]); if (checked) { if (index === -1) selectedItems.push(row); } else if (index !== -1) { selectedItems.splice(index, 1); } }); if (onSelectChange != null) { onSelectChange(selectedItems); } }; // New sort const handleSort = (field, asc) => { reset({ queryPaging: { orderBy: [{ field, desc: !(asc ?? true) }] } }); }; // Set items for rerenderer const setItems = (callback) => { const result = callback(rows); if (result == null) return; setRows(result); }; // Destruct states const { queryPaging, autoLoad: stateAutoLoad, hasNextPage, lastLoadedItems, selectedItems } = state; const currentPage = queryPaging.currentPage ?? 0; const batchSize = queryPaging.batchSize; // Current page selected items const pageSelectedItems = selectable ? rows.reduce((previousValue, currentItem) => { if (selectedItems.some((item) => item[idField] === currentItem[idField])) return previousValue + 1; return previousValue; }, 0) : 0; // Total rows const totalRows = hasNextPage ? -1 : currentPage * batchSize + (lastLoadedItems ?? 0); // Auto load data when current page is 0 if (currentPage === 0 && stateAutoLoad && lastLoadedItems == null) loadDataLocal(); React.useEffect(() => { return () => { state.isMounted = false; }; }, []); // Layout return (_jsxs(Paper, { children: [_jsx(TableContainer, { sx: { maxHeight }, children: _jsxs(Table, { ...rest, children: [_jsx(TableHead, { children: _jsxs(TableRow, { sx: { "& th": { backgroundColor: headerColors[0], color: headerColors[1] } }, children: [selectable && (_jsx(TableCell, { padding: "checkbox", children: _jsx(Checkbox, { color: "primary", indeterminate: pageSelectedItems > 0 && pageSelectedItems < rows.length, checked: pageSelectedItems > 0, onChange: (_event, checked) => handleSelectAll(checked) }) })), columns.map((column, index) => { // Destruct const { align, field, header, minWidth, sortable, sortAsc = true, type, width } = column; // Header text const headerText = header ?? field; // Sortable let sortLabel; if (sortable && field != null) { const active = queryPaging.orderBy?.some((o) => o.field.toLowerCase() === field.toLowerCase()); sortLabel = (_jsx(TableSortLabel, { active: active, direction: sortAsc ? "asc" : "desc", onClick: (_event) => { if (active) column.sortAsc = !sortAsc; handleSort(field, column.sortAsc); }, children: headerText })); } else { sortLabel = headerText; } return (_jsx(TableCell, { align: GridAlignGet(align, type), width: width, sx: { minWidth: minWidth == null ? width == null ? TableExMinWidth : undefined : minWidth }, children: sortLabel }, field ?? index.toString())); })] }) }), _jsx(TableBody, { sx: { "& tr:nth-of-type(odd):not(.Mui-selected)": { backgroundColor: alternatingColors[0] }, "& tr:nth-of-type(even):not(.Mui-selected)": { backgroundColor: alternatingColors[1] } }, children: [...Array(queryPaging.batchSize)].map((_item, rowIndex) => { // Row const row = rowIndex < rows.length ? rows[rowIndex] : undefined; // Row id field value const rowId = DataTypes.getValue(row, idField) ?? rowIndex; // Selected or not const isItemSelected = selectable ? selectedItems.some((item) => item[idField] === rowId) : false; return (_jsxs(TableRow, { selected: isItemSelected, children: [selectable && (_jsx(TableCell, { padding: "checkbox", children: row && (_jsx(Checkbox, { color: "primary", checked: isItemSelected, onChange: (_event, checked) => handleSelect(row, checked) })) })), columns.map(({ align, cellRenderer = DataGridRenderers.defaultCellRenderer, field, type, valueFormatter }, columnIndex) => { const formatProps = { data: row, field, rowIndex, columnIndex }; const cellProps = { align: GridAlignGet(align, type), valign: "middle" }; const child = row ? (cellRenderer({ data: row, field, formattedValue: valueFormatter ? valueFormatter(formatProps) : undefined, selected: isItemSelected, type, rowIndex, columnIndex, cellProps, setItems })) : (_jsx(React.Fragment, { children: "\u00A0" })); return (_jsx(TableCell, { ...cellProps, children: child }, `${rowId}${columnIndex}`)); })] }, rowId)); }) })] }) }), _jsx(TablePagination, { component: "div", showFirstButton: true, count: totalRows, rowsPerPage: batchSize, page: currentPage, onPageChange: handleChangePage, onRowsPerPageChange: handleChangeRowsPerPage, rowsPerPageOptions: [ batchSize, 2 * batchSize, 5 * batchSize, 10 * batchSize ] })] })); }