mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
CE-604 Updates to DataTable for fixed-sticky footer and expandable rows; Misc style updates going along with e.g., card border change
This commit is contained in:
@ -505,7 +505,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, omit
|
|||||||
|
|
||||||
if (wrapWidgetsInTabPanels)
|
if (wrapWidgetsInTabPanels)
|
||||||
{
|
{
|
||||||
renderedWidget = (<TabPanel index={i} value={selectedTab} style={{padding: "1rem 1.5rem 0 1.5rem", width: "100%"}}>
|
renderedWidget = (<TabPanel index={i} value={selectedTab} style={{padding: "1rem 0 0 1.5rem", width: "100%", marginBottom: "-1.5rem"}}>
|
||||||
{renderedWidget}
|
{renderedWidget}
|
||||||
</TabPanel>);
|
</TabPanel>);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import TableRow from "@mui/material/TableRow";
|
|||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import parse from "html-react-parser";
|
import parse from "html-react-parser";
|
||||||
import {useEffect, useMemo, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
import {useAsyncDebounce, useGlobalFilter, usePagination, useSortBy, useTable, useExpanded} from "react-table";
|
||||||
import MDInput from "qqq/components/legacy/MDInput";
|
import MDInput from "qqq/components/legacy/MDInput";
|
||||||
import MDPagination from "qqq/components/legacy/MDPagination";
|
import MDPagination from "qqq/components/legacy/MDPagination";
|
||||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||||
@ -47,6 +47,8 @@ interface Props
|
|||||||
canSearch?: boolean;
|
canSearch?: boolean;
|
||||||
showTotalEntries?: boolean;
|
showTotalEntries?: boolean;
|
||||||
hidePaginationDropdown?: boolean;
|
hidePaginationDropdown?: boolean;
|
||||||
|
fixedStickyLastRow?: boolean;
|
||||||
|
fixedHeight?: number;
|
||||||
table: TableDataInput;
|
table: TableDataInput;
|
||||||
pagination?: {
|
pagination?: {
|
||||||
variant: "contained" | "gradient";
|
variant: "contained" | "gradient";
|
||||||
@ -56,6 +58,18 @@ interface Props
|
|||||||
noEndBorder?: 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) => (
|
const NoMaxWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
||||||
<Tooltip {...props} classes={{popper: className}} />
|
<Tooltip {...props} classes={{popper: className}} />
|
||||||
))({
|
))({
|
||||||
@ -71,6 +85,8 @@ function DataTable({
|
|||||||
hidePaginationDropdown,
|
hidePaginationDropdown,
|
||||||
canSearch,
|
canSearch,
|
||||||
showTotalEntries,
|
showTotalEntries,
|
||||||
|
fixedStickyLastRow,
|
||||||
|
fixedHeight,
|
||||||
table,
|
table,
|
||||||
pagination,
|
pagination,
|
||||||
isSorted,
|
isSorted,
|
||||||
@ -83,8 +99,73 @@ function DataTable({
|
|||||||
defaultValue = (entriesPerPage) ? entriesPerPage : "10";
|
defaultValue = (entriesPerPage) ? entriesPerPage : "10";
|
||||||
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
|
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
|
||||||
|
|
||||||
const columns = useMemo<any>(() => table.columns, [table]);
|
let widths = [];
|
||||||
|
for(let i = 0; i<table.columns.length; i++)
|
||||||
|
{
|
||||||
|
const column = table.columns[i];
|
||||||
|
if(column.type !== "hidden")
|
||||||
|
{
|
||||||
|
widths.push(table.columns[i].width ?? "1fr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let showExpandColumn = false;
|
||||||
|
for (let i = 0; i < table.rows.length; i++)
|
||||||
|
{
|
||||||
|
if(table.rows[i].subRows)
|
||||||
|
{
|
||||||
|
showExpandColumn = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnsToMemo = [...table.columns];
|
||||||
|
if(showExpandColumn)
|
||||||
|
{
|
||||||
|
widths.push("60px");
|
||||||
|
columnsToMemo.push(
|
||||||
|
{
|
||||||
|
///////////////////////////////
|
||||||
|
// Build our expander column //
|
||||||
|
///////////////////////////////
|
||||||
|
id: "__expander",
|
||||||
|
width: 60,
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// use this block if we want to do expand-all //
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
// @ts-ignore
|
||||||
|
// header: ({getToggleAllRowsExpandedProps, isAllRowsExpanded}) => (
|
||||||
|
// <span {...getToggleAllRowsExpandedProps()}>
|
||||||
|
// {isAllRowsExpanded ? "yes" : "no"}
|
||||||
|
// </span>
|
||||||
|
// ),
|
||||||
|
header: () => (<span />),
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
cell: ({row}) =>
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter to build the toggle for expanding a row //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
row.canExpand ? (
|
||||||
|
<span
|
||||||
|
{...row.getToggleRowExpandedProps({
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// We could use the row.depth property and paddingLeft to indicate the depth of the row //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// style: {paddingLeft: `${row.depth * 2}rem`,},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon fontSize="medium">{row.isExpanded ? "expand_less" : "chevron_right"}</Icon>
|
||||||
|
</span>
|
||||||
|
) : null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = useMemo<any>(() => columnsToMemo, [table]);
|
||||||
const data = useMemo<any>(() => table.rows, [table]);
|
const data = useMemo<any>(() => table.rows, [table]);
|
||||||
|
const gridTemplateColumns = widths.join(" ");
|
||||||
|
|
||||||
if (!columns || !data)
|
if (!columns || !data)
|
||||||
{
|
{
|
||||||
@ -95,6 +176,7 @@ function DataTable({
|
|||||||
{columns, data, initialState: {pageIndex: 0}},
|
{columns, data, initialState: {pageIndex: 0}},
|
||||||
useGlobalFilter,
|
useGlobalFilter,
|
||||||
useSortBy,
|
useSortBy,
|
||||||
|
useExpanded,
|
||||||
usePagination
|
usePagination
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -113,7 +195,7 @@ function DataTable({
|
|||||||
previousPage,
|
previousPage,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
setGlobalFilter,
|
setGlobalFilter,
|
||||||
state: {pageIndex, pageSize, globalFilter},
|
state: {pageIndex, pageSize, globalFilter, expanded},
|
||||||
}: any = tableInstance;
|
}: any = tableInstance;
|
||||||
|
|
||||||
// Set the default value for the entries per page when component mounts
|
// Set the default value for the entries per page when component mounts
|
||||||
@ -193,63 +275,27 @@ function DataTable({
|
|||||||
entriesEnd = pageSize * (pageIndex + 1);
|
entriesEnd = pageSize * (pageIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function getTable(includeHead: boolean, rows: any, isFooter: boolean)
|
||||||
<TableContainer sx={{boxShadow: "none"}}>
|
|
||||||
{entriesPerPage && ((hidePaginationDropdown !== undefined && ! hidePaginationDropdown) || canSearch) ? (
|
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
|
||||||
{entriesPerPage && (hidePaginationDropdown === undefined || ! hidePaginationDropdown) && (
|
|
||||||
<Box display="flex" alignItems="center">
|
|
||||||
<Autocomplete
|
|
||||||
disableClearable
|
|
||||||
value={pageSize.toString()}
|
|
||||||
options={entries}
|
|
||||||
onChange={(event, newValues: any) =>
|
|
||||||
{
|
{
|
||||||
if(typeof newValues === "string")
|
let boxStyle = {};
|
||||||
|
if(fixedStickyLastRow)
|
||||||
{
|
{
|
||||||
setEntriesPerPage(parseInt(newValues, 10));
|
boxStyle = isFooter ? {overflowY: "visible", borderTop: "0.0625rem solid #f0f2f5;"} : {height: fixedHeight ? `${fixedHeight}px` : "360px", overflowY: "auto"};
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
const className = isFooter ? "hideScrollbars" : "";
|
||||||
setEntriesPerPage(parseInt(newValues[0], 10));
|
return <Box sx={boxStyle} className={className}>
|
||||||
}
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
sx={{width: "5rem"}}
|
|
||||||
renderInput={(params) => <MDInput {...params} />}
|
|
||||||
/>
|
|
||||||
<MDTypography variant="caption" color="secondary">
|
|
||||||
entries per page
|
|
||||||
</MDTypography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{canSearch && (
|
|
||||||
<Box width="12rem" ml="auto">
|
|
||||||
<MDInput
|
|
||||||
placeholder="Search..."
|
|
||||||
value={search}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
onChange={({currentTarget}: any) =>
|
|
||||||
{
|
|
||||||
setSearch(search);
|
|
||||||
onSearchChange(currentTarget.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
) : null}
|
|
||||||
<Table {...getTableProps()}>
|
<Table {...getTableProps()}>
|
||||||
<Box component="thead">
|
{
|
||||||
|
includeHead && (
|
||||||
|
<Box component="thead" sx={{position: "sticky", top: 0, background: "white"}}>
|
||||||
{headerGroups.map((headerGroup: any, i: number) => (
|
{headerGroups.map((headerGroup: any, i: number) => (
|
||||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()}>
|
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", gridTemplateColumns: gridTemplateColumns}}>
|
||||||
{headerGroup.headers.map((column: any) => (
|
{headerGroup.headers.map((column: any) => (
|
||||||
column.type !== "hidden" && (
|
column.type !== "hidden" && (
|
||||||
<DataTableHeadCell
|
<DataTableHeadCell
|
||||||
key={i++}
|
key={i++}
|
||||||
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
|
||||||
width={column.width ? column.width : "auto"}
|
|
||||||
align={column.align ? column.align : "left"}
|
align={column.align ? column.align : "left"}
|
||||||
sorted={setSortedValue(column)}
|
sorted={setSortedValue(column)}
|
||||||
>
|
>
|
||||||
@ -260,12 +306,14 @@ function DataTable({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
<TableBody {...getTableBodyProps()}>
|
<TableBody {...getTableBodyProps()}>
|
||||||
{page.map((row: any, key: any) =>
|
{rows.map((row: any, key: any) =>
|
||||||
{
|
{
|
||||||
prepareRow(row);
|
prepareRow(row);
|
||||||
return (
|
return (
|
||||||
<TableRow sx={{verticalAlign: "top"}} key={key} {...row.getRowProps()}>
|
<TableRow sx={{verticalAlign: "top", display: "grid", gridTemplateColumns: gridTemplateColumns}} key={key} {...row.getRowProps()}>
|
||||||
{row.cells.map((cell: any) => (
|
{row.cells.map((cell: any) => (
|
||||||
cell.column.type !== "hidden" && (
|
cell.column.type !== "hidden" && (
|
||||||
<DataTableBodyCell
|
<DataTableBodyCell
|
||||||
@ -308,6 +356,9 @@ function DataTable({
|
|||||||
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
<ImageCell imageUrl={row.values["imageUrl"]} label={row.values["imageLabel"]} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
(cell.column.id === "__expander") && cell.render("cell")
|
||||||
|
}
|
||||||
</DataTableBodyCell>
|
</DataTableBodyCell>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
@ -316,6 +367,65 @@ function DataTable({
|
|||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer sx={{boxShadow: "none"}}>
|
||||||
|
{entriesPerPage && ((hidePaginationDropdown !== undefined && !hidePaginationDropdown) || canSearch) ? (
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center" p={3}>
|
||||||
|
{entriesPerPage && (hidePaginationDropdown === undefined || !hidePaginationDropdown) && (
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<Autocomplete
|
||||||
|
disableClearable
|
||||||
|
value={pageSize.toString()}
|
||||||
|
options={entries}
|
||||||
|
onChange={(event, newValues: any) =>
|
||||||
|
{
|
||||||
|
if (typeof newValues === "string")
|
||||||
|
{
|
||||||
|
setEntriesPerPage(parseInt(newValues, 10));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setEntriesPerPage(parseInt(newValues[0], 10));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
sx={{width: "5rem"}}
|
||||||
|
renderInput={(params) => <MDInput {...params} />}
|
||||||
|
/>
|
||||||
|
<MDTypography variant="caption" color="secondary">
|
||||||
|
entries per page
|
||||||
|
</MDTypography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{canSearch && (
|
||||||
|
<Box width="12rem" ml="auto">
|
||||||
|
<MDInput
|
||||||
|
placeholder="Search..."
|
||||||
|
value={search}
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
onChange={({currentTarget}: any) =>
|
||||||
|
{
|
||||||
|
setSearch(search);
|
||||||
|
onSearchChange(currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{
|
||||||
|
fixedStickyLastRow ? (
|
||||||
|
<>
|
||||||
|
{getTable(true, page.slice(0, page.length -1), false)}
|
||||||
|
{getTable(false, page.slice(page.length-1), true)}
|
||||||
|
</>
|
||||||
|
) : getTable(true, page, false)
|
||||||
|
}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@ -368,15 +478,4 @@ function DataTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declaring default props for DataTable
|
|
||||||
DataTable.defaultProps = {
|
|
||||||
entriesPerPage: 10,
|
|
||||||
entriesPerPageOptions: ["5", "10", "15", "20", "25"],
|
|
||||||
canSearch: false,
|
|
||||||
showTotalEntries: true,
|
|
||||||
pagination: {variant: "gradient", color: "info"},
|
|
||||||
isSorted: true,
|
|
||||||
noEndBorder: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DataTable;
|
export default DataTable;
|
||||||
|
@ -54,11 +54,13 @@ interface Props
|
|||||||
noRowsFoundHTML?: string;
|
noRowsFoundHTML?: string;
|
||||||
rowsPerPage?: number;
|
rowsPerPage?: number;
|
||||||
hidePaginationDropdown?: boolean;
|
hidePaginationDropdown?: boolean;
|
||||||
|
fixedStickyLastRow?: boolean;
|
||||||
|
fixedHeight?: number;
|
||||||
data: TableDataInput;
|
data: TableDataInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qController = Client.getInstance();
|
const qController = Client.getInstance();
|
||||||
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown}: Props): JSX.Element
|
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||||
|
|
||||||
@ -79,6 +81,8 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown}:
|
|||||||
table={data}
|
table={data}
|
||||||
entriesPerPage={rowsPerPage}
|
entriesPerPage={rowsPerPage}
|
||||||
hidePaginationDropdown={hidePaginationDropdown}
|
hidePaginationDropdown={hidePaginationDropdown}
|
||||||
|
fixedStickyLastRow={fixedStickyLastRow}
|
||||||
|
fixedHeight={fixedHeight}
|
||||||
showTotalEntries={false}
|
showTotalEntries={false}
|
||||||
isSorted={false}
|
isSorted={false}
|
||||||
noEndBorder
|
noEndBorder
|
||||||
|
@ -122,6 +122,8 @@ function TableWidget(props: Props): JSX.Element
|
|||||||
noRowsFoundHTML={props.widgetData?.noRowsFoundHTML}
|
noRowsFoundHTML={props.widgetData?.noRowsFoundHTML}
|
||||||
rowsPerPage={props.widgetData?.rowsPerPage}
|
rowsPerPage={props.widgetData?.rowsPerPage}
|
||||||
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
hidePaginationDropdown={props.widgetData?.hidePaginationDropdown}
|
||||||
|
fixedStickyLastRow={props.widgetData?.fixedStickyLastRow}
|
||||||
|
fixedHeight={props.widgetData?.fixedHeight}
|
||||||
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows}}
|
data={{columns: props.widgetData?.columns, rows: props.widgetData?.rows}}
|
||||||
/>
|
/>
|
||||||
</Widget>
|
</Widget>
|
||||||
|
@ -1284,6 +1284,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
|||||||
{
|
{
|
||||||
mainCardStyles.background = "none";
|
mainCardStyles.background = "none";
|
||||||
mainCardStyles.boxShadow = "none";
|
mainCardStyles.boxShadow = "none";
|
||||||
|
mainCardStyles.border = "none";
|
||||||
mainCardStyles.minHeight = "";
|
mainCardStyles.minHeight = "";
|
||||||
mainCardStyles.alignItems = "stretch";
|
mainCardStyles.alignItems = "stretch";
|
||||||
mainCardStyles.flexGrow = 1;
|
mainCardStyles.flexGrow = 1;
|
||||||
|
@ -2043,6 +2043,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
|||||||
getRowId={(row) => row.__rowIndex}
|
getRowId={(row) => row.__rowIndex}
|
||||||
selectionModel={rowSelectionModel}
|
selectionModel={rowSelectionModel}
|
||||||
hideFooterSelectedRowCount={true}
|
hideFooterSelectedRowCount={true}
|
||||||
|
sx={{border: 0}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -564,3 +564,14 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|||||||
display: inline;
|
display: inline;
|
||||||
right: .5rem
|
right: .5rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideScrollbars::-webkit-scrollbar {
|
||||||
|
background: transparent; /* Chrome/Safari/Webkit */
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideScrollbars {
|
||||||
|
padding-right: 8px; /* pad-right for about half the width of a scrollbar.. */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE 10+ */
|
||||||
|
}
|
Reference in New Issue
Block a user