import { Box, 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, 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 { title: string; cellStyle?: SxProps; hidden?: boolean; render: (row: T) => JSX.Element; sortKey?: string; disableSort?: boolean; } interface Props { rows: T[], columns?: Column[] | (() => Column[]), title?: string | JSX.Element | (() => string | JSX.Element); subtitle?: string | JSX.Element | (() => string | JSX.Element); total: number, pageSize: number, page: number, setPage: (value: React.SetStateAction) => void, handleRowsPerPageChange: (event: any) => void, renderGutter?: (row: T) => JSX.Element, gutterPadding?: number, setSearchText?: React.Dispatch>, isLoading?: boolean; multiGutter?: boolean; rowsPerPageOptions?: number[]; noDataMessage?: string; renderMobile?: (row: T) => 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>; order?: string; setOrder?: React.Dispatch>; } export default function ResponsiveTable(props: Props) { 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 } = 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 order = props.order ? props.order : "asc" const isMobile = useMobile() const [inputSearchText, setInputSearchText] = useState(""); const [openGutters, setOpenGutters] = useState([]) const [handler, setHandler] = useState(undefined); const [columnAnchor, setColumnAnchor] = useState(null) const filterKey = window.location.pathname.split('/').pop() + "-table-filter-list"; const [filterList, setFilterList] = useState(() => { 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) => { 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 ( setInputSearchText(e.currentTarget.value)} InputProps={{ startAdornment: ( ), endAdornment: ( ), }} /> ) } const renderTitle = () => { if (typeof(title) === "string" ) return ( {title} ) if (title) return ( {title} ) return null } const renderSubtitle = () => { if (typeof(subtitle) === "string" ) return ( {subtitle} ) if (subtitle) return ( {subtitle} ) return null } function capitalize(val: any) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } const renderMobileRow = (row: T) => { if (renderMobile) return ( renderMobile(row) ) if (columns) { return ( {columns.map((column, index) => { return ( { !hideKeys && {column.title+": "} } {column.render(row)} ) })} ) } return ( Object.keys(row).map((key, index) => { let value = row[key as keyof typeof row] return ( {capitalize(key)+": "} {value+""} ) }) ) } if (isMobile || mobileView) return ( {renderTitle()} {renderSubtitle()} {setSearchText && searchBar()} {rows.map((row, index) => { return ( onRowClick&&onRowClick(row)}> {renderMobileRow(row)} {renderGutter && <> {renderGutter(row)} {openGutters.includes(index) && } handleCollapse(index, event)} // style={{pre}} > } ) })} {(rows && rows.length < 1) && (!isLoading ? { noDataMessage ? noDataMessage : "No data."} : ) } ) 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) => { const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() // console.log(column.sortKey) if (setOrderBy) { if (orderBy === sortKey && setOrder && order) { console.log(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) => { if (!orderBy) return const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() if (sortKey === orderBy) { if (order === "asc") { return } else { return } } return null; } return ( {renderTitle()} {renderSubtitle()} {actions && actions} {setSearchText && {searchBar()} } { columns && !hideKeys && } {customElement && {customElement} } { rowSelect && } { renderGutter && } { columns ? columns.map((column, index) => { if (filterList.includes(column.title) || column.hidden) return null return ( !column.disableSort && columnClick(column)} sx={{ cursor: column.disableSort ? "default" : "pointer" }} > {column.title} {showOrderIcon(column)} ) }) : (rows && rows.length > 0) && Object.keys(rows[0]).map((value, index) => { return( {value.toString()} ) }) } { isLoading ? { isLoading && } : rows.map((row, index) => { // if (filterList.includes()) return null return ( onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}> {rowSelect && {event.stopPropagation()}} onChange={(_, checked) => { rowSelect(row, index, checked) }}/> } {renderGutter && handleCollapse(index, event)}> } {columns ? columns.map((column, j) => { if (filterList.includes(column.title) || column.hidden) return null if (column.render) return ( {column.render(row)} ) return ( {Object.values(row)[j].toString()} ) }) : Object.values(row).map((value, j) => { return ( {value ? value.toString() : ""} ) }) } {renderGutter && {renderGutter(row)} } ) }) } {(rows && rows.length)< 1 && !isLoading && {noDataMessage ? noDataMessage : "No data found"} }
{!hidePagination && handlePageChange(page)} onRowsPerPageChange={handleRowsPerPageChange} /> } {columns?.map((column, index) => { return ( filter(column.title)} key={"column-list-"+index} aria-label="Open User Settings" dense sx={{ marginLeft: -0.75 }} > ) })}
) }