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 { ChevronRight, Close, Search } from "@mui/icons-material"
import { ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material"
import { useEffect, useState } from "react";
import React from "react";
import { useMobile } from "hooks";
@ -61,7 +61,7 @@ const useStyles = makeStyles((theme: Theme) => {
);
export interface Column<T> {
title: string | JSX.Element;
title: string;
cellStyle?: SxProps<Theme>;
render: (row: T) => JSX.Element;
}
@ -117,6 +117,18 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const [inputSearchText, setInputSearchText] = useState("");
const [openGutters, setOpenGutters] = useState<number[]>([])
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(() => {
if (!setSearchText) return;
@ -266,12 +278,14 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
)
}
if (isMobile) return (
<Box>
<Box className={classes.mobileTitleBox}>
{renderTitle()}
{renderSubtitle()}
{setSearchText && searchBar()}
</Box>
{rows.map((row, index) => {
return (
@ -322,6 +336,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</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 (
<TableContainer className={classes.tableContainer} component={Paper}>
<Table>
@ -345,6 +370,13 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
{searchBar()}
</Box>}
</Grid2>
<Grid2>
{ columns &&
<IconButton onClick={openColumnMenu} >
<ViewColumn />
</IconButton>
}
</Grid2>
</Grid2>
</Grid2>
</Grid2>
@ -354,6 +386,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
{ renderGutter && <TableCell></TableCell>}
{ columns ?
columns.map((column, index) => {
if (filterList.includes(column.title)) return null
return (
<TableCell key={"header-cell-"+index}>
{column.title}
@ -382,6 +415,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</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}>
@ -400,6 +434,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
}
{columns ?
columns.map((column, j) => {
if (filterList.includes(column.title)) return null
if (column.render) return (
<TableCell key={"row-"+index+"cell-"+j} sx={{ padding: 0}} >
{column.render(row)}
@ -454,6 +489,26 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
onPageChange={(_event, page) => handlePageChange(page)}
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>
)
}

View file

@ -1,5 +1,5 @@
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 { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
@ -8,7 +8,7 @@ import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGroupAPI } from "providers";
import { useEffect, useState } from "react";
import PageContainer from "./PageContainer";
import { useMobile, usePermissionAPI } from "hooks";
import { useMobile } from "hooks";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
@ -18,7 +18,6 @@ import SmartBreadcrumb from "common/SmartBreadcrumb";
import GroupActions from "group/GroupActions";
// import CircleGraphIcon from "common/CircleGraphIcon";
import DevicesSummary from "device/DevicesSummary";
import MeasurementSummary from "component/MeasurementSummary";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -76,7 +75,6 @@ export default function Devices() {
const location = useLocation();
const deviceAPI = useDeviceAPI();
const groupAPI = useGroupAPI();
const permissionAPI = usePermissionAPI();
const [devicesLoading, setDevicesLoading] = useState(false)
const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0);
@ -86,8 +84,6 @@ export default function Devices() {
const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<Device[]>([])
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 [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(() => {
loadDevices()
}, [limit, page, order, orderBy, search, tab])
useEffect(() => {
loadDeviceStatistics()
}, [])
useEffect(() => {
loadGroups()
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup])
// useEffect(() => {
// permissionAPI.getPermissions
// }, [groupID])
const handleChange = (event: any) => {
setLimit(event.target.value);
};
// const appendToUrl = (appendage: number | string) => {
// const basePath = location.pathname.replace(/\/$/, "");
// return(`${basePath}/${appendage}`);
// };
const prependToUrl = (prependage: number | string) => {
const basePath = location.pathname.replace(/\/$/, "");
return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));