411 lines
14 KiB
TypeScript
411 lines
14 KiB
TypeScript
import { Gate } from "models/Gate";
|
|
import { Box, Card, IconButton, List, ListItem, ListItemText, Theme, Typography } from "@mui/material";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useMobile } from "hooks";
|
|
//import MaterialTable from "material-table";
|
|
//import { getTableIcons } from "common/ResponsiveTable";
|
|
import { Terminal } from "models/Terminal";
|
|
import { makeStyles } from "@mui/styles";
|
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
|
import AddGateFab from "./AddGateFab";
|
|
import GateSettings from "./GateSettings";
|
|
import { useGateAPI, useGlobalState } from "providers";
|
|
import { CheckCircleOutline, DoNotDisturb, ErrorOutline, HelpOutlineOutlined, Settings } from "@mui/icons-material";
|
|
import { cloneDeep } from "lodash";
|
|
import moment from "moment";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { UnitMeasurement } from "models/UnitMeasurement";
|
|
import { quack } from "protobuf-ts/quack";
|
|
import { getTemperatureUnit } from "utils";
|
|
import { teal } from "@mui/material/colors";
|
|
import { react } from "@babel/types";
|
|
|
|
interface Props {
|
|
//gates: Gate[];
|
|
terminals: Terminal[];
|
|
currentTerminal?: string; //the key of the terminal for the currently selected tab
|
|
useMobile: boolean;
|
|
//duplicateGate: (gate: Gate) => void;
|
|
}
|
|
|
|
const useStyles = makeStyles((theme: Theme) => ({
|
|
gridListTile: {
|
|
minHeight: "184px",
|
|
height: "auto !important",
|
|
width: "184px",
|
|
padding: 2
|
|
},
|
|
hidden: {
|
|
visibility: "hidden"
|
|
},
|
|
gateCard: {
|
|
marginBottom: 10,
|
|
paddingLeft: 15
|
|
}
|
|
}));
|
|
|
|
export default function GateList(props: Props) {
|
|
const {terminals, currentTerminal } = props;
|
|
const [{as}] = useGlobalState();
|
|
// const history = useHistory();
|
|
const navigate = useNavigate();
|
|
const isMobile = useMobile();
|
|
const classes = useStyles();
|
|
const [{user}] = useGlobalState()
|
|
const [gateDialog, setGateDialog] = useState(false);
|
|
const [tablePage, setTablePage] = useState(0)
|
|
const [pageSize, setPageSize] = useState(10)
|
|
const [total, setTotal] = useState(0)
|
|
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) => {
|
|
let path = "/terminals/" + gate.key;
|
|
navigate(path, { state: gate })
|
|
};
|
|
|
|
useEffect(() => {
|
|
let map = new Map<string, string>();
|
|
terminals.forEach(t => {
|
|
map.set(t.key, t.name);
|
|
});
|
|
setTerminalMap(map);
|
|
}, [terminals]);
|
|
|
|
const loadGates = useCallback(() => {
|
|
let keys = undefined
|
|
let types = undefined
|
|
if(currentTerminal) {
|
|
keys = [currentTerminal]
|
|
types = ["terminal"]
|
|
}
|
|
gateAPI.listGates(pageSize, pageSize * tablePage, "asc", "name", search, undefined, keys, types, undefined, undefined, as).then(resp => {
|
|
setGates(resp.data.gates.map(gate => Gate.create(gate)))
|
|
setTotal(resp.data.total)
|
|
})
|
|
},[currentTerminal, search, pageSize, tablePage, as])
|
|
|
|
useEffect(() => {
|
|
loadGates()
|
|
},[loadGates])
|
|
|
|
const handleChange = (event: any) => {
|
|
setPageSize(event.target.value);
|
|
};
|
|
|
|
const displayPCAStatus = (state: pond.PCAState) => {
|
|
switch(state){
|
|
case pond.PCAState.PCA_STATE_IN_BOUNDS:
|
|
return <CheckCircleOutline sx={{color: "green"}}/>
|
|
case pond.PCAState.PCA_STATE_OUT_BOUNDS:
|
|
return <ErrorOutline sx={{color: "red"}} />
|
|
case pond.PCAState.PCA_STATE_OFF:
|
|
return <DoNotDisturb />
|
|
default:
|
|
return <HelpOutlineOutlined />
|
|
}
|
|
}
|
|
|
|
// const lastOutletReading = (reading: pond.UnitMeasurementsForComponent[]) => {
|
|
// let outletDisplay = "--"
|
|
// let timeSince = "No Reading Yet"
|
|
// let color = ""
|
|
// reading.forEach(um => {
|
|
// let clone = cloneDeep(um)
|
|
// let measurement = UnitMeasurement.create(clone, user)
|
|
// if(measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE || measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE){
|
|
// let nodes = measurement.values[measurement.values.length-1].values
|
|
// outletDisplay = nodes[nodes.length-1].toFixed(2) + " " + measurement.unit
|
|
// timeSince = moment(measurement.timestamps[measurement.timestamps.length-1]).fromNow()
|
|
// color = measurement.colour
|
|
// }
|
|
// })
|
|
|
|
// if(isMobile){
|
|
// return (
|
|
// <React.Fragment>
|
|
// <Typography color={color}>{outletDisplay}</Typography>
|
|
// <Typography>{timeSince}</Typography>
|
|
// </React.Fragment>
|
|
// )
|
|
// }
|
|
// return (
|
|
// <Box padding={2}>
|
|
// <Box display="flex" justifyContent="space-between">
|
|
// <Typography>Value:</Typography>
|
|
// <Typography color={color}>{outletDisplay}</Typography>
|
|
// </Box>
|
|
// <Box display="flex" justifyContent="space-between">
|
|
// <Typography>Time:</Typography>
|
|
// <Typography>{timeSince}</Typography>
|
|
// </Box>
|
|
// </Box>
|
|
// )
|
|
// }
|
|
|
|
// const tempDisplay = (gate: Gate) => {
|
|
// let display = "--"
|
|
// if(gate.status.pcaState !== pond.PCAState.PCA_STATE_OFF){
|
|
// //only display it if the final temp is between -4 and 60 C, is a valid number, and is not exactly 0
|
|
// //0 is technically a valid number but the likely hood of the calculation being exactly 0 is negligible
|
|
// if (gate.status.finalTemp < 60 && gate.status.finalTemp > -40 && !isNaN(gate.status.finalTemp) && gate.status.finalTemp !== 0){
|
|
// display = getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? CtoF(gate.status.finalTemp).toFixed(2) + "°F" : gate.status.finalTemp.toFixed(2) + "°C"
|
|
// }
|
|
// }
|
|
// return (
|
|
// <Typography>
|
|
// {display}
|
|
// </Typography>
|
|
// )
|
|
// }
|
|
|
|
const conditionDisplay = (gate: Gate) => {
|
|
let display = ""
|
|
if(gate.status.pcaState === pond.PCAState.PCA_STATE_OFF){
|
|
display = "Inactive"
|
|
} else if (gate.status.pcaState === pond.PCAState.PCA_STATE_UNKNOWN){
|
|
display = "--"
|
|
} else { //the pca is currently active calulate the delta temp (outlet - ambient)
|
|
let ambient = cloneDeep(gate.status.lastAmbientReading)
|
|
let temp = cloneDeep(gate.status.lastTempReading)
|
|
|
|
if(ambient.length > 0 && temp.length > 0){
|
|
let ambientTemp: number | undefined
|
|
let outletTemp: number | undefined
|
|
ambient.forEach(um => {
|
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
|
let clone = cloneDeep(um)
|
|
let measurement = UnitMeasurement.any(clone, user)
|
|
if(measurement.values.length > 0){
|
|
let readings = measurement.values[measurement.values.length -1]
|
|
if(readings.values.length > 0){
|
|
ambientTemp = readings.values[readings.values.length -1]
|
|
}
|
|
}
|
|
}
|
|
})
|
|
temp.forEach(um => {
|
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
|
let clone = cloneDeep(um)
|
|
let measurement = UnitMeasurement.any(clone, user)
|
|
if(measurement.values.length > 0){
|
|
let readings = measurement.values[measurement.values.length -1]
|
|
if(readings.values.length > 0){
|
|
outletTemp = readings.values[readings.values.length -1]
|
|
}
|
|
}
|
|
}
|
|
})
|
|
if(ambientTemp && outletTemp){
|
|
let deltaTemp = outletTemp - ambientTemp
|
|
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
|
}else{
|
|
display = "Sensor Missing"
|
|
}
|
|
}
|
|
}
|
|
return (
|
|
<Typography>
|
|
{display}
|
|
</Typography>
|
|
)
|
|
}
|
|
|
|
// const CtoF = (celsius: number) => {
|
|
// return Math.round((celsius * (9 / 5) + 32) * 100) / 100;
|
|
// };
|
|
|
|
const desktopCols = (): Column<Gate>[] => {
|
|
return [
|
|
{
|
|
title: "Gate",
|
|
render: gate => (
|
|
<Box padding={2}>
|
|
<Typography>
|
|
{gate.name}
|
|
</Typography>
|
|
</Box>
|
|
)
|
|
},
|
|
{
|
|
title: "Terminal",
|
|
render: gate => (
|
|
<Box padding={2}>
|
|
<Typography>
|
|
{terminalMap.get(gate.terminal())}
|
|
</Typography>
|
|
</Box>
|
|
)
|
|
},
|
|
{
|
|
title: "PCA Status",
|
|
render: gate => {
|
|
return (<Box padding={2}>
|
|
{displayPCAStatus(gate.status.pcaState)}
|
|
</Box>)
|
|
}
|
|
},
|
|
{
|
|
title: "Delta Temperature",
|
|
render: gate => {
|
|
return (
|
|
<Box padding={2}>
|
|
{conditionDisplay(gate)}
|
|
</Box>
|
|
)}
|
|
},
|
|
//taking out these columns for now because were not sure if the customer actually wants them
|
|
// {
|
|
// title: "Estimated Temp",
|
|
// render: gate => {
|
|
// return (
|
|
// <Box padding={2}>
|
|
// {tempDisplay(gate)}
|
|
// </Box>
|
|
// )
|
|
// }
|
|
// },
|
|
// {
|
|
// title: "Last FLow",
|
|
// render: gate => (
|
|
// <Box padding={2}>
|
|
// <Box display="flex" justifyContent="space-between">
|
|
// <Typography>Flow:</Typography>
|
|
// <Typography color={teal[500]}>{gate.status.lastMassAirflow.toFixed(2)} kg/s</Typography>
|
|
// </Box>
|
|
// <Box display="flex" justifyContent="space-between">
|
|
// <Typography>Read:</Typography>
|
|
// <Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No Reading Yet"}</Typography>
|
|
// </Box>
|
|
// </Box>
|
|
// )
|
|
// },
|
|
// {
|
|
// title: "Outlet Temperature",
|
|
// render: gate => (
|
|
// <Box>{lastOutletReading(gate.status.lastTempReading)}</Box>
|
|
// )
|
|
// },
|
|
// {
|
|
// title: "Outlet Pressure",
|
|
// render: gate => (
|
|
// <Box>{lastOutletReading(gate.status.lastPressureReading)}</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>
|
|
<Box display="flex" alignItems="center" gap={1}>
|
|
{displayPCAStatus(gate.status.pcaState)}
|
|
<IconButton onClick={(event) => {
|
|
event.stopPropagation()
|
|
setSelectedGate(gate)
|
|
setGateDialog(true)
|
|
}}>
|
|
<Settings />
|
|
</IconButton>
|
|
</Box>
|
|
</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>Delta Temperature:</Typography>
|
|
{conditionDisplay(gate)}
|
|
</Box>
|
|
{/* taking these out for now because we are not sure if the customer wants them */}
|
|
{/* <Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
<Typography>Estimated Temp:</Typography>
|
|
{tempDisplay(gate)}
|
|
</Box>
|
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
<Typography>Last Flow:</Typography>
|
|
<Typography color={teal[500]}>{gate.status.lastMassAirflow.toFixed(2)} kg/s</Typography>
|
|
<Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No PCA reading yet"}</Typography>
|
|
</Box>
|
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
<Typography>Outlet Temp:</Typography>
|
|
{lastOutletReading(gate.status.lastTempReading)}
|
|
</Box>
|
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
<Typography>Outlet Pressure:</Typography>
|
|
{lastOutletReading(gate.status.lastPressureReading)}
|
|
</Box> */}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
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) => {setTablePage(page)}}
|
|
total={total}
|
|
onRowClick={(gate) => {goToGate(gate)}}
|
|
hideKeys={isMobile || props.useMobile}
|
|
setSearchText={(search) => setSearch(search)}
|
|
loadMore={()=>{
|
|
let current = cloneDeep(gates)
|
|
let keys = undefined
|
|
let types = undefined
|
|
if(currentTerminal) {
|
|
keys = [currentTerminal]
|
|
types = ["terminal"]
|
|
}
|
|
gateAPI.listGates(pageSize, current.length , "asc", "name", search, undefined, keys, types, undefined, undefined, as).then(resp => {
|
|
let newGates = resp.data.gates.map(gate => Gate.create(gate))
|
|
setGates(current.concat(newGates))
|
|
})
|
|
}}
|
|
/>
|
|
)
|
|
};
|
|
|
|
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>
|
|
}
|