implemented column filtering in ResponsiveTable

This commit is contained in:
Carter 2025-02-25 11:22:27 -06:00
parent 4829e8d841
commit 1fb166650f
2 changed files with 60 additions and 35 deletions

View file

@ -1,6 +1,6 @@
import { Box, Card, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material"; 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 { makeStyles } from "@mui/styles";
import { ChevronRight, Close, Search } from "@mui/icons-material" import { ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material"
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import React from "react"; import React from "react";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
@ -61,7 +61,7 @@ const useStyles = makeStyles((theme: Theme) => {
); );
export interface Column<T> { export interface Column<T> {
title: string | JSX.Element; title: string;
cellStyle?: SxProps<Theme>; cellStyle?: SxProps<Theme>;
render: (row: T) => JSX.Element; render: (row: T) => JSX.Element;
} }
@ -117,6 +117,18 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const [inputSearchText, setInputSearchText] = useState(""); const [inputSearchText, setInputSearchText] = useState("");
const [openGutters, setOpenGutters] = useState<number[]>([]) const [openGutters, setOpenGutters] = useState<number[]>([])
const [handler, setHandler] = useState<NodeJS.Timeout | undefined>(undefined); const [handler, setHandler] = useState<NodeJS.Timeout | undefined>(undefined);
const [columnAnchor, setColumnAnchor] = useState<any>(null)
const [filterList, setFilterList] = useState<string[]>([])
const openColumnMenu = (event: any) => {
setColumnAnchor(event.currentTarget);
// setUserMenuIsOpen(true);
};
const closeColumnMenu = () => {
setColumnAnchor(null);
// setUserMenuIsOpen(true);
};
useEffect(() => { useEffect(() => {
if (!setSearchText) return; if (!setSearchText) return;
@ -266,12 +278,14 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
) )
} }
if (isMobile) return ( if (isMobile) return (
<Box> <Box>
<Box className={classes.mobileTitleBox}> <Box className={classes.mobileTitleBox}>
{renderTitle()} {renderTitle()}
{renderSubtitle()} {renderSubtitle()}
{setSearchText && searchBar()} {setSearchText && searchBar()}
</Box> </Box>
{rows.map((row, index) => { {rows.map((row, index) => {
return ( return (
@ -322,6 +336,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</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)
}
return ( return (
<TableContainer className={classes.tableContainer} component={Paper}> <TableContainer className={classes.tableContainer} component={Paper}>
<Table> <Table>
@ -345,6 +370,13 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
{searchBar()} {searchBar()}
</Box>} </Box>}
</Grid2> </Grid2>
<Grid2>
{ columns &&
<IconButton onClick={openColumnMenu} >
<ViewColumn />
</IconButton>
}
</Grid2>
</Grid2> </Grid2>
</Grid2> </Grid2>
</Grid2> </Grid2>
@ -354,6 +386,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
{ renderGutter && <TableCell></TableCell>} { renderGutter && <TableCell></TableCell>}
{ columns ? { columns ?
columns.map((column, index) => { columns.map((column, index) => {
if (filterList.includes(column.title)) return null
return ( return (
<TableCell key={"header-cell-"+index}> <TableCell key={"header-cell-"+index}>
{column.title} {column.title}
@ -382,6 +415,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</TableRow> </TableRow>
: :
rows.map((row, index) => { rows.map((row, index) => {
// if (filterList.includes()) return null
return ( return (
<React.Fragment key={"row-"+index}> <React.Fragment key={"row-"+index}>
<TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}> <TableRow onClick={() => onRowClick&&onRowClick(row)} className={onRowClick ? classes.rowHover : undefined}>
@ -400,6 +434,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
} }
{columns ? {columns ?
columns.map((column, j) => { columns.map((column, j) => {
if (filterList.includes(column.title)) return null
if (column.render) return ( if (column.render) return (
<TableCell key={"row-"+index+"cell-"+j} sx={{ padding: 0}} > <TableCell key={"row-"+index+"cell-"+j} sx={{ padding: 0}} >
{column.render(row)} {column.render(row)}
@ -454,6 +489,26 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
onPageChange={(_event, page) => handlePageChange(page)} onPageChange={(_event, page) => handlePageChange(page)}
onRowsPerPageChange={handleRowsPerPageChange} onRowsPerPageChange={handleRowsPerPageChange}
/> />
<Menu
id="userMenu"
anchorEl={columnAnchor}
open={columnAnchor !== null}
onClose={closeColumnMenu}
disableAutoFocusItem>
{columns?.map((column, index) => {
return (
<MenuItem key={"column-list-"+index} aria-label="Open User Settings" dense sx={{ marginLeft: -0.75 }}>
<ListItemIcon>
<Checkbox
checked={!filterList.includes(column.title)}
onClick={() => filter(column.title)}
/>
</ListItemIcon>
<ListItemText primary={column.title} />
</MenuItem>
)
})}
</Menu>
</TableContainer> </TableContainer>
) )
} }

View file

@ -1,5 +1,5 @@
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material"; import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
import { Box, Chip, CircularProgress, Grid2, IconButton, Skeleton, Tab, Tabs, Theme, Tooltip, Typography } from "@mui/material"; import { Box, Chip, Grid2, IconButton, Skeleton, Tab, Tabs, Theme, Tooltip, Typography } from "@mui/material";
import { blue, green, orange } from "@mui/material/colors"; import { blue, green, orange } from "@mui/material/colors";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
@ -8,7 +8,7 @@ import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGroupAPI } from "providers"; import { useDeviceAPI, useGroupAPI } from "providers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import { useMobile, usePermissionAPI } from "hooks"; import { useMobile } from "hooks";
import { useLocation, useNavigate, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { getDeviceStateHelper } from "pbHelpers/DeviceState"; import { getDeviceStateHelper } from "pbHelpers/DeviceState";
@ -18,7 +18,6 @@ import SmartBreadcrumb from "common/SmartBreadcrumb";
import GroupActions from "group/GroupActions"; import GroupActions from "group/GroupActions";
// import CircleGraphIcon from "common/CircleGraphIcon"; // import CircleGraphIcon from "common/CircleGraphIcon";
import DevicesSummary from "device/DevicesSummary"; import DevicesSummary from "device/DevicesSummary";
import MeasurementSummary from "component/MeasurementSummary";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -76,7 +75,6 @@ export default function Devices() {
const location = useLocation(); const location = useLocation();
const deviceAPI = useDeviceAPI(); const deviceAPI = useDeviceAPI();
const groupAPI = useGroupAPI(); const groupAPI = useGroupAPI();
const permissionAPI = usePermissionAPI();
const [devicesLoading, setDevicesLoading] = useState(false) const [devicesLoading, setDevicesLoading] = useState(false)
const [limit, setLimit] = useState(10); const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
@ -86,8 +84,6 @@ export default function Devices() {
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<Device[]>([]) const [devices, setDevices] = useState<Device[]>([])
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false); const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
const [stats, setStats] = useState<pond.DeviceStatistics>(pond.DeviceStatistics.create())
const [loadingStats, setLoadingStats] = useState(false)
const [hasPlenums, setHasPlenums] = useState(false) const [hasPlenums, setHasPlenums] = useState(false)
const [groupsLoading, setGroupsLoading] = useState(false) const [groupsLoading, setGroupsLoading] = useState(false)
@ -218,44 +214,18 @@ export default function Devices() {
}) })
} }
const loadDeviceStatistics = () => {
setLoadingStats(true)
deviceAPI.statistics(
getContextKeys(),
getContextTypes()
).then(resp => {
let stats = pond.DeviceStatistics.fromObject(resp.data.stats)
setStats(stats)
}).finally(() => {
setLoadingStats(false)
})
}
useEffect(() => { useEffect(() => {
loadDevices() loadDevices()
}, [limit, page, order, orderBy, search, tab]) }, [limit, page, order, orderBy, search, tab])
useEffect(() => {
loadDeviceStatistics()
}, [])
useEffect(() => { useEffect(() => {
loadGroups() loadGroups()
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup]) }, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
// useEffect(() => {
// permissionAPI.getPermissions
// }, [groupID])
const handleChange = (event: any) => { const handleChange = (event: any) => {
setLimit(event.target.value); setLimit(event.target.value);
}; };
// const appendToUrl = (appendage: number | string) => {
// const basePath = location.pathname.replace(/\/$/, "");
// return(`${basePath}/${appendage}`);
// };
const prependToUrl = (prependage: number | string) => { const prependToUrl = (prependage: number | string) => {
const basePath = location.pathname.replace(/\/$/, ""); const basePath = location.pathname.replace(/\/$/, "");
return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1")); return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));