finished the expanded cables dialog, worked on the scrolling for bin cables in the pages table view, did some mobile view edits
This commit is contained in:
parent
c3ee4a518c
commit
dc52ef77a7
6 changed files with 617 additions and 112 deletions
222
src/bin/BinTableExpanded.tsx
Normal file
222
src/bin/BinTableExpanded.tsx
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
import { Box, Table, TableBody, TableCell, TableHead, TableRow, Theme, Typography } from "@mui/material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { avg, celsiusToFahrenheit } from "utils";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { darken } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
tableStyle: { borderCollapse: "collapse" },
|
||||||
|
headerCellStyle: {
|
||||||
|
fontWeight: 650,
|
||||||
|
backgroundColor: darken(theme.palette.background.paper, 0.2),
|
||||||
|
fontSize: 15,
|
||||||
|
textAlign: "center",
|
||||||
|
border: "1px solid black"
|
||||||
|
},
|
||||||
|
cellStyle: {
|
||||||
|
padding: 2,
|
||||||
|
fontWeight: 650,
|
||||||
|
fontSize: 17,
|
||||||
|
textAlign: "center",
|
||||||
|
border: "1px solid black",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// interface Column {
|
||||||
|
// label: string // e.g. "Cable 1 Temp", "Cable 2 Moisture"
|
||||||
|
// cableIndex: number
|
||||||
|
// type: "temp" | "moisture"
|
||||||
|
// }
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
cables: pond.GrainCable[]
|
||||||
|
targetTemp: number // in Celsius, for colouring
|
||||||
|
targetMoisture: number // for colouring
|
||||||
|
inBounds?: string
|
||||||
|
caution?: string
|
||||||
|
warning?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BinTableExpanded(props: Props) {
|
||||||
|
const { cables, targetTemp, targetMoisture, inBounds = "#43a047", caution = "#f9a825", warning = "#c62828" } = props
|
||||||
|
const classes = useStyles()
|
||||||
|
const [{ user }] = useGlobalState()
|
||||||
|
|
||||||
|
const useFahrenheit = user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
|
|
||||||
|
// Build column definitions by iterating cables once
|
||||||
|
// const columns = useMemo<Column[]>(() => {
|
||||||
|
// const cols: Column[] = []
|
||||||
|
// cables.forEach((cable, cableIndex) => {
|
||||||
|
// cols.push({ label: `Cable ${cableIndex + 1} Temp`, cableIndex, type: "temp" })
|
||||||
|
// // Same moisture-validity check as BinTableView
|
||||||
|
// if (cable.moisture.length > 0 && avg(cable.moisture) > 0) {
|
||||||
|
// cols.push({ label: `Cable ${cableIndex + 1} Moisture`, cableIndex, type: "moisture" })
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// return cols
|
||||||
|
// }, [cables])
|
||||||
|
|
||||||
|
// Row count driven by the longest cable
|
||||||
|
const rowCount = useMemo(
|
||||||
|
() => Math.max(0, ...cables.map(c => c.celcius.length)),
|
||||||
|
[cables]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Render a single cell value + background colour
|
||||||
|
const renderCell = (cable: pond.GrainCable, nodeIndex: number, type: "temp" | "moisture") => {
|
||||||
|
if (type === "temp") {
|
||||||
|
const raw = cable.celcius[nodeIndex]
|
||||||
|
if (raw === undefined) return { display: "--", color: "" }
|
||||||
|
|
||||||
|
const isExcluded = cable.excludedNodes.includes(nodeIndex)
|
||||||
|
if (isExcluded) return { display: "--", color: "" }
|
||||||
|
|
||||||
|
let display: string
|
||||||
|
let color = ""
|
||||||
|
const inGrain = nodeIndex < cable.topNode
|
||||||
|
|
||||||
|
if (inGrain) {
|
||||||
|
if (raw > targetTemp + 5) color = warning
|
||||||
|
else if (raw > targetTemp) color = caution
|
||||||
|
else color = inBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
const converted = useFahrenheit ? celsiusToFahrenheit(raw) : raw
|
||||||
|
display = converted.toFixed(1) + (useFahrenheit ? " °F" : " °C")
|
||||||
|
return { display, color }
|
||||||
|
}
|
||||||
|
|
||||||
|
// moisture
|
||||||
|
const raw = cable.moisture[nodeIndex]
|
||||||
|
if (raw === undefined || raw === 0) return { display: "--", color: "" }
|
||||||
|
|
||||||
|
const isExcluded = cable.excludedNodes.includes(nodeIndex)
|
||||||
|
if (isExcluded) return { display: "--", color: "" }
|
||||||
|
|
||||||
|
let color = ""
|
||||||
|
const inGrain = nodeIndex < cable.topNode
|
||||||
|
if (inGrain) {
|
||||||
|
if (raw > targetMoisture + 1.5) color = warning
|
||||||
|
else if (raw > targetMoisture) color = caution
|
||||||
|
else color = inBounds
|
||||||
|
}
|
||||||
|
return { display: raw.toFixed(2) + "%", color }
|
||||||
|
}
|
||||||
|
|
||||||
|
const cableTable = (cable: pond.GrainCable) => {
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className={classes.headerCellStyle}>
|
||||||
|
Level
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.headerCellStyle}>
|
||||||
|
Temperature
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.headerCellStyle}>
|
||||||
|
Moisture
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{/* Rows are reversed so highest node is at top, matching BinTableView */}
|
||||||
|
{Array.from({ length: rowCount }, (_, i) => rowCount - 1 - i).map(nodeIndex => {
|
||||||
|
|
||||||
|
const tempData = renderCell(cable, nodeIndex, "temp")
|
||||||
|
const moistureData = renderCell(cable, nodeIndex, "moisture")
|
||||||
|
return (
|
||||||
|
<TableRow key={nodeIndex}>
|
||||||
|
<TableCell padding="none" className={classes.cellStyle}>
|
||||||
|
{nodeIndex + 1}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
padding="none"
|
||||||
|
className={classes.cellStyle}
|
||||||
|
sx={{ backgroundColor: tempData.color || undefined }}>
|
||||||
|
{tempData.display}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
padding="none"
|
||||||
|
className={classes.cellStyle}
|
||||||
|
sx={{ backgroundColor: moistureData.color || undefined }}>
|
||||||
|
{moistureData.display}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <Table className={classes.tableStyle}>
|
||||||
|
// <TableHead>
|
||||||
|
// <TableRow>
|
||||||
|
// <TableCell className={classes.headerCellStyle}>Level</TableCell>
|
||||||
|
// {columns.map((col, i) => (
|
||||||
|
// <TableCell key={i} className={classes.headerCellStyle}>
|
||||||
|
// {col.label}
|
||||||
|
// </TableCell>
|
||||||
|
// ))}
|
||||||
|
// </TableRow>
|
||||||
|
// </TableHead>
|
||||||
|
// <TableBody>
|
||||||
|
// {/* Rows are reversed so highest node is at top, matching BinTableView */}
|
||||||
|
// {Array.from({ length: rowCount }, (_, i) => rowCount - 1 - i).map(nodeIndex => (
|
||||||
|
// <TableRow key={nodeIndex}>
|
||||||
|
// <TableCell padding="none" className={classes.cellStyle}>
|
||||||
|
// {nodeIndex + 1}
|
||||||
|
// </TableCell>
|
||||||
|
// {columns.map((col, colI) => {
|
||||||
|
// const cable = cables[col.cableIndex]
|
||||||
|
// const { display, color } = renderCell(cable, nodeIndex, col.type)
|
||||||
|
// return (
|
||||||
|
// <TableCell
|
||||||
|
// key={colI}
|
||||||
|
// padding="none"
|
||||||
|
// className={classes.cellStyle}
|
||||||
|
// sx={{ backgroundColor: color || undefined }}
|
||||||
|
// >
|
||||||
|
// {display}
|
||||||
|
// </TableCell>
|
||||||
|
// )
|
||||||
|
// })}
|
||||||
|
// </TableRow>
|
||||||
|
// ))}
|
||||||
|
// </TableBody>
|
||||||
|
// </Table>
|
||||||
|
<Box padding={1}>
|
||||||
|
{/* Table Legend */}
|
||||||
|
<Box display="flex" alignItems="center" justifyContent={"space-between"} marginY={2}>
|
||||||
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
|
<Box sx={{ width: 40, height: 20, backgroundColor: inBounds, borderRadius: 1, flexShrink: 0 }} />
|
||||||
|
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
|
<Box sx={{ width: 40, height: 20, backgroundColor: caution, borderRadius: 1, flexShrink: 0 }} />
|
||||||
|
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+10 °F" : "+5 °C"} above target</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
|
<Box sx={{ width: 40, height: 20, backgroundColor: warning, borderRadius: 1, flexShrink: 0 }} />
|
||||||
|
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "row", overflowX: "auto", gap: 2}}>
|
||||||
|
{cables.map((cable, i) => (
|
||||||
|
<Box key={i} sx={{ flexShrink: 0 }}>
|
||||||
|
<Typography className={classes.headerCellStyle} sx={{ textAlign: "center" }}>
|
||||||
|
{cable.name}
|
||||||
|
</Typography>
|
||||||
|
{cableTable(cable)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { Avatar, Box, Button, Card, Checkbox, FormControlLabel, Grid2, IconButto
|
||||||
import BinSensorCard from "bin/BinSensorCard";
|
import BinSensorCard from "bin/BinSensorCard";
|
||||||
import { GetDefaultDateRange } from "common/time/DateRange";
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||||
import { ExtractMoisture } from "grain";
|
import { ExtractMoisture } from "grain";
|
||||||
import { useThemeType } from "hooks";
|
import { useMobile, useThemeType } from "hooks";
|
||||||
import { Bin, Component } from "models";
|
import { Bin, Component } from "models";
|
||||||
import { Ambient } from "models/Ambient";
|
import { Ambient } from "models/Ambient";
|
||||||
import { CO2 } from "models/CO2";
|
import { CO2 } from "models/CO2";
|
||||||
|
|
@ -72,6 +72,7 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
||||||
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
||||||
const [filterNodes, setFilterNodes] = useState(true)
|
const [filterNodes, setFilterNodes] = useState(true)
|
||||||
|
const isMobile = useMobile()
|
||||||
|
|
||||||
//organize the components into the correct 'positions' using the bins preferences
|
//organize the components into the correct 'positions' using the bins preferences
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
|
@ -597,11 +598,12 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
let device = componentDevices.get(controller.key())
|
let device = componentDevices.get(controller.key())
|
||||||
if(device){
|
if(device){
|
||||||
componentAPI.update(device, controller.settings).then(resp => {
|
componentAPI.update(device, controller.settings).then(resp => {
|
||||||
console.log("updated controller")
|
controller.mode = mode // ← update the field the UI reads
|
||||||
|
|
||||||
if (prefType === pond.BinComponent.BIN_COMPONENT_FAN) {
|
if (prefType === pond.BinComponent.BIN_COMPONENT_FAN) {
|
||||||
setFans(prev => [...prev])
|
setFans(prev => prev.map(f => f.key() === controller.key() ? controller : f))
|
||||||
} else {
|
} else {
|
||||||
setHeaters(prev => [...prev])
|
setHeaters(prev => prev.map(h => h.key() === controller.key() ? controller : h))
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log("there was a problem updating the controller")
|
console.log("there was a problem updating the controller")
|
||||||
|
|
@ -693,7 +695,7 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
}}>
|
}}>
|
||||||
{showGraphs ? "Show Cards" : "Show Graphs"}
|
{showGraphs ? "Show Cards" : "Show Graphs"}
|
||||||
</Button>
|
</Button>
|
||||||
{showGraphs &&
|
{showGraphs && !isMobile &&
|
||||||
<TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
|
<TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
|
||||||
}
|
}
|
||||||
{cables.length > 0 &&
|
{cables.length > 0 &&
|
||||||
|
|
@ -719,17 +721,203 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
{assignmentMenu()}
|
{assignmentMenu()}
|
||||||
{sensorCount > 0 &&
|
{sensorCount > 0 &&
|
||||||
<Box>
|
<Box>
|
||||||
<Box display="flex" gap={2} alignItems="center">
|
<Box display="flex" gap={2} alignItems="center" marginBottom={1}>
|
||||||
<Typography sx={{fontWeight: 650, fontSize: 25}}>Sensors</Typography>
|
<Typography sx={{fontWeight: 650, fontSize: 25}}>Sensors</Typography>
|
||||||
{graphControls()}
|
{graphControls()}
|
||||||
</Box>
|
</Box>
|
||||||
|
{showGraphs && isMobile &&
|
||||||
|
<TimeBar updateDateRange={updateDateRange} startDate={startDate} endDate={endDate} />
|
||||||
|
}
|
||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
|
{plenums.map(plenum => {
|
||||||
|
let device = componentDevices.get(plenum.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(plenum.type(), plenum.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={plenum.name()}
|
||||||
|
tag={"Plenum"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(plenum.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(plenum.type());
|
||||||
|
setSelectedComponentKey(plenum.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{tempHumidCard(plenum)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
{pressures.map(pressure => {
|
||||||
|
let device = componentDevices.get(pressure.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(pressure.type(), pressure.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={pressure.name()}
|
||||||
|
tag={"Pressure"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(pressure.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(pressure.type());
|
||||||
|
setSelectedComponentKey(pressure.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{pressureCard(pressure)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{ambient.map(ambient => {
|
||||||
|
let device = componentDevices.get(ambient.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(ambient.type(), ambient.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={ambient.name()}
|
||||||
|
tag={"Ambient"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(ambient.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(ambient.type());
|
||||||
|
setSelectedComponentKey(ambient.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{tempHumidCard(ambient)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
{headspaceTH.map(h => {
|
||||||
|
let device = componentDevices.get(h.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(h.type(), h.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={h.name()}
|
||||||
|
tag={"Headspace"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(h.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(h.type());
|
||||||
|
setSelectedComponentKey(h.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{tempHumidCard(h)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
|
{headspaceCO2.map(co2 => {
|
||||||
|
let device = componentDevices.get(co2.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(co2.type(), co2.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={co2.name()}
|
||||||
|
tag={"Headspace"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(co2.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(co2.type());
|
||||||
|
setSelectedComponentKey(co2.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{co2Card(co2)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{headspaceLidar.map(lidar => {
|
||||||
|
let device = componentDevices.get(lidar.key())
|
||||||
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(lidar.type(), lidar.subType(), themeType)
|
||||||
|
return(
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
<BinSensorGraph
|
||||||
|
icon={icon}
|
||||||
|
title={lidar.name()}
|
||||||
|
tag={"Headspace"}
|
||||||
|
device={device}
|
||||||
|
component={components.get(lidar.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(lidar.type());
|
||||||
|
setSelectedComponentKey(lidar.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
|
{lidarCard(lidar)}
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
})}
|
||||||
{cables.map(cable => {
|
{cables.map(cable => {
|
||||||
let device = componentDevices.get(cable.key())
|
let device = componentDevices.get(cable.key())
|
||||||
if(showGraphs && device){
|
if(showGraphs && device){
|
||||||
let icon = GetComponentIcon(cable.type(), cable.subType(), themeType)
|
let icon = GetComponentIcon(cable.type(), cable.subType(), themeType)
|
||||||
return(
|
return(
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
<BinSensorGraph
|
<BinSensorGraph
|
||||||
icon={icon}
|
icon={icon}
|
||||||
title={cable.name()}
|
title={cable.name()}
|
||||||
|
|
@ -751,165 +939,10 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return(
|
return(
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
{cableCard(cable)}
|
{cableCard(cable)}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)}
|
|
||||||
{plenums.map(plenum => {
|
|
||||||
let device = componentDevices.get(plenum.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
let icon = GetComponentIcon(plenum.type(), plenum.subType(), themeType)
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph
|
|
||||||
icon={icon}
|
|
||||||
title={plenum.name()}
|
|
||||||
tag={"Plenum"}
|
|
||||||
device={device}
|
|
||||||
component={components.get(plenum.key())!}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onMenuClick={(event) => {
|
|
||||||
setComponentUnnassigned(false);
|
|
||||||
setSelectedComponentType(plenum.type());
|
|
||||||
setSelectedComponentKey(plenum.key());
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{tempHumidCard(plenum)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{pressures.map(pressure => {
|
|
||||||
let device = componentDevices.get(pressure.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
let icon = GetComponentIcon(pressure.type(), pressure.subType(), themeType)
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph
|
|
||||||
icon={icon}
|
|
||||||
title={pressure.name()}
|
|
||||||
tag={"Pressure"}
|
|
||||||
device={device}
|
|
||||||
component={components.get(pressure.key())!}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onMenuClick={(event) => {
|
|
||||||
setComponentUnnassigned(false);
|
|
||||||
setSelectedComponentType(pressure.type());
|
|
||||||
setSelectedComponentKey(pressure.key());
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{pressureCard(pressure)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{ambient.map(ambient => {
|
|
||||||
let device = componentDevices.get(ambient.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
let icon = GetComponentIcon(ambient.type(), ambient.subType(), themeType)
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph
|
|
||||||
icon={icon}
|
|
||||||
title={ambient.name()}
|
|
||||||
tag={"Ambient"}
|
|
||||||
device={device}
|
|
||||||
component={components.get(ambient.key())!}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onMenuClick={(event) => {
|
|
||||||
setComponentUnnassigned(false);
|
|
||||||
setSelectedComponentType(ambient.type());
|
|
||||||
setSelectedComponentKey(ambient.key());
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{tempHumidCard(ambient)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
{headspaceTH.map(h => {
|
|
||||||
let device = componentDevices.get(h.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
let icon = GetComponentIcon(h.type(), h.subType(), themeType)
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph
|
|
||||||
icon={icon}
|
|
||||||
title={h.name()}
|
|
||||||
tag={"Headspace"}
|
|
||||||
device={device}
|
|
||||||
component={components.get(h.key())!}
|
|
||||||
startDate={startDate}
|
|
||||||
endDate={endDate}
|
|
||||||
onMenuClick={(event) => {
|
|
||||||
setComponentUnnassigned(false);
|
|
||||||
setSelectedComponentType(h.type());
|
|
||||||
setSelectedComponentKey(h.key());
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{tempHumidCard(h)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
|
|
||||||
{headspaceCO2.map(co2 => {
|
|
||||||
let device = componentDevices.get(co2.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
let icon = GetComponentIcon(co2.type(), co2.subType(), themeType)
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph device={device} component={components.get(co2.key())!} startDate={startDate} endDate={endDate} />
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{co2Card(co2)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{headspaceLidar.map(lidar => {
|
|
||||||
let device = componentDevices.get(lidar.key())
|
|
||||||
if(showGraphs && device){
|
|
||||||
return(
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
<BinSensorGraph device={device} component={components.get(lidar.key())!} startDate={startDate} endDate={endDate} />
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
|
||||||
{lidarCard(lidar)}
|
|
||||||
</Grid2>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -922,14 +955,29 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
{heaters.map(heater => {
|
{heaters.map(heater => {
|
||||||
let device = componentDevices.get(heater.key())
|
let device = componentDevices.get(heater.key())
|
||||||
if(showGraphs && device){
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(heater.type(), heater.subType(), themeType)
|
||||||
return(
|
return(
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
<BinSensorGraph device={device} component={components.get(heater.key())!} startDate={startDate} endDate={endDate} />
|
<BinSensorGraph
|
||||||
|
device={device}
|
||||||
|
icon={icon}
|
||||||
|
title={heater.name()}
|
||||||
|
tag={"Heater"}
|
||||||
|
component={components.get(heater.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(heater.type());
|
||||||
|
setSelectedComponentKey(heater.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
{controllerCard(heater)}
|
{controllerCard(heater)}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
)
|
||||||
|
|
@ -937,14 +985,29 @@ export default function BinSensorsDisplay(props: Props){
|
||||||
{fans.map(fan => {
|
{fans.map(fan => {
|
||||||
let device = componentDevices.get(fan.key())
|
let device = componentDevices.get(fan.key())
|
||||||
if(showGraphs && device){
|
if(showGraphs && device){
|
||||||
|
let icon = GetComponentIcon(fan.type(), fan.subType(), themeType)
|
||||||
return(
|
return(
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
<BinSensorGraph device={device} component={components.get(fan.key())!} startDate={startDate} endDate={endDate} />
|
<BinSensorGraph
|
||||||
|
device={device}
|
||||||
|
icon={icon}
|
||||||
|
title={fan.name()}
|
||||||
|
tag={"Fan"}
|
||||||
|
component={components.get(fan.key())!}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onMenuClick={(event) => {
|
||||||
|
setComponentUnnassigned(false);
|
||||||
|
setSelectedComponentType(fan.type());
|
||||||
|
setSelectedComponentKey(fan.key());
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Grid2 size={{xs: 6, sm: 6, md: 4, lg: 3}}>
|
<Grid2 size={{xs: 12, sm: 6, md: 4, lg: 3}}>
|
||||||
{controllerCard(fan)}
|
{controllerCard(fan)}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { NodeData } from "bin/3dView/Data/BuildNodeData";
|
||||||
import { CableData } from "bin/3dView/Data/BuildCableData";
|
import { CableData } from "bin/3dView/Data/BuildCableData";
|
||||||
import ModeChangeDialog from "bin/conditioning/modeChangeDialog";
|
import ModeChangeDialog from "bin/conditioning/modeChangeDialog";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bin: Bin
|
bin: Bin
|
||||||
|
|
@ -41,6 +42,7 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const [openNodeControls, setOpenNodeControls] = useState(false)
|
const [openNodeControls, setOpenNodeControls] = useState(false)
|
||||||
const [devMap, setDevMap] = useState<Map<number, Device>>(new Map())
|
const [devMap, setDevMap] = useState<Map<number, Device>>(new Map())
|
||||||
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
|
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
|
||||||
|
const isMobile = useMobile()
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
let newMap: Map<number, Device> = new Map()
|
let newMap: Map<number, Device> = new Map()
|
||||||
|
|
@ -65,7 +67,7 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const binModeControl = () => {
|
const binModeControl = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{fontWeight: 650, fontSize: 20}}>{bin.name()}</Typography>
|
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 20}}>{bin.name()}</Typography>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<Select
|
<Select
|
||||||
value={binMode}
|
value={binMode}
|
||||||
|
|
@ -100,7 +102,7 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const heatmapDisplay = () => {
|
const heatmapDisplay = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{fontWeight: 650, fontSize: 20}}>Heatmap Display</Typography>
|
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 20}}>Heatmap Display</Typography>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
{/* <InputLabel>Display</InputLabel> */}
|
{/* <InputLabel>Display</InputLabel> */}
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -160,7 +162,7 @@ export default function bin3dVisualizer(props: Props){
|
||||||
const controllerDisplay = () => {
|
const controllerDisplay = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{fontWeight: 650, fontSize: 20}}>
|
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 20}}>
|
||||||
Controllers
|
Controllers
|
||||||
</Typography>
|
</Typography>
|
||||||
{fans && fans.map(fan => {
|
{fans && fans.map(fan => {
|
||||||
|
|
@ -178,9 +180,9 @@ export default function bin3dVisualizer(props: Props){
|
||||||
py: 1,
|
py: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2">{fan.name()}</Typography>
|
<Typography sx={{fontSize: isMobile ? 13 : 15}}>{fan.name()}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="body2">
|
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 15}}>
|
||||||
{isOn ? "ON" : "OFF"}
|
{isOn ? "ON" : "OFF"}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|
@ -207,9 +209,9 @@ export default function bin3dVisualizer(props: Props){
|
||||||
py: 1,
|
py: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2">{heater.name()}</Typography>
|
<Typography sx={{fontSize: isMobile ? 13 : 15}}>{heater.name()}</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="body2">
|
<Typography sx={{fontWeight: 650, fontSize: isMobile ? 13 : 15}}>
|
||||||
{isOn ? "ON" : "OFF"}
|
{isOn ? "ON" : "OFF"}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|
@ -227,6 +229,63 @@ export default function bin3dVisualizer(props: Props){
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const desktopHUD = () => {
|
||||||
|
return (
|
||||||
|
<Box position={"absolute"} top={20} left={30} height={"80%"} width={"30%"} padding={2}>
|
||||||
|
<Stack direction={"column"} justifyContent={"space-between"} sx={{height: "100%", width: "100%"}}>
|
||||||
|
{binModeControl()}
|
||||||
|
{heatmapDisplay()}
|
||||||
|
{controllerDisplay()}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobileHUD = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* Top-left: Bin Name / Mode */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={12}
|
||||||
|
left={12}
|
||||||
|
width="40%"
|
||||||
|
sx={{ pointerEvents: 'none' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ pointerEvents: 'auto' }}>
|
||||||
|
{binModeControl()}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Top-right: Heatmap Display */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top={12}
|
||||||
|
right={12}
|
||||||
|
width="40%"
|
||||||
|
sx={{ pointerEvents: 'none' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ pointerEvents: 'auto' }}>
|
||||||
|
{heatmapDisplay()}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Bottom-left: Controller Status */}
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
bottom={12}
|
||||||
|
left={12}
|
||||||
|
width="45%"
|
||||||
|
sx={{ pointerEvents: 'none' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ pointerEvents: 'auto' }}>
|
||||||
|
{controllerDisplay()}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{(selectedCable && selectedNode) &&
|
{(selectedCable && selectedNode) &&
|
||||||
|
|
@ -325,15 +384,9 @@ export default function bin3dVisualizer(props: Props){
|
||||||
showMoisture={showMoisture}
|
showMoisture={showMoisture}
|
||||||
// showGrain={showFill}
|
// showGrain={showFill}
|
||||||
// showHotspots={showHotspots}
|
// showHotspots={showHotspots}
|
||||||
xOffset={-150}
|
xOffset={isMobile ? undefined : -150}
|
||||||
/>
|
/>
|
||||||
<Box position={"absolute"} top={20} left={30} height={"80%"} width={"30%"} padding={2}>
|
{isMobile ? mobileHUD() : desktopHUD()}
|
||||||
<Stack direction={"column"} justifyContent={"space-between"} sx={{height: "100%", width: "100%"}}>
|
|
||||||
{binModeControl()}
|
|
||||||
{heatmapDisplay()}
|
|
||||||
{controllerDisplay()}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,8 @@ export default function BinControls(props: Props) {
|
||||||
setControls(controls);
|
setControls(controls);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error("Interaction fetch error:", err)
|
console.error("Interaction fetch error:", err)
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},[linkedComponents, interactionsAPI, componentDevices, as])
|
},[linkedComponents, interactionsAPI, componentDevices, as])
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,36 @@
|
||||||
import { Edit, GppGood, Save } from "@mui/icons-material";
|
import { Edit, GppGood, Info, Save } from "@mui/icons-material";
|
||||||
import { Box, darken, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Theme, Typography } from "@mui/material";
|
import { Box, Button, darken, DialogActions, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Theme, Tooltip, Typography } from "@mui/material";
|
||||||
import { green, grey, orange, red, yellow } from "@mui/material/colors";
|
import { green, grey, orange, red, yellow } from "@mui/material/colors";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import HumidityIcon from "component/HumidityIcon";
|
import HumidityIcon from "component/HumidityIcon";
|
||||||
import TemperatureIcon from "component/TemperatureIcon";
|
import TemperatureIcon from "component/TemperatureIcon";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { Bin } from "models";
|
import { Bin } from "models";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { avg, celsiusToFahrenheit } from "utils";
|
import { avg, celsiusToFahrenheit } from "utils";
|
||||||
|
import BinTableExpanded from "./BinTableExpanded";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
tableStyle: {
|
tableStyle: {
|
||||||
borderCollapse: "collapse",
|
borderCollapse: "separate",
|
||||||
|
borderSpacing: 0, // removes the default gap that "separate" introduces
|
||||||
},
|
},
|
||||||
headerCellStyle: {
|
headerCellStyle: {
|
||||||
fontWeight: 650,
|
fontWeight: 650,
|
||||||
backgroundColor: darken(theme.palette.background.paper, 0.2),
|
backgroundColor: darken(theme.palette.background.paper, 0.2),
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
border: "1px solid black"
|
borderTop: "1px solid black",
|
||||||
|
borderLeft: "1px solid black",
|
||||||
|
borderBottom: "1px solid black",
|
||||||
|
"&:last-child": {
|
||||||
|
borderRight: "1px solid black",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cellStyle: {
|
cellStyle: {
|
||||||
padding: 2,
|
padding: 2,
|
||||||
|
|
@ -30,11 +39,6 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
border: "1px solid black",
|
border: "1px solid black",
|
||||||
|
|
||||||
},
|
|
||||||
thresholdCell: {
|
|
||||||
fontWeight: 650,
|
|
||||||
fontSize: 15,
|
|
||||||
textAlign: "center",
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -62,9 +66,19 @@ export default function BinTableView(props: Props) {
|
||||||
const [tempEntry, setTempEntry] = useState("")
|
const [tempEntry, setTempEntry] = useState("")
|
||||||
const [moistureEntry, setMoistureEntry] = useState("")
|
const [moistureEntry, setMoistureEntry] = useState("")
|
||||||
const [moistureTarget, setMoistureTarget] = useState(0)
|
const [moistureTarget, setMoistureTarget] = useState(0)
|
||||||
|
const [openExpandedCables, setOpenExpandedCables] = useState(false)
|
||||||
|
const mobileView = useMobile()
|
||||||
const inBounds = green[600]
|
const inBounds = green[600]
|
||||||
const caution = yellow[800]
|
const caution = yellow[800]
|
||||||
const warning = red[700]
|
const warning = red[700]
|
||||||
|
const headerRow1Ref = useRef<HTMLTableRowElement>(null)
|
||||||
|
const [firstRowHeight, setFirstRowHeight] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (headerRow1Ref.current) {
|
||||||
|
setFirstRowHeight(headerRow1Ref.current.getBoundingClientRect().height)
|
||||||
|
}
|
||||||
|
}, [enterTemp, enterMoisture]) // re-measure when the row height might change due to TextField appearing
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
let val = bin.targetTemp()
|
let val = bin.targetTemp()
|
||||||
|
|
@ -250,15 +264,54 @@ export default function BinTableView(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expandedTableDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={openExpandedCables} onClose={()=>{setOpenExpandedCables(false)}}>
|
||||||
|
<BinTableExpanded
|
||||||
|
cables={bin.status.grainCables}
|
||||||
|
targetMoisture={bin.targetMoisture()}
|
||||||
|
targetTemp={bin.targetTemp()}
|
||||||
|
inBounds={inBounds}
|
||||||
|
caution={caution}
|
||||||
|
warning={warning}
|
||||||
|
/>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={()=> {setOpenExpandedCables(false)}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box padding={2}>
|
<Box padding={2} sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||||
|
{expandedTableDialog()}
|
||||||
{/* Level Table */}
|
{/* Level Table */}
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
|
<Typography>Grain Table</Typography>
|
||||||
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenExpandedCables(true)
|
||||||
|
}}>
|
||||||
|
Show All Cables
|
||||||
|
</Button>
|
||||||
|
<Tooltip title="testing">
|
||||||
|
<Info />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* this box is to make the whole table scrollable */}
|
||||||
|
<Box sx={{ overflowY: "auto", flex: 1, minHeight: 0 }}>
|
||||||
<Table className={classes.tableStyle}>
|
<Table className={classes.tableStyle}>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow ref={headerRow1Ref}>
|
||||||
<TableCell className={classes.thresholdCell}>
|
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
<GppGood />
|
<GppGood />
|
||||||
<Typography fontWeight={650}>
|
<Typography fontWeight={650}>
|
||||||
|
|
@ -266,7 +319,7 @@ export default function BinTableView(props: Props) {
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.thresholdCell}>
|
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
<TemperatureIcon heightWidth={30}/>
|
<TemperatureIcon heightWidth={30}/>
|
||||||
{enterTemp ?
|
{enterTemp ?
|
||||||
|
|
@ -296,7 +349,7 @@ export default function BinTableView(props: Props) {
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.thresholdCell}>
|
<TableCell className={classes.headerCellStyle} sx={{ top: 0, position: "sticky", zIndex: 3 }}>
|
||||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
<HumidityIcon height={30} width={20}/>
|
<HumidityIcon height={30} width={20}/>
|
||||||
{enterMoisture ?
|
{enterMoisture ?
|
||||||
|
|
@ -328,11 +381,12 @@ export default function BinTableView(props: Props) {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className={classes.headerCellStyle}>Level</TableCell>
|
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Level</TableCell>
|
||||||
<TableCell className={classes.headerCellStyle}>Temperature</TableCell>
|
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Temperature</TableCell>
|
||||||
<TableCell className={classes.headerCellStyle}>Moisture</TableCell>
|
<TableCell className={classes.headerCellStyle} sx={{ top: firstRowHeight, position: "sticky", zIndex: 3 }}>Moisture</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{binLevels.map((level, i) => (
|
{binLevels.map((level, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
|
|
@ -340,10 +394,46 @@ export default function BinTableView(props: Props) {
|
||||||
{levelDisplay(level)}
|
{levelDisplay(level)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>TESTING</TableCell>
|
||||||
|
<TableCell>SCROLLING</TableCell>
|
||||||
|
<TableCell>ROWS</TableCell>
|
||||||
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Box>
|
||||||
{/* Table Legend */}
|
{/* Table Legend */}
|
||||||
<Box display="flex" alignItems="center" justifyContent={"space-between"} marginTop={2}>
|
<Box display="flex" alignItems="center" justifyContent="space-between" marginTop={2} sx={{ flexShrink: 0 }}>
|
||||||
<Box display="flex" alignItems="center" gap={1}>
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
<LegendSwatch color={inBounds} />
|
<LegendSwatch color={inBounds} />
|
||||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { Pressure } from "models/Pressure";
|
||||||
import { CO2 } from "models/CO2";
|
import { CO2 } from "models/CO2";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { quack } from "protobuf-ts/quack";
|
import { quack } from "protobuf-ts/quack";
|
||||||
import { Box, Drawer, Grid2 as Grid, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Theme, Tooltip } from "@mui/material";
|
import { Box, Drawer, Grid2 as Grid, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tab, Tabs, Theme, Tooltip } from "@mui/material";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import NotesIcon from "@mui/icons-material/Notes";
|
import NotesIcon from "@mui/icons-material/Notes";
|
||||||
import TasksIcon from "products/Construction/TasksIcon";
|
import TasksIcon from "products/Construction/TasksIcon";
|
||||||
|
|
@ -22,10 +22,34 @@ import BindaptIcon from "products/Bindapt/BindaptIcon";
|
||||||
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||||
import BinActions from "bin/BinActions";
|
import BinActions from "bin/BinActions";
|
||||||
import BinSummary from "bin/binSummary/BinSummary";
|
import BinSummary from "bin/binSummary/BinSummary";
|
||||||
|
import { Close } from "@mui/icons-material";
|
||||||
|
import React from "react";
|
||||||
|
import BinHistory from "bin/BinHistory";
|
||||||
|
import Chat from "chat/Chat";
|
||||||
|
import TaskViewer from "tasks/TaskViewer";
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanelMine(props: TabPanelProps) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
{...other}>
|
||||||
|
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
const themeType = theme.palette.mode;
|
const themeType = theme.palette.mode;
|
||||||
const activeBG = theme.palette.secondary.main;
|
|
||||||
return ({
|
return ({
|
||||||
spacer: {
|
spacer: {
|
||||||
width: "32px",
|
width: "32px",
|
||||||
|
|
@ -46,6 +70,14 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
menuPaper: {
|
menuPaper: {
|
||||||
borderRadius: "20px"
|
borderRadius: "20px"
|
||||||
},
|
},
|
||||||
|
drawerPaperDesktop: {
|
||||||
|
height: "100%",
|
||||||
|
width: "30%"
|
||||||
|
},
|
||||||
|
drawerPaperMobile: {
|
||||||
|
height: "50%",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -76,6 +108,7 @@ export default function BinV2(){
|
||||||
const [fans, setFans] = useState<Controller[]>([]);
|
const [fans, setFans] = useState<Controller[]>([]);
|
||||||
const [compositionNameMap, setCompositionNameMap] = useState<Map<string, string>>(new Map());
|
const [compositionNameMap, setCompositionNameMap] = useState<Map<string, string>>(new Map());
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const [noteTab, setNoteTab] = useState(0)
|
||||||
|
|
||||||
|
|
||||||
const [componentDevices, setComponentDevices] = useState<Map<string, number>>(
|
const [componentDevices, setComponentDevices] = useState<Map<string, number>>(
|
||||||
|
|
@ -438,20 +471,62 @@ export default function BinV2(){
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
|
setNoteTab(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//DRAWERS
|
//DRAWERS
|
||||||
const noteDrawer = () => {
|
const noteDrawer = () => {
|
||||||
return (
|
return (
|
||||||
<Drawer open={noteDrawerOpen}>
|
<Drawer
|
||||||
|
open={noteDrawerOpen}
|
||||||
|
anchor={isMobile ? "bottom" : "right"}
|
||||||
|
classes={{ paper: isMobile ? classes.drawerPaperMobile : classes.drawerPaperDesktop }}
|
||||||
|
onClose={() => setNoteDrawerOpen(false)}>
|
||||||
|
<IconButton style={{ width: 50 }} onClick={() => setNoteDrawerOpen(false)}>
|
||||||
|
<Close />
|
||||||
|
</IconButton>
|
||||||
|
<Tabs
|
||||||
|
value={noteTab}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
onChange={handleChange}
|
||||||
|
aria-label="disabled tabs example"
|
||||||
|
// centered={!(isMobile || displayMobile)}
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto"
|
||||||
|
//marginTop: isMobile || displayMobile || drawer ? "-3rem" : "0.25rem"
|
||||||
|
}}>
|
||||||
|
<Tab label="Notes" />
|
||||||
|
<Tab label="History" />
|
||||||
|
</Tabs>
|
||||||
|
<TabPanelMine value={noteTab} index={0}>
|
||||||
|
{/* <Box height={isMobile || displayMobile ? "80vh" : "90vh"} padding={2}> */}
|
||||||
|
<Chat parent={binID} parentType={"bin"} type={pond.NoteType.NOTE_TYPE_BIN} />
|
||||||
|
{/* </Box> */}
|
||||||
|
</TabPanelMine>
|
||||||
|
<TabPanelMine value={noteTab} index={1}>
|
||||||
|
<BinHistory drawer binID={binID} />
|
||||||
|
</TabPanelMine>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskDrawer = () => {
|
const taskDrawer = () => {
|
||||||
return (
|
return (
|
||||||
<Drawer open={taskDrawerOpen}>
|
<Drawer
|
||||||
|
style={{ zIndex: 1099 }}
|
||||||
|
open={taskDrawerOpen}
|
||||||
|
anchor={isMobile ? "bottom" : "right"}
|
||||||
|
classes={{ paper: isMobile ? classes.drawerPaperMobile : classes.drawerPaperDesktop }}
|
||||||
|
onClose={() => setTaskDrawerOpen(false)}>
|
||||||
|
<Box>
|
||||||
|
<IconButton style={{ width: 50 }} onClick={() => setTaskDrawerOpen(false)}>
|
||||||
|
<Close />
|
||||||
|
</IconButton>
|
||||||
|
<TaskViewer drawerView keys={[binID]} types={["bin"]} overlayButton />
|
||||||
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue