re-worked the terminals page and gateList component for better use with ResponsiveTable

This commit is contained in:
csawatzky 2025-03-31 13:43:12 -06:00
parent 6f0921bf95
commit 02cee8623a
5 changed files with 213 additions and 159 deletions

View file

@ -79,6 +79,7 @@ interface Props<T> {
setPage: (value: React.SetStateAction<number>) => void, setPage: (value: React.SetStateAction<number>) => void,
handleRowsPerPageChange: (event: any) => void, handleRowsPerPageChange: (event: any) => void,
renderGutter?: (row: T) => JSX.Element, renderGutter?: (row: T) => JSX.Element,
gutterPadding?: number,
setSearchText?: React.Dispatch<React.SetStateAction<string>>, setSearchText?: React.Dispatch<React.SetStateAction<string>>,
isLoading?: boolean; isLoading?: boolean;
multiGutter?: boolean; multiGutter?: boolean;
@ -104,6 +105,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
// title, // title,
// subtitle, // subtitle,
renderGutter, renderGutter,
gutterPadding,
setSearchText, setSearchText,
isLoading, isLoading,
multiGutter, multiGutter,
@ -323,7 +325,8 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
sx={{ sx={{
width: "100%", width: "100%",
display: "flex", display: "flex",
justifyContent: "center" justifyContent: "center",
padding: gutterPadding
}} }}
onClick={(event) => handleCollapse(index, event)} onClick={(event) => handleCollapse(index, event)}
// style={{pre}} // style={{pre}}

View file

@ -1,16 +1,22 @@
import { Gate } from "models/Gate"; import { Gate } from "models/Gate";
import { Box, Card, Theme } from "@mui/material"; import { Box, Card, IconButton, List, ListItem, ListItemText, Theme, Typography } from "@mui/material";
import React, { useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
//import { getTableIcons } from "common/ResponsiveTable"; //import { getTableIcons } from "common/ResponsiveTable";
import { Terminal } from "models/Terminal"; import { Terminal } from "models/Terminal";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
import AddGateFab from "./AddGateFab";
import GateSettings from "./GateSettings";
import { useGateAPI } from "providers";
import { Settings } from "@mui/icons-material";
interface Props { interface Props {
gates: Gate[]; //gates: Gate[];
terminals: Terminal[]; terminals: Terminal[];
currentTerminal?: string; //the key of the terminal for the currently selected tab
useMobile: boolean; useMobile: boolean;
//duplicateGate: (gate: Gate) => void; //duplicateGate: (gate: Gate) => void;
} }
@ -32,12 +38,20 @@ const useStyles = makeStyles((theme: Theme) => ({
})); }));
export default function GateList(props: Props) { export default function GateList(props: Props) {
const { gates, terminals } = props; const {terminals, currentTerminal } = props;
// const history = useHistory(); // const history = useHistory();
const navigate = useNavigate(); const navigate = useNavigate();
const isMobile = useMobile(); const isMobile = useMobile();
const classes = useStyles(); const classes = useStyles();
const [gateDialog, setGateDialog] = useState(false);
const [tablePage, setTablePage] = useState(0)
const [pageSize, setPageSize] = useState(5)
const [total, setTotal] = useState(0)
const [terminalMap, setTerminalMap] = useState<Map<string, string>>(new Map<string, string>()); const [terminalMap, setTerminalMap] = useState<Map<string, string>>(new Map<string, string>());
const gateAPI = useGateAPI();
const [gates, setGates] = useState<Gate[]>([])
const [selectedGate, setSelectedGate] = useState<Gate | undefined>()
const [search, setSearch] = useState("")
const goToGate = (gate: Gate) => { const goToGate = (gate: Gate) => {
let path = "/terminals/" + gate.key; let path = "/terminals/" + gate.key;
@ -52,92 +66,180 @@ export default function GateList(props: Props) {
setTerminalMap(map); setTerminalMap(map);
}, [terminals]); }, [terminals]);
const desktopView = () => { const loadGates = useCallback(() => {
return (<Box>Desktop table</Box>) let keys = undefined
// return ( let types = undefined
// <MaterialTable if(currentTerminal) {
// columns={[ keys = [currentTerminal]
// { types = ["terminal"]
// title: "Gate", }
// field: "name", gateAPI.listGates(pageSize, pageSize * tablePage, "asc", "name", search, undefined, keys, types).then(resp => {
// headerStyle: { setGates(resp.data.gates.map(gate => Gate.create(gate)))
// fontWeight: 650, setTotal(resp.data.total)
// fontSize: 20 })
// } },[currentTerminal, search])
// },
// { useEffect(() => {
// title: "Terminals", loadGates()
// field: "settings.terminal", },[currentTerminal, search])
// headerStyle: {
// fontWeight: 650, const handleChange = (event: any) => {
// fontSize: 20 setPageSize(event.target.value);
// },
// render: rowData => terminalMap.get(rowData.terminal())
// },
// {
// title: "Duct Type",
// field: "settings.ductName",
// headerStyle: {
// fontWeight: 650,
// fontSize: 20
// }
// },
// {
// title: "Duct Size(mm)",
// field: "settings.ductDiameter",
// headerStyle: {
// fontWeight: 650,
// fontSize: 20
// }
// },
// {
// title: "Duct Length(m)",
// field: "settings.ductLength",
// headerStyle: {
// fontWeight: 650,
// fontSize: 20
// }
// },
// {
// title: "PCA Unit",
// field: "settings.pcaType",
// headerStyle: {
// fontWeight: 650,
// fontSize: 20
// }
// }
// ]}
// data={gates}
// icons={getTableIcons()}
// onRowClick={(_, gate) => {
// gate && goToGate(gate);
// }}
// title={""}
// options={{
// pageSize: 10
// }}
// />
// );
}; };
const mobileView = () => { const desktopCols = (): Column<Gate>[] => {
return ( return [
<React.Fragment> {
{gates.map((gate, i) => ( title: "Gate",
<Card render: gate => (
key={i} <Box padding={2}>
className={classes.gateCard} <Typography>
onClick={() => {
goToGate(gate);
}}>
<Box margin={2} fontSize={20} fontWeight={650}>
{gate.name} {gate.name}
</Typography>
</Box> </Box>
</Card> )
))} },
</React.Fragment> {
); title: "Terminal",
render: gate => (
<Box padding={2}>
<Typography>
{terminalMap.get(gate.terminal())}
</Typography>
</Box>
)
},
{
title: "Duct Type",
render: gate => (
<Box padding={2}>
<Typography>
{gate.settings.ductName}
</Typography>
</Box>
)
},
{
title: "Duct Size(mm)",
render: gate => (
<Box padding={2}>
<Typography>
{gate.ductDiameter()}
</Typography>
</Box>
)
},
{
title: "Duct Length(m)",
render: gate => (
<Box padding={2}>
<Typography>
{gate.settings.ductLength}
</Typography>
</Box>
)
},
{
title: "PCA Unit",
render: gate => (
<Box padding={2}>
<Typography>
{gate.settings.pcaType}
</Typography>
</Box>
)
},
]
}
const mobileCols = (): Column<Gate>[] => {
return [{
title: "",
render: gate => {
return (
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography margin="auto" marginLeft={0} fontWeight={650} fontSize={20}>
{gate.name}
</Typography>
<IconButton onClick={(event) => {
event.stopPropagation()
setSelectedGate(gate)
setGateDialog(true)
}}>
<Settings />
</IconButton>
</Box>
)
}
}]
}
const gutter = (gate: Gate) => {
return(
<Box>
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography>Terminal:</Typography>
<Typography>{terminalMap.get(gate.terminal()) ?? "None"}</Typography>
</Box>
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography>Duct Type:</Typography>
<Typography>{gate.ductName() !== "" ? gate.ductName() : "None"}</Typography>
</Box>
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography>Duct Size(mm):</Typography>
<Typography>{gate.ductDiameter()}</Typography>
</Box>
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography>DuctLength(m):</Typography>
<Typography>{gate.ductLength()}</Typography>
</Box>
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
<Typography>PCA Unit:</Typography>
<Typography>{gate.settings.pcaType !== "" ? gate.settings.pcaType : "None"}</Typography>
</Box>
</Box>
// <Typography variant="body2" color="textSecondary" sx={{ padding: 1 }}>
// </Typography>
)
}
const gateTable = () => {
return (
<ResponsiveTable<Gate>
page={tablePage}
pageSize={pageSize}
columns={isMobile || props.useMobile ? mobileCols() : desktopCols()}
handleRowsPerPageChange={handleChange}
renderGutter={isMobile || props.useMobile ? gutter : undefined}
gutterPadding={0}
rows={gates}
setPage={(page) => {console.log(page)}}
total={total}
onRowClick={(gate) => {goToGate(gate)}}
hideKeys={isMobile || props.useMobile}
setSearchText={(search) => setSearch(search)}
/>
)
}; };
return isMobile || props.useMobile ? mobileView() : desktopView(); return <Box>
<GateSettings
open={gateDialog}
gate={selectedGate}
close={newGate => {
if (newGate) {
gates.push(newGate)
}
setGateDialog(false);
}}
terminals={terminals}
/>
<AddGateFab
onClick={() => {
setGateDialog(true);
}}
pulse={gates.length < 1}
/>
{gateTable()}
</Box>
} }

