/* QQQ - Low-code Application Framework for Engineers. * Copyright (C) 2021-2022. Kingsrook, LLC * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States * contact@kingsrook.com * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import {tooltipClasses, TooltipProps} from "@mui/material"; import Autocomplete from "@mui/material/Autocomplete"; import Box from "@mui/material/Box"; import Icon from "@mui/material/Icon"; import {styled} from "@mui/material/styles"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableContainer from "@mui/material/TableContainer"; import TableRow from "@mui/material/TableRow"; import Tooltip from "@mui/material/Tooltip"; import parse from "html-react-parser"; import {useEffect, useMemo, useState} from "react"; import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable, useExpanded} from "react-table"; import colors from "qqq/assets/theme/base/colors"; import MDInput from "qqq/components/legacy/MDInput"; import MDPagination from "qqq/components/legacy/MDPagination"; import MDTypography from "qqq/components/legacy/MDTypography"; import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell"; import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell"; import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell"; import ImageCell from "qqq/components/widgets/tables/cells/ImageCell"; import {TableDataInput} from "qqq/components/widgets/tables/TableCard"; interface Props { entriesPerPage?: number; entriesPerPageOptions?: number[]; canSearch?: boolean; showTotalEntries?: boolean; hidePaginationDropdown?: boolean; fixedStickyLastRow?: boolean; fixedHeight?: number; table: TableDataInput; pagination?: { variant: "contained" | "gradient"; color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "light"; }; isSorted?: boolean; noEndBorder?: boolean; } DataTable.defaultProps = { entriesPerPage: 10, entriesPerPageOptions: ["5", "10", "15", "20", "25"], canSearch: false, showTotalEntries: true, fixedStickyLastRow: false, fixedHeight: null, pagination: {variant: "gradient", color: "info"}, isSorted: true, noEndBorder: false, }; const NoMaxWidthTooltip = styled(({className, ...props}: TooltipProps) => ( ))({ [`& .${tooltipClasses.tooltip}`]: { maxWidth: "none", textAlign: "left" }, }); function DataTable({ entriesPerPage, entriesPerPageOptions, hidePaginationDropdown, canSearch, showTotalEntries, fixedStickyLastRow, fixedHeight, table, pagination, isSorted, noEndBorder, }: Props): JSX.Element { let defaultValue: any; let entries: any[]; defaultValue = (entriesPerPage) ? entriesPerPage : "10"; entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"]; let widths = []; for(let i = 0; i ( // // {isAllRowsExpanded ? "yes" : "no"} // // ), header: () => (), // @ts-ignore cell: ({row}) => ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Use the row.canExpand and row.getToggleRowExpandedProps prop getter to build the toggle for expanding a row // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// row.canExpand ? ( {/* float this icon to keep it "out of the flow" - in other words, to keep it from making the row taller than it otherwise would be... */} {row.isExpanded ? "expand_less" : "chevron_right"} ) : null, }, ); } const columns = useMemo(() => columnsToMemo, [table]); const data = useMemo(() => table.rows, [table]); const gridTemplateColumns = widths.join(" "); if (!columns || !data) { return null; } const tableInstance = useTable( {columns, data, initialState: {pageIndex: 0}}, useGlobalFilter, useSortBy, useExpanded, usePagination ); const { getTableProps, getTableBodyProps, headerGroups, prepareRow, rows, page, pageOptions, canPreviousPage, canNextPage, gotoPage, nextPage, previousPage, setPageSize, setGlobalFilter, state: {pageIndex, pageSize, globalFilter, expanded}, }: any = tableInstance; // Set the default value for the entries per page when component mounts useEffect(() => setPageSize(defaultValue || 10), [defaultValue]); // Set the entries per page value based on the select value const setEntriesPerPage = (value: any) => setPageSize(value); // Render the pagination const renderPagination = pageOptions.map((option: any) => ( gotoPage(Number(option))} active={pageIndex === option} > {option + 1} )); // Handler for the input to set the pagination index const handleInputPagination = ({target: {value}}: any) => value > pageOptions.length || value < 0 ? gotoPage(0) : gotoPage(Number(value)); // Customized page options starting from 1 const customizedPageOptions = pageOptions.map((option: any) => option + 1); // Setting value for the pagination input const handleInputPaginationValue = ({target: value}: any) => gotoPage(Number(value.value - 1)); // Search input value state const [search, setSearch] = useState(globalFilter); // Search input state handle const onSearchChange = useAsyncDebounce((value) => { setGlobalFilter(value || undefined); }, 100); // A function that sets the sorted value for the table const setSortedValue = (column: any) => { let sortedValue; if (isSorted && column.isSorted) { sortedValue = column.isSortedDesc ? "desc" : "asc"; } else if (isSorted) { sortedValue = "none"; } else { sortedValue = false; } return sortedValue; }; // Setting the entries starting point const entriesStart = pageIndex === 0 ? pageIndex + 1 : pageIndex * pageSize + 1; // Setting the entries ending point let entriesEnd; if (pageIndex === 0) { entriesEnd = pageSize; } else if (pageIndex === pageOptions.length - 1) { entriesEnd = rows.length; } else { entriesEnd = pageSize * (pageIndex + 1); } function getTable(includeHead: boolean, rows: any, isFooter: boolean) { let boxStyle = {}; if(fixedStickyLastRow) { boxStyle = isFooter ? {borderTop: `0.0625rem solid ${colors.grayLines.main};`, overflow: "auto", scrollbarGutter: "stable"} : {height: fixedHeight ? `${fixedHeight}px` : "360px", overflowY: "scroll", scrollbarGutter: "stable", marginBottom: "-1px"}; } return { includeHead && ( {headerGroups.map((headerGroup: any, i: number) => ( {headerGroup.headers.map((column: any) => ( column.type !== "hidden" && ( {column.render("header")} ) ))} ))} ) } {rows.map((row: any, key: any) => { prepareRow(row); let overrideNoEndBorder = false; ////////////////////////////////////////////////////////////////////////////////// // don't do an end-border on nested rows - unless they're the last one in a set // ////////////////////////////////////////////////////////////////////////////////// if(row.depth > 0) { overrideNoEndBorder = true; if(key + 1 < rows.length && rows[key + 1].depth == 0) { overrideNoEndBorder = false; } } /////////////////////////////////////// // don't do end-border on the footer // /////////////////////////////////////// if(isFooter) { overrideNoEndBorder = true; } return ( 0 ? "#FAFAFA" : "initial")}} key={key} {...row.getRowProps()}> {row.cells.map((cell: any) => ( cell.column.type !== "hidden" && ( { cell.column.type === "default" && ( cell.value && "number" === typeof cell.value ? ( {cell.value.toLocaleString()} ) : ({cell.render("Cell")}) ) } { cell.column.type === "htmlAndTooltip" && ( {parse(cell.value)} ) } { cell.column.type === "html" && ( {parse(cell.value)} ) } { cell.column.type === "image" && row.values["imageTotal"] && ( ) } { cell.column.type === "image" && !row.values["imageTotal"] && ( ) } { (cell.column.id === "__expander") && cell.render("cell") } ) ))} ); })}
} return ( {entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? ( {entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && ( { if (typeof newValues === "string") { setEntriesPerPage(parseInt(newValues, 10)); } else { setEntriesPerPage(parseInt(newValues[0], 10)); } }} size="small" sx={{width: "5rem"}} renderInput={(params) => } />   entries per page )} {canSearch && ( { setSearch(search); onSearchChange(currentTarget.value); }} /> )} ) : null} { fixedStickyLastRow ? ( <> {getTable(true, page.slice(0, page.length -1), false)} {getTable(false, page.slice(page.length-1), true)} ) : getTable(true, page, false) } {showTotalEntries && ( Showing {entriesStart} to {entriesEnd} of {rows.length} entries )} {pageOptions.length > 1 && ( {canPreviousPage && ( previousPage()}> chevron_left )} {renderPagination.length > 6 ? ( { handleInputPagination(event); handleInputPaginationValue(event); }} /> ) : ( renderPagination )} {canNextPage && ( nextPage()}> chevron_right )} )} ); } export default DataTable;