/* 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;