View file

@ -64,10 +64,18 @@ export class Gate {
return this.settings.lowerFlow; return this.settings.lowerFlow;
} }
public ductName(): string {
return this.settings.ductName;
}
public ductDiameter(): number { public ductDiameter(): number {
return this.settings.ductDiameter; return this.settings.ductDiameter;
} }
public ductLength(): number {
return this.settings.ductLength;
}
public gateMarkerColour(): string { public gateMarkerColour(): string {
switch (this.pcaState) { switch (this.pcaState) {
case pond.PCAState.PCA_STATE_OFF: case pond.PCAState.PCA_STATE_OFF:

View file

@ -161,7 +161,7 @@ export default function Router(props: Props) {
<Route <Route
key="Terminal page route" key="Terminal page route"
path="" // "/settings/basic" path="" // "/settings/basic"
element={<Terminals key={"Mines page"} />} element={<Terminals key={"Terminals page"} />}
/> />
<Route <Route
path="/:gateKey" // "/settings/basic" path="/:gateKey" // "/settings/basic"

View file

@ -15,7 +15,6 @@ import {
import { Terminal } from "models"; import { Terminal } from "models";
import { useTerminalAPI, useGlobalState } from "providers"; import { useTerminalAPI, useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import GateSettings from "gate/GateSettings";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import TerminalSettings from "terminal/TerminalSettings"; import TerminalSettings from "terminal/TerminalSettings";
@ -24,7 +23,6 @@ import EditIcon from "@mui/icons-material/Edit";
import { blue, green, red } from "@mui/material/colors"; import { blue, green, red } from "@mui/material/colors";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { useSnackbar, useUserAPI } from "hooks"; import { useSnackbar, useUserAPI } from "hooks";
import { Gate } from "models/Gate";
import GateList from "gate/GateList"; import GateList from "gate/GateList";
import { Scope } from "models"; import { Scope } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
@ -34,7 +32,6 @@ import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
import ShareObject from "user/ShareObject"; import ShareObject from "user/ShareObject";
import RemoveSelfFromObject from "user/RemoveSelfFromObject"; import RemoveSelfFromObject from "user/RemoveSelfFromObject";
import AddGateFab from "gate/AddGateFab";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
const parentTab = { const parentTab = {
@ -142,19 +139,12 @@ interface Props {
} }
export default function Terminals(props: Props) { export default function Terminals(props: Props) {
const [loading, setLoading] = useState(false);
const terminalAPI = useTerminalAPI(); const terminalAPI = useTerminalAPI();
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
const [terminals, setTerminals] = useState<Terminal[]>([]); const [terminals, setTerminals] = useState<Terminal[]>([]);
const [gates, setGates] = useState<Gate[]>([]);
// map using the terminal key and the gates for that terminal
const [gateMap, setGateMap] = useState<Map<string, Gate[]>>(new Map<string, Gate[]>());
const [displayGates, setDisplayGates] = useState<Gate[]>([]);
const [gateDialog, setGateDialog] = useState(false);
const classes = useStyles(); const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
const [addTerminal, setAddTerminal] = useState(false); const [addTerminal, setAddTerminal] = useState(false);
const [tabClick, setTabClick] = useState(true);
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null); const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
const [currentTerminal, setCurrentTerminal] = useState<Terminal>(); const [currentTerminal, setCurrentTerminal] = useState<Terminal>();
const [removeTerminal, setRemoveTerminal] = useState(false); const [removeTerminal, setRemoveTerminal] = useState(false);
@ -184,44 +174,17 @@ export default function Terminals(props: Props) {
useEffect(() => { useEffect(() => {
if (props.terminal) { if (props.terminal) {
setCurrentTerminal(props.terminal); setCurrentTerminal(props.terminal);
setDisplayGates(gateMap.get(props.terminal.key) ?? []);
} }
}, [props.terminal, gateMap]); }, [props.terminal]);
const load = useCallback(() => { const load = useCallback(() => {
console.log(loading) terminalAPI.listTerminals(200, 0, "asc", "name").then(resp => {
if (loading) return;
console.log("terminal load")
setLoading(true);
terminalAPI
.listTerminalsAndGates(200, 0)
.then(resp => {
console.log(resp)
setTerminals(resp.data.terminals.map(a => Terminal.any(a))); setTerminals(resp.data.terminals.map(a => Terminal.any(a)));
let allGates = resp.data.gates.map(t => Gate.any(t));
setGates(allGates);
let tMap: Map<string, Gate[]> = new Map<string, Gate[]>();
allGates.forEach(gate => {
let mapEntry = tMap.get(gate.terminal());
if (mapEntry) {
mapEntry.push(gate);
} else {
tMap.set(gate.terminal(), [gate]);
}
setGateMap(tMap);
setDisplayGates(allGates);
});
}) })
.catch(err => {
openSnack("There was a problem loading your terminals");
})
.finally(() => {
setLoading(false);
});
}, [terminalAPI, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps }, [terminalAPI, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => { useEffect(() => {
console.log("load use") //TODO: will need to figure out a way to prevent multiple api calls when loading the page
load(); load();
}, [load]); }, [load]);
@ -324,12 +287,13 @@ export default function Terminals(props: Props) {
}; };
const changeTabs = (newValue: number) => { const changeTabs = (newValue: number) => {
if (tabClick && newValue <= terminals.length) { //TODO: change this to just change the selected tab
if (newValue <= terminals.length) {
setValue(newValue); setValue(newValue);
if (terminals[newValue - 1]) { if (terminals[newValue - 1]) {
setDisplayGates(gateMap.get(terminals[newValue - 1].key) ?? []); setCurrentTerminal(terminals[newValue -1])
} else { } else {
setDisplayGates(gates); setCurrentTerminal(undefined)
} }
} }
}; };
@ -337,7 +301,7 @@ export default function Terminals(props: Props) {
const gateDisplay = () => { const gateDisplay = () => {
return ( return (
<GateList <GateList
gates={displayGates} currentTerminal={currentTerminal?.key}
terminals={terminals} terminals={terminals}
useMobile={props.terminal !== undefined} useMobile={props.terminal !== undefined}
/> />
@ -370,10 +334,9 @@ export default function Terminals(props: Props) {
<span> <span>
<div className={classes.tabText}>{a.name}</div> <div className={classes.tabText}>{a.name}</div>
<MoreVert <MoreVert
onMouseEnter={() => setTabClick(false)}
onMouseLeave={() => setTabClick(true)}
className={classes.icon} className={classes.icon}
onClick={event => { onClick={event => {
event.stopPropagation()
let target = event.currentTarget; let target = event.currentTarget;
setMenuAnchorEl(target); setMenuAnchorEl(target);
setCurrentTerminal(terminals[index]); setCurrentTerminal(terminals[index]);
@ -465,22 +428,6 @@ export default function Terminals(props: Props) {
return ( return (
<PageContainer> <PageContainer>
<GateSettings
open={gateDialog}
close={newGate => {
if (newGate) {
if (newGate.terminal() !== "") {
let tMap = gateMap;
tMap.get(newGate.terminal())?.push(newGate);
}
let t = gates;
t.push(newGate);
setGates([...t]);
}
setGateDialog(false);
}}
terminals={terminals}
/>
<TerminalSettings <TerminalSettings
open={addTerminal} open={addTerminal}
closeDialog={newTerminal => { closeDialog={newTerminal => {
@ -498,12 +445,6 @@ export default function Terminals(props: Props) {
{removeDialog()} {removeDialog()}
{sharingDialogs()} {sharingDialogs()}
{gateDisplay()} {gateDisplay()}
<AddGateFab
onClick={() => {
setGateDialog(true);
}}
pulse={gates.length < 1}
/>
</PageContainer> </PageContainer>
); );
} }