import {Table, TableBody, TableCell, TableHead, TableRow} from "@material-ui/core"; import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward"; import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward"; import React, {useCallback, useContext, useEffect, useState} from "react"; import "./data-table.css"; import {LocaleContext} from "../locale"; import clsx from "clsx"; import {Box, IconButton} from "@mui/material"; import {formatDateTime} from "../util"; import CachedIcon from "@material-ui/icons/Cached"; export function DataTable(props) { const { className, placeholder, columns, data, pagination, fetchData, onClick, onFilter, defaultSortColumn, defaultSortOrder, title, ...other } = props; const {translate: L} = useContext(LocaleContext); const [doFetchData, setFetchData] = useState(false); const [sortAscending, setSortAscending] = useState(["asc","ascending"].includes(defaultSortOrder?.toLowerCase)); const [sortColumn, setSortColumn] = useState(defaultSortColumn || null); const sortable = props.hasOwnProperty("sortable") ? !!props.sortable : true; const onRowClick = onClick || (() => {}); const onFetchData = useCallback((force = false) => { if (doFetchData || force) { setFetchData(false); const orderBy = columns[sortColumn]?.field || null; const sortOrder = sortAscending ? "asc" : "desc"; fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder); } }, [doFetchData, columns, sortColumn, sortAscending, pagination]); // pagination changed? useEffect(() => { let forceFetch = false; if (pagination.getPageSize() < pagination.getTotal()) { // page size is smaller than the total count forceFetch = true; } else if (data?.length && pagination.getPageSize() >= data.length && data.length < pagination.getTotal()) { // page size is greater than the current visible count but there were hidden rows before forceFetch = true; } onFetchData(forceFetch); }, [pagination.data.pageSize, pagination.data.current]); // sorting changed useEffect(() => { onFetchData(true); }, [sortAscending, sortColumn]); let headerRow = []; const onChangeSort = useCallback((index, column) => { if (sortable && column.sortable) { if (sortColumn === index) { setSortAscending(!sortAscending); } else { setSortColumn(index); } } }, [onFetchData, sortColumn, sortAscending]); for (const [index, column] of columns.entries()) { if (!(column instanceof DataColumn)) { throw new Error("DataTable can only have DataColumn-objects as column definition, got: " + typeof column); } if (sortable && column.sortable) { headerRow.push( onChangeSort(index, column)} align={column.align}> {sortColumn === index ? (sortAscending ? : ): <>}{column.renderHead(index)} ); } else { headerRow.push( {column.renderHead(index)} ); } } const numColumns = columns.length; let numRows = 0; let rows = []; if (data && data?.length) { numRows = data.length; for (const [rowIndex, entry] of data.entries()) { let row = []; for (const [index, column] of columns.entries()) { row.push( {column.renderData(L, entry, index)} ); } rows.push( ["tr","td"].includes(e.target.tagName.toLowerCase()) && onRowClick(rowIndex, entry)} key={"row-" + rowIndex}> { row } ); } } else if (placeholder) { rows.push( { placeholder } ); } return

onFetchData(true)}> {title}

{ headerRow } { rows }
{pagination.renderPagination(L, numRows)}
} export class DataColumn { constructor(label, field = null, params = {}) { this.label = label; this.field = field; this.sortable = !params.hasOwnProperty("sortable") || !!params.sortable; this.align = params.align || "left"; this.params = params; } renderData(L, entry, index) { return typeof this.field === 'function' ? this.field(entry) : entry[this.field]; } renderHead() { return this.label; } } export class StringColumn extends DataColumn { constructor(label, field = null, params = {}) { super(label, field, params); } renderData(L, entry, index) { let data = super.renderData(L, entry, index); if (this.params.style) { data = {data} } return data; } } export class NumericColumn extends DataColumn { constructor(label, field = null, params = {}) { super(label, field, params); this.decimalDigits = params.decimalDigits || null; this.integerDigits = params.integerDigits || null; this.prefix = params.prefix || ""; this.suffix = params.suffix || ""; this.decimalChar = params.decimalChar || "."; } renderData(L, entry, index) { let number = super.renderData(L, entry).toString(); if (this.decimalDigits !== null) { number = number.toFixed(this.decimalDigits); } if (this.integerDigits !== null) { let currentLength = number.split(".")[0].length; if (currentLength < this.integerDigits) { number = number.padStart(this.integerDigits - currentLength, "0"); } } if (this.decimalChar !== ".") { number = number.replace(".", this.decimalChar); } return this.prefix + number + this.suffix; } } export class DateTimeColumn extends DataColumn { constructor(label, field = null, params = {}) { super(label, field, params); this.precise = !!params.precise; } renderData(L, entry, index) { let date = super.renderData(L, entry); return formatDateTime(L, date, this.precise); } } export class BoolColumn extends DataColumn { constructor(label, field = null, params = {}) { super(label, field, params); } renderData(L, entry, index) { let data = super.renderData(L, entry); return L(data ? "general.yes" : "general.no"); } } export class ControlsColumn extends DataColumn { constructor(label, buttons = [], params = {}) { super(label, null, { align: "center", ...params, sortable: false }); this.buttons = buttons; } renderData(L, entry, index) { let buttonElements = []; for (const [index, button] of this.buttons.entries()) { let element = typeof button.element === 'function' ? button.element(entry, index) : button.element; let buttonProps = {}; if (typeof button.props === 'function') { buttonProps = button.props(entry, index); } else { buttonProps = button.props; } let props = { ...buttonProps, key: "button-" + index, onClick: (e) => { e.stopPropagation(); button.onClick(entry, index); }, className: "data-table-clickable", } if (button.hasOwnProperty("disabled")) { props.disabled = typeof button.disabled === 'function' ? button.disabled(entry, index) : button.disabled; } if ((!button.hasOwnProperty("hidden")) || (typeof button.hidden === 'function' && !button.hidden(entry, index)) || (!button.hidden)) { buttonElements.push(React.createElement(element, props)) } } return <> {buttonElements} } }