added table sorting to responsive table

This commit is contained in:
Carter 2025-05-22 11:16:08 -06:00
parent 4bd66d3d24
commit 8143ef4c37
2 changed files with 60 additions and 10 deletions

View file

@ -1,6 +1,6 @@
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 { 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, ViewColumn } from "@mui/icons-material" import { ArrowDownward, ArrowUpward, 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";
@ -64,6 +64,8 @@ export interface Column<T> {
cellStyle?: SxProps<Theme>; cellStyle?: SxProps<Theme>;
hidden?: boolean; hidden?: boolean;
render: (row: T) => JSX.Element; render: (row: T) => JSX.Element;
sortKey?: string;
disableSort?: boolean;
} }
interface Props<T> { interface Props<T> {
@ -92,6 +94,10 @@ interface Props<T> {
customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows 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 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 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">>;
} }
export default function ResponsiveTable<T extends Object>(props: Props<T>) { export default function ResponsiveTable<T extends Object>(props: Props<T>) {
@ -120,12 +126,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
selectedRows, selectedRows,
customElement, customElement,
mobileView, mobileView,
hidePagination hidePagination,
orderBy,
setOrderBy,
order,
setOrder
} = props } = props
const classes = useStyles(); const classes = useStyles();
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle
const title = typeof(props.title) === "function" ? props.title() : props.title const title = typeof(props.title) === "function" ? props.title() : props.title
// const order = props.order ? props.order : "asc"
const isMobile = useMobile() const isMobile = useMobile()
@ -373,12 +384,30 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
setFilterList(newFilterList) setFilterList(newFilterList)
} }
const columnClick = (column: Column<T>) => {
const sortKey = column.sortKey ? column.sortKey : column.title
console.log(column.sortKey)
if (setOrderBy) 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
if (sortKey === orderBy) {
if (order === "asc") {
return <ArrowDownward/>
} else {
return <ArrowUpward/>
}
}
return null;
}
return ( return (
<TableContainer className={classes.tableContainer} component={Paper}> <TableContainer className={classes.tableContainer} component={Paper}>
<Table> <Table>
<TableHead> <TableHead>
<TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}> <TableRow sx={ !title && !setSearchText ? { display: "none" } : undefined}>
<TableCell colSpan={10000} sx={{ border: "none" }}> <TableCell colSpan={10000} sx={{ border: "none" }}>
<Grid2 container justifyContent="space-between"> <Grid2 container justifyContent="space-between">
@ -419,7 +448,6 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</TableRow> </TableRow>
} }
<TableRow sx={ hideKeys ? { display: "none" } : undefined } > <TableRow sx={ hideKeys ? { display: "none" } : undefined } >
{ rowSelect && <TableCell></TableCell> } { rowSelect && <TableCell></TableCell> }
{ renderGutter && <TableCell></TableCell>} { renderGutter && <TableCell></TableCell>}
@ -427,8 +455,14 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
columns.map((column, index) => { columns.map((column, index) => {
if (filterList.includes(column.title) || column.hidden) return null if (filterList.includes(column.title) || column.hidden) return null
return ( return (
<TableCell key={"header-cell-"+index}> <TableCell
{column.title} key={"header-cell-"+index}
onClick={() => !column.disableSort && columnClick(column)}
sx={{ cursor: column.disableSort ? "default" : "pointer" }}
>
<Grid2 container spacing={1}>
{column.title} {showOrderIcon(column)}
</Grid2>
</TableCell> </TableCell>
) )
}) })

View file

@ -92,8 +92,8 @@ export default function Devices() {
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);
const [order, ] = useState<"asc" | "desc">("asc"); const [order, setOrder] = useState<"asc" | "desc">("asc");
const [orderBy, ] = useState("name"); const [orderBy, setOrderBy] = useState("name");
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<Device[]>([]) const [devices, setDevices] = useState<Device[]>([])
@ -293,10 +293,15 @@ export default function Devices() {
if (newValue === "never") openGroupSettings(undefined, "add"); if (newValue === "never") openGroupSettings(undefined, "add");
}; };
useEffect(() => {
console.log(devices)
}, [devices])
const columns = (): Column<Device>[] => { const columns = (): Column<Device>[] => {
let columns = [ let columns: Column<Device>[] = [
{ {
title: "State", title: "State",
sortKey: "state",
render: (device: Device) => { render: (device: Device) => {
const status = device.status ?? pond.DeviceStatus.create() const status = device.status ?? pond.DeviceStatus.create()
const deviceStateHelper = getDeviceStateHelper(status.state); const deviceStateHelper = getDeviceStateHelper(status.state);
@ -309,12 +314,14 @@ export default function Devices() {
}, },
{ {
title: "ID", title: "ID",
sortKey: "deviceId",
render: (device: Device) => <span className={classes.cellContainer}> render: (device: Device) => <span className={classes.cellContainer}>
{device.settings?.deviceId} {device.settings?.deviceId}
</span> </span>
}, },
{ {
title: "Device", title: "Device",
sortKey: "name",
render: (device: Device) => { render: (device: Device) => {
return ( return (
<Box className={classes.cellContainer}> <Box className={classes.cellContainer}>
@ -346,6 +353,7 @@ export default function Devices() {
}, },
{ {
title: "Tags", title: "Tags",
sortKey: "tags",
render: (device: Device) => { render: (device: Device) => {
return ( return (
<Grid2 container spacing={1} className={classes.tagsCellContainer}> <Grid2 container spacing={1} className={classes.tagsCellContainer}>
@ -362,6 +370,8 @@ export default function Devices() {
if (hasPlenums) { if (hasPlenums) {
columns.push({ columns.push({
title: "Plenum", title: "Plenum",
sortKey: "hi",
disableSort: true,
render: (device: Device) => { render: (device: Device) => {
if (device.status.plenum?.temperature) { if (device.status.plenum?.temperature) {
return ( return (
@ -380,6 +390,8 @@ export default function Devices() {
if (hasSen5x) { if (hasSen5x) {
columns.push({ columns.push({
title: "Sen5X", title: "Sen5X",
sortKey: "hi",
disableSort: true,
render: (device: Device) => { render: (device: Device) => {
if (device.status.sen5x?.temperature) { if (device.status.sen5x?.temperature) {
return ( return (
@ -532,6 +544,10 @@ export default function Devices() {
setSearchText={setSearch} setSearchText={setSearch}
isLoading={devicesLoading} isLoading={devicesLoading}
actions={getGroup() && <GroupActions removeCallback={removeGroupCallback} group={getGroup()} permissions={groupPermissions} devices={devices} refreshCallback={loadDevices}/>} actions={getGroup() && <GroupActions removeCallback={removeGroupCallback} group={getGroup()} permissions={groupPermissions} devices={devices} refreshCallback={loadDevices}/>}
order={order}
setOrder={setOrder}
orderBy={orderBy}
setOrderBy={setOrderBy}
/> />
<ProvisionDevice <ProvisionDevice
isOpen={isProvisionDialogOpen} isOpen={isProvisionDialogOpen}