694 lines
No EOL
23 KiB
TypeScript
694 lines
No EOL
23 KiB
TypeScript
import { Box, Button, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material"
|
|
import { useEffect, useRef, useState } from "react";
|
|
import React from "react";
|
|
import { useMobile } from "hooks";
|
|
import classNames from "classnames";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => {
|
|
//const isMobile = useMobile()
|
|
return ({
|
|
gutter: {
|
|
backgroundColor: darken(theme.palette.background.paper, 0.05),
|
|
border: "none"
|
|
},
|
|
tableContainer: {
|
|
// margin: theme.spacing(1),
|
|
// [theme.breakpoints.up("sm")]: {
|
|
// margin: theme.spacing(2)
|
|
// },
|
|
// width: "auto"
|
|
},
|
|
title: {
|
|
padding: theme.spacing(1),
|
|
},
|
|
subtitle: {
|
|
padding: theme.spacing(1),
|
|
marginTop: theme.spacing(-2),
|
|
},
|
|
titleComp: {
|
|
padding: theme.spacing(1),
|
|
marginLeft: theme.spacing(-1)
|
|
},
|
|
subtitleComp: {
|
|
padding: theme.spacing(1),
|
|
marginTop: theme.spacing(-2),
|
|
marginLeft: theme.spacing(-1),
|
|
},
|
|
titleContainer: {
|
|
border: "none",
|
|
},
|
|
searchContainer: {
|
|
border: "none",
|
|
textAlign: "right",
|
|
},
|
|
mobileTitleBox: {
|
|
margin: theme.spacing(1),
|
|
marginBottom: theme.spacing(1.5),
|
|
},
|
|
card: {
|
|
margin: theme.spacing(1),
|
|
},
|
|
rowHover: {
|
|
cursor: "pointer",
|
|
"&:hover": {
|
|
backgroundColor: "rgba(150, 150, 150, 0.15)",
|
|
}
|
|
},
|
|
})},
|
|
);
|
|
|
|
export interface Column<T> {
|
|
title: string;
|
|
cellStyle?: SxProps<Theme>;
|
|
hidden?: boolean;
|
|
render: (row: T) => JSX.Element;
|
|
sortKey?: string;
|
|
disableSort?: boolean;
|
|
}
|
|
|
|
interface Props<T> {
|
|
rows: T[],
|
|
columns?: Column<T>[] | (() => Column<T>[]),
|
|
title?: string | JSX.Element | (() => string | JSX.Element);
|
|
subtitle?: string | JSX.Element | (() => string | JSX.Element);
|
|
total: number,
|
|
pageSize: number,
|
|
page: number,
|
|
setPage: (value: React.SetStateAction<number>) => void,
|
|
handleRowsPerPageChange: (event: any) => void,
|
|
renderGutter?: (row: T) => JSX.Element,
|
|
gutterPadding?: number,
|
|
setSearchText?: React.Dispatch<React.SetStateAction<string>>,
|
|
isLoading?: boolean;
|
|
multiGutter?: boolean;
|
|
rowsPerPageOptions?: number[];
|
|
noDataMessage?: string;
|
|
renderMobile?: (row: T, index: number) => JSX.Element;
|
|
hideKeys?: boolean;
|
|
onRowClick?: (row: T) => void;
|
|
actions?: string | JSX.Element;
|
|
rowSelect?: (row: T, index: number, checked: boolean) => void; //row select function that passes the the row back up, when this function is passed in render a column of checkboxes
|
|
selectedRows?: number[]; //which rows are selected will need to be controlled by the parent in order to keep track between page changes
|
|
customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows
|
|
mobileView?: boolean //can be used to force the table to use its mobile view for narrow containing elements ie, drawer
|
|
hidePagination?: boolean //when passed in will hide the pagination at the bottom of the table
|
|
orderBy?: string;
|
|
setOrderBy?: React.Dispatch<React.SetStateAction<string>>;
|
|
order?: string;
|
|
setOrder?: React.Dispatch<React.SetStateAction<"asc" | "desc">>;
|
|
endTitleElement?: string | JSX.Element | (() => string | JSX.Element);
|
|
loadMore?: () => void
|
|
}
|
|
|
|
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|
const {
|
|
rows,
|
|
total,
|
|
page,
|
|
pageSize,
|
|
setPage,
|
|
handleRowsPerPageChange,
|
|
// title,
|
|
// subtitle,
|
|
renderGutter,
|
|
gutterPadding,
|
|
setSearchText,
|
|
isLoading,
|
|
multiGutter,
|
|
// columns,
|
|
rowsPerPageOptions,
|
|
noDataMessage,
|
|
renderMobile,
|
|
hideKeys,
|
|
onRowClick,
|
|
actions,
|
|
rowSelect,
|
|
selectedRows,
|
|
customElement,
|
|
mobileView,
|
|
hidePagination,
|
|
orderBy,
|
|
setOrderBy,
|
|
order,
|
|
setOrder,
|
|
loadMore
|
|
} = props
|
|
const classes = useStyles();
|
|
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
|
|
const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle
|
|
const title = typeof(props.title) === "function" ? props.title() : props.title
|
|
const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement
|
|
|
|
const isMobile = useMobile()
|
|
|
|
const [inputSearchText, setInputSearchText] = useState("");
|
|
const [openGutters, setOpenGutters] = useState<number[]>([])
|
|
const [handler, setHandler] = useState<NodeJS.Timeout | undefined>(undefined);
|
|
const [columnAnchor, setColumnAnchor] = useState<any>(null)
|
|
|
|
const [rowWidths, setRowWidths] = useState<number[]>([])
|
|
const dragIndexRef = useRef<number | undefined>(undefined);
|
|
const dragStartRef = useRef<number | undefined>(undefined);
|
|
const rowWidthsRef = useRef<number[]>([]);
|
|
|
|
const filterKey = window.location.pathname.split('/').pop() + "-table-filter-list";
|
|
|
|
const [filterList, setFilterList] = useState<string[]>(() => {
|
|
const saved = localStorage.getItem(filterKey);
|
|
return saved ? JSON.parse(saved) : [];
|
|
});
|
|
|
|
useEffect(() => {
|
|
localStorage.setItem(filterKey, JSON.stringify(filterList));
|
|
}, [filterList])
|
|
|
|
const openColumnMenu = (event: any) => {
|
|
setColumnAnchor(event.currentTarget);
|
|
// setUserMenuIsOpen(true);
|
|
};
|
|
|
|
const closeColumnMenu = () => {
|
|
setColumnAnchor(null);
|
|
// setUserMenuIsOpen(true);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!setSearchText) return;
|
|
if (inputSearchText === undefined) return;
|
|
setHandler(setTimeout(() => {
|
|
setSearchText(inputSearchText);
|
|
}, 750)); // Delay of 750ms
|
|
|
|
return () => clearTimeout(handler); // Cleanup on change
|
|
}, [inputSearchText]);
|
|
|
|
const handleCollapse = (index: number, event: React.MouseEvent<HTMLDivElement | HTMLButtonElement, MouseEvent>) => {
|
|
event.stopPropagation();
|
|
if (multiGutter) {
|
|
if (openGutters.includes(index)) {
|
|
setOpenGutters(openGutters => openGutters.filter(num => num !== index));
|
|
} else {
|
|
setOpenGutters(openGutters => [...openGutters, index]);
|
|
}
|
|
} else {
|
|
if (openGutters.includes(index)) {
|
|
setOpenGutters([])
|
|
} else {
|
|
setOpenGutters([index])
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleKeyDown = (e: any) => {
|
|
if (e.key === 'Enter') {
|
|
if (setSearchText) setSearchText(inputSearchText);
|
|
clearTimeout(handler)
|
|
}
|
|
};
|
|
|
|
const handlePageChange = (page: number) => {
|
|
setOpenGutters([])
|
|
setPage(page)
|
|
}
|
|
|
|
const clearSearch = () => {
|
|
setInputSearchText("")
|
|
}
|
|
|
|
const searchBar = () => {
|
|
return (
|
|
<TextField
|
|
fullWidth
|
|
variant="standard"
|
|
placeholder="Search..."
|
|
//className={classes.title}
|
|
value={inputSearchText}
|
|
onKeyDown={handleKeyDown}
|
|
onChange={e => setInputSearchText(e.currentTarget.value)}
|
|
InputProps={{
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Search />
|
|
</InputAdornment>
|
|
),
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton onClick={clearSearch}>
|
|
<Close />
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const renderTitle = () => {
|
|
if (typeof(title) === "string" ) return (
|
|
<Typography variant="h5" className={classes.title}>
|
|
{title}
|
|
</Typography>
|
|
)
|
|
if (title) return (
|
|
<Box className={classes.titleComp}>
|
|
{title}
|
|
</Box>
|
|
)
|
|
return null
|
|
}
|
|
|
|
const renderSubtitle = () => {
|
|
if (typeof(subtitle) === "string" ) return (
|
|
<Typography variant="body2" color="textSecondary" className={classes.subtitle}>
|
|
{subtitle}
|
|
</Typography>
|
|
)
|
|
if (subtitle) return (
|
|
<Box className={classes.subtitleComp}>
|
|
{subtitle}
|
|
</Box>
|
|
)
|
|
return null
|
|
}
|
|
|
|
function capitalize(val: any) {
|
|
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
|
}
|
|
|
|
const renderMobileRow = (row: T, index: number) => {
|
|
if (renderMobile) return (
|
|
renderMobile(row, index)
|
|
)
|
|
if (columns) {
|
|
return (
|
|
<Grid2 container direction={"row"} spacing={1} >
|
|
{columns.map((column, index) => {
|
|
return (
|
|
<React.Fragment key={"mobile-row-column-"+index}>
|
|
{ !hideKeys && <Grid2 size={{ xs: 3 }} alignContent={"center"}>
|
|
<Typography fontWeight={"bold"} >
|
|
{column.title+": "}
|
|
</Typography>
|
|
</Grid2>}
|
|
<Grid2 size={{ xs: hideKeys ? 12 : 9 }} alignContent={"center"} >
|
|
{column.render(row)}
|
|
</Grid2>
|
|
</React.Fragment>
|
|
)
|
|
})}
|
|
</Grid2>
|
|
)
|
|
}
|
|
return (
|
|
Object.keys(row).map((key, index) => {
|
|
const value = row[key as keyof typeof row]
|
|
return (
|
|
<Grid2 container direction={"row"} key={"mobile-list-"+index} spacing={1} padding={1}>
|
|
<Grid2 size={{ xs: 4 }} alignContent={"center"} >
|
|
<Typography fontWeight={"bold"} >
|
|
{capitalize(key)+": "}
|
|
</Typography>
|
|
</Grid2>
|
|
<Grid2 size={{ xs: 8 }}>
|
|
<Typography>
|
|
{value+""}
|
|
</Typography>
|
|
</Grid2>
|
|
</Grid2>
|
|
)
|
|
})
|
|
)
|
|
}
|
|
|
|
|
|
if (isMobile || mobileView) return (
|
|
<Box>
|
|
<Box className={classes.mobileTitleBox}>
|
|
{renderTitle()}
|
|
{renderSubtitle()}
|
|
{setSearchText && searchBar()}
|
|
</Box>
|
|
{rows.map((row, index) => {
|
|
return (
|
|
<Card className={classNames(classes.card)} key={"mobile-card-"+index} onClick={() => onRowClick&&onRowClick(row)}>
|
|
{renderMobileRow(row, index)}
|
|
{renderGutter &&
|
|
<>
|
|
<Divider/>
|
|
<Collapse className={classes.gutter} in={openGutters.includes(index)} >
|
|
{renderGutter(row)}
|
|
</Collapse>
|
|
{openGutters.includes(index) && <Divider/>}
|
|
<ListItemButton
|
|
sx={{
|
|
width: "100%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
padding: gutterPadding
|
|
}}
|
|
onClick={(event) => handleCollapse(index, event)}
|
|
>
|
|
<IconButton
|
|
sx={{
|
|
transform: openGutters.includes(index) ?
|
|
'rotate(-90deg)' : 'rotate(90deg)',
|
|
transition: 'transform 0.3s ease',
|
|
alignContent: "center"
|
|
}}
|
|
>
|
|
<ChevronRight/>
|
|
</IconButton>
|
|
</ListItemButton>
|
|
</>
|
|
}
|
|
</Card>
|
|
)
|
|
})}
|
|
{(rows && rows.length < 1) &&
|
|
(!isLoading ?
|
|
<Typography sx={{ textAlign: "center" }} color="textSecondary">
|
|
{ noDataMessage ? noDataMessage : "No data."}
|
|
</Typography>
|
|
:
|
|
<Box sx={{ width: "100%", textAlign: "center" }} >
|
|
<CircularProgress/>
|
|
</Box>
|
|
)
|
|
}
|
|
{(loadMore && rows.length < total) &&
|
|
<Box>
|
|
<Button
|
|
onClick={loadMore}
|
|
color="primary"
|
|
sx={{width: "100%"}}
|
|
variant="contained">
|
|
Load More
|
|
</Button>
|
|
</Box>
|
|
}
|
|
</Box>
|
|
)
|
|
|
|
const filter = (colName: string) => {
|
|
let newFilterList = [...filterList]
|
|
if (filterList.includes(colName)) {
|
|
newFilterList = newFilterList.filter(name => name !== colName)
|
|
} else {
|
|
newFilterList.push(colName)
|
|
}
|
|
setFilterList(newFilterList)
|
|
}
|
|
|
|
const columnClick = (column: Column<T>) => {
|
|
const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
|
|
// console.log(column.sortKey)
|
|
if (setOrderBy) {
|
|
if (orderBy === sortKey && setOrder && order) {
|
|
if (order === "desc") {
|
|
setOrder("asc")
|
|
} else {
|
|
setOrderBy("")
|
|
}
|
|
// setOrder(order === "asc" ? "desc" : "asc")
|
|
} else {
|
|
if (setOrder) setOrder("desc")
|
|
setOrderBy(sortKey)
|
|
}
|
|
}
|
|
// if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc")
|
|
}
|
|
|
|
const showOrderIcon = (column: Column<T>) => {
|
|
if (!orderBy) return
|
|
const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
|
|
if (sortKey === orderBy) {
|
|
if (order === "asc") {
|
|
return <ArrowDownward fontSize="small" sx={{ marginLeft: 1 }} />
|
|
} else {
|
|
return <ArrowUpward fontSize="small" sx={{ marginLeft: 1 }} />
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const resizeClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
|
|
|
|
let newWidths: number[] = []
|
|
event.currentTarget.parentElement?.parentElement?.parentElement?.childNodes.forEach(node => {
|
|
if (node instanceof HTMLElement) {
|
|
newWidths.push(node.clientWidth)
|
|
}
|
|
})
|
|
|
|
rowWidthsRef.current = newWidths
|
|
dragIndexRef.current = index;
|
|
dragStartRef.current = event.clientX;
|
|
|
|
window.addEventListener('mousemove', resizeDrag);
|
|
window.addEventListener('mouseup', resizeUnclick);
|
|
}
|
|
|
|
const resizeUnclick = () => {
|
|
dragIndexRef.current = undefined;
|
|
dragStartRef.current = undefined;
|
|
window.removeEventListener('mousemove', resizeDrag);
|
|
window.removeEventListener('mouseup', resizeUnclick);
|
|
}
|
|
|
|
const resizeDrag = (e: any) => {
|
|
// console.log(dragIndex)
|
|
const index = dragIndexRef.current;
|
|
const dragStart = dragStartRef.current
|
|
const rowWidths = rowWidthsRef.current
|
|
if (!index) return
|
|
if (!rowWidths[index]) return
|
|
if (!rowWidths[index+1]) return
|
|
if (!dragStart) return
|
|
|
|
let newWidths: number[] = [...rowWidths]
|
|
newWidths[index] = newWidths[index] - (dragStart - e.clientX)
|
|
newWidths[index+1] = newWidths[index+1] + (dragStart - e.clientX)
|
|
setRowWidths(newWidths)
|
|
|
|
}
|
|
|
|
return (
|
|
<TableContainer className={classes.tableContainer} component={Paper}>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}>
|
|
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
|
<Grid2 container justifyContent="space-between">
|
|
<Grid2>
|
|
<Box sx={{ marginTop: -1 }}>
|
|
{renderTitle()}
|
|
{renderSubtitle()}
|
|
</Box>
|
|
</Grid2>
|
|
<Grid2>
|
|
<Grid2 container alignItems={"center"} spacing={1}>
|
|
<Grid2>
|
|
{actions && actions}
|
|
</Grid2>
|
|
<Grid2>
|
|
{endTitleElement}
|
|
</Grid2>
|
|
<Grid2>
|
|
{setSearchText &&<Box className={classes.searchContainer}>
|
|
{searchBar()}
|
|
</Box>}
|
|
</Grid2>
|
|
<Grid2>
|
|
{ columns && !hideKeys &&
|
|
<IconButton onClick={openColumnMenu} >
|
|
<ViewColumn />
|
|
</IconButton>
|
|
}
|
|
</Grid2>
|
|
</Grid2>
|
|
</Grid2>
|
|
</Grid2>
|
|
</TableCell>
|
|
</TableRow>
|
|
|
|
{customElement &&
|
|
<TableRow>
|
|
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
|
{customElement}
|
|
</TableCell>
|
|
</TableRow>
|
|
}
|
|
|
|
<TableRow sx={ hideKeys ? { display: "none" } : undefined } >
|
|
{ rowSelect && <TableCell></TableCell> }
|
|
{ renderGutter && <TableCell></TableCell>}
|
|
{ columns ?
|
|
columns.map((column, index) => {
|
|
if (filterList.includes(column.title) || column.hidden) return null
|
|
return (
|
|
<TableCell
|
|
key={"header-cell-"+index}
|
|
// style={{ border: "1px solid red" }}
|
|
// onClick={() => !column.disableSort && columnClick(column)}
|
|
// sx={{ cursor: column.disableSort ? "default" : "pointer" }}
|
|
>
|
|
<Grid2 container spacing={1} justifyContent={"space-between"}>
|
|
<Grid2
|
|
alignItems="center"
|
|
display="flex"
|
|
onClick={() => !column.disableSort && columnClick(column)}
|
|
sx={{ cursor: column.disableSort ? "default" : "pointer" }}
|
|
>
|
|
{column.title} {showOrderIcon(column)}
|
|
</Grid2>
|
|
{index < columns.length-1 &&
|
|
<Grid2
|
|
sx={{ cursor: "col-resize" }}
|
|
onMouseDown={event => resizeClick(event, index)}
|
|
>
|
|
|
|
|
</Grid2>
|
|
}
|
|
</Grid2>
|
|
</TableCell>
|
|
)
|
|
})
|
|
: (rows && rows.length > 0) &&
|
|
Object.keys(rows[0]).map((value, index) => {
|
|
return(
|
|
<TableCell key={"header-cell-"+index}>
|
|
{value.toString()}
|
|
</TableCell>
|
|
)
|
|
})
|
|
}
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{ isLoading ?
|
|
<TableRow>
|
|
{ isLoading &&
|
|
<TableCell colSpan={6} sx={{ textAlign: "center"}}>
|
|
<CircularProgress />
|
|
</TableCell>
|
|
}
|
|
</TableRow>
|
|
:
|
|
rows.map((row, index) => {
|
|
// if (filterList.includes()) return null
|
|
return (
|
|
<React.Fragment key={"row-"+index}>
|
|
<TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}>
|
|
{rowSelect &&
|
|
<TableCell>
|
|
<Checkbox
|
|
checked={selectedRows ? selectedRows.includes(index) : false}
|
|
onClick={(event) => {event.stopPropagation()}}
|
|
onChange={(_, checked) => {
|
|
rowSelect(row, index, checked)
|
|
}}/>
|
|
</TableCell>
|
|
}
|
|
{renderGutter &&
|
|
<TableCell width={1}>
|
|
<IconButton sx={{
|
|
transform: openGutters.includes(index) ?
|
|
'rotate(90deg)' : 'rotate(0deg)',
|
|
transition: 'transform 0.3s ease',
|
|
marginRight: (isMobile || mobileView) ? -1 : 0
|
|
}}
|
|
onClick={(event) => handleCollapse(index, event)}>
|
|
<ChevronRight/>
|
|
</IconButton>
|
|
</TableCell>
|
|
}
|
|
{columns ?
|
|
columns.map((column, j) => {
|
|
// console.log(j)
|
|
if (filterList.includes(column.title) || column.hidden) return null
|
|
if (column.render) return (
|
|
<TableCell key={"row-"+index+"cell-"+j} sx={{padding: 0, width: rowWidths[j] ? rowWidths[j] : undefined}}>
|
|
{column.render(row)}
|
|
</TableCell>
|
|
)
|
|
return (
|
|
<TableCell key={"row-"+index+"cell-"+j}>
|
|
{Object.values(row)[j] && Object.values(row)[j].toString()}
|
|
</TableCell>
|
|
)
|
|
})
|
|
:
|
|
Object.values(row).map((value, j) => {
|
|
return (
|
|
<TableCell key={"row-"+index+"cell-"+j}>
|
|
{value ? value.toString() : ""}
|
|
</TableCell>
|
|
)
|
|
})
|
|
}
|
|
</TableRow>
|
|
{renderGutter &&
|
|
<TableRow className={classes.gutter}>
|
|
<TableCell colSpan={6} sx={{padding:0, margin: 0, border: "none"}}>
|
|
<Collapse in={openGutters.includes(index)} timeout="auto" unmountOnExit>
|
|
{renderGutter(row)}
|
|
</Collapse>
|
|
</TableCell>
|
|
</TableRow>
|
|
}
|
|
</React.Fragment>
|
|
)
|
|
})
|
|
}
|
|
{(rows && rows.length)< 1 && !isLoading &&
|
|
<TableRow>
|
|
<TableCell colSpan={6} sx={{textAlign: "center" }}>
|
|
<Typography variant="body2" color="textSecondary">
|
|
{noDataMessage ? noDataMessage : "No data found"}
|
|
</Typography>
|
|
</TableCell>
|
|
</TableRow>
|
|
}
|
|
</TableBody>
|
|
</Table>
|
|
{!hidePagination &&
|
|
<TablePagination
|
|
rowsPerPageOptions={rowsPerPageOptions ? rowsPerPageOptions : [5, 10, 20]}
|
|
component="div"
|
|
count={total ? total : 0}
|
|
rowsPerPage={pageSize}
|
|
page={page}
|
|
onPageChange={(_event, page) => handlePageChange(page)}
|
|
onRowsPerPageChange={handleRowsPerPageChange}
|
|
/>
|
|
}
|
|
<Menu
|
|
id="userMenu"
|
|
anchorEl={columnAnchor}
|
|
open={columnAnchor !== null}
|
|
onClose={closeColumnMenu}
|
|
disableAutoFocusItem>
|
|
{columns?.map((column, index) => {
|
|
return (
|
|
<MenuItem
|
|
onClick={() => filter(column.title)}
|
|
key={"column-list-"+index}
|
|
aria-label="Open User Settings"
|
|
dense
|
|
sx={{ marginLeft: -0.75 }}
|
|
>
|
|
<ListItemIcon>
|
|
<Checkbox
|
|
checked={!filterList.includes(column.title)}
|
|
/>
|
|
</ListItemIcon>
|
|
<ListItemText primary={column.title} />
|
|
</MenuItem>
|
|
)
|
|
})}
|
|
</Menu>
|
|
</TableContainer>
|
|
)
|
|
} |