finished work on the table, made the legend, and hooked up the edits for the temp/moisture targets
This commit is contained in:
parent
0f4d706e76
commit
a65dbe8820
7 changed files with 282 additions and 35 deletions
|
|
@ -43,7 +43,7 @@ export default function GrainCable(props: Props) {
|
|||
<React.Fragment>
|
||||
<Cylinder geometry={geometry} position={calculateCenter()} opacity={0.99} renderOrder={renderOrder} depthWrite={false} depthTest={false}/>
|
||||
{nodes.map((node, i) => (
|
||||
<CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} targetMoisture={bin.targetMoisture()} upperThreshold={bin.upperTempThreshold()}/>
|
||||
<CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} targetMoisture={bin.targetMoisture()} upperThreshold={bin.targetTemp()}/>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function NodePointCloud(props: Props) {
|
|||
.filter(n => n.inGrain && !n.excluded)
|
||||
.map(n => {
|
||||
const lower = bin.lowerTempThreshold();
|
||||
const upper = bin.upperTempThreshold();
|
||||
const upper = bin.targetTemp();
|
||||
|
||||
if (n.celcius >= lower && n.celcius <= upper) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ export default function TempHeatMap({ bin, nodes, flatMaxY }: Props) {
|
|||
const binRadius = bin.diameter() / 2;
|
||||
const sidewallHeight = bin.sidewallHeight();
|
||||
const hopperHeight = bin.hopperHeight() ?? 0;
|
||||
const upperThreshold = bin.upperTempThreshold();
|
||||
const upperThreshold = bin.targetTemp();
|
||||
|
||||
const sidewallBaseY = -sidewallHeight / 2;
|
||||
const hopperTipY = sidewallBaseY - hopperHeight;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { useEffect, useState } from "react";
|
|||
import { Controller } from "models/Controller";
|
||||
import { GrainCable } from "models/GrainCable";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import BinDetails from "./components/binDetails";
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
|
|
@ -156,7 +157,7 @@ export default function BinSummary(props: Props){
|
|||
</Card>
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 size={isMobile ? 12 : 7}>
|
||||
<Card raised>
|
||||
<Card raised sx={{height: 600}}>
|
||||
<Bin3dVisualizer
|
||||
bin={bin}
|
||||
fans={fans}
|
||||
|
|
@ -171,8 +172,8 @@ export default function BinSummary(props: Props){
|
|||
</Card>
|
||||
</Grid2>
|
||||
<Grid2 size={isMobile ? 12 : 5}>
|
||||
<Card raised sx={{height: "100%"}}>
|
||||
<BinTableView bin={bin}/>
|
||||
<Card raised sx={{height: 600}}>
|
||||
<BinDetails bin={bin} updateBinCallback={updateBinCallback}/>
|
||||
</Card>
|
||||
</Grid2>
|
||||
<Grid2 size={12}>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,65 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { Box, Tab, Tabs } from "@mui/material";
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
import BinTableView from "./binTableView";
|
||||
import { Bin } from "models";
|
||||
|
||||
interface Props {
|
||||
|
||||
bin: Bin
|
||||
updateBinCallback?: (bin: Bin) => void
|
||||
}
|
||||
|
||||
export default function BinDetails (props: Props) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BinDetails (props: Props) {
|
||||
const {bin, updateBinCallback} = props
|
||||
const [currentTab, setCurrentTab] = useState(0)
|
||||
|
||||
//this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={(_, value) => setCurrentTab(value)}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="fullWidth">
|
||||
<Tab label={"Table View"} value={0} />
|
||||
<Tab label={"Alerts"} value={1} />
|
||||
<Tab label={"Interactions"} value={2} />
|
||||
<Tab label={"Analysis"} value={3} />
|
||||
</Tabs>
|
||||
<TabPanelMine value={currentTab} index={0}>
|
||||
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={1}>
|
||||
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={2}>
|
||||
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={currentTab} index={3}>
|
||||
|
||||
</TabPanelMine>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +1,47 @@
|
|||
import { Box, Table, TableBody, TableCell, TableHead, TableRow } from "@mui/material";
|
||||
import { green, grey, orange, red } from "@mui/material/colors";
|
||||
import { Edit, GppGood, Save } from "@mui/icons-material";
|
||||
import { Box, darken, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Theme, Typography } from "@mui/material";
|
||||
import { green, grey, orange, red, yellow } from "@mui/material/colors";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import HumidityIcon from "component/HumidityIcon";
|
||||
import TemperatureIcon from "component/TemperatureIcon";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Bin } from "models";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { avg, celsiusToFahrenheit } from "utils";
|
||||
|
||||
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",
|
||||
|
||||
},
|
||||
thresholdCell: {
|
||||
fontWeight: 650,
|
||||
fontSize: 15,
|
||||
textAlign: "center",
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
interface Props {
|
||||
bin: Bin
|
||||
updateBinCallback?: (bin: Bin) => void
|
||||
}
|
||||
|
||||
interface BinLevel {
|
||||
|
|
@ -20,8 +53,29 @@ interface BinLevel {
|
|||
|
||||
|
||||
export default function BinTableView(props: Props) {
|
||||
const {bin} = props
|
||||
const classes = useStyles()
|
||||
const {bin, updateBinCallback} = props
|
||||
const [{user}] = useGlobalState()
|
||||
const [enterTemp, setEnterTemp] = useState(false)
|
||||
const [enterMoisture, setEnterMoisture] = useState(false)
|
||||
const [tempTarget, setTempTarget] = useState(0)
|
||||
const [tempEntry, setTempEntry] = useState("")
|
||||
const [moistureEntry, setMoistureEntry] = useState("")
|
||||
const [moistureTarget, setMoistureTarget] = useState(0)
|
||||
const inBounds = green[600]
|
||||
const caution = yellow[800]
|
||||
const warning = red[700]
|
||||
|
||||
useEffect(()=>{
|
||||
let val = bin.targetTemp()
|
||||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
val = bin.targetTemp() * (9/5) + 32
|
||||
}
|
||||
setTempTarget(val)
|
||||
setTempEntry(val.toFixed(2))
|
||||
setMoistureTarget(bin.targetMoisture())
|
||||
setMoistureEntry(bin.targetMoisture().toFixed(2))
|
||||
},[bin])
|
||||
|
||||
const binLevels = useMemo(() => {
|
||||
const cables = bin.status.grainCables
|
||||
|
|
@ -89,21 +143,20 @@ export default function BinTableView(props: Props) {
|
|||
let moistureDisplay = "--"
|
||||
let lvlTemp
|
||||
let lvlMoisture
|
||||
let tempColour: string = grey[600]
|
||||
let moistureColour: string = grey[600]
|
||||
let tempColour: string = ""
|
||||
let moistureColour: string = ""
|
||||
|
||||
//determine the temp
|
||||
if(level.grainTemps.length > 0){
|
||||
console.log(level.grainTemps)
|
||||
lvlTemp = avg(level.grainTemps)
|
||||
//if the average temp is 5 degrees above the bins threshold
|
||||
if(lvlTemp > (bin.upperTempThreshold() + 5)){
|
||||
tempColour = red[700]
|
||||
}else if(lvlTemp > bin.upperTempThreshold()){
|
||||
if(lvlTemp > (bin.targetTemp() + 5)){
|
||||
tempColour = warning
|
||||
}else if(lvlTemp > bin.targetTemp()){
|
||||
//if the average temp is greater than 5 degrees over
|
||||
tempColour = orange[600]
|
||||
tempColour = caution
|
||||
}else {
|
||||
tempColour = green[300]
|
||||
tempColour = inBounds
|
||||
}
|
||||
}else if (level.airTemps.length > 0){
|
||||
lvlTemp = avg(level.airTemps)
|
||||
|
|
@ -112,19 +165,19 @@ export default function BinTableView(props: Props) {
|
|||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
lvlTemp = celsiusToFahrenheit(lvlTemp)
|
||||
}
|
||||
tempDisplay = lvlTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " F" : " C")
|
||||
tempDisplay = lvlTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")
|
||||
}
|
||||
|
||||
//determine the moisture to show
|
||||
if(level.grainMoistures.length > 0){
|
||||
lvlMoisture = avg(level.grainMoistures)
|
||||
if(lvlMoisture > (bin.targetMoisture() + 1.5)){
|
||||
moistureColour = red[700]
|
||||
moistureColour = warning
|
||||
}else if(lvlMoisture > bin.targetMoisture()){
|
||||
//if the average temp is greater than 5 degrees over
|
||||
moistureColour = orange[600]
|
||||
moistureColour = caution
|
||||
}else {
|
||||
moistureColour = green[300]
|
||||
moistureColour = inBounds
|
||||
}
|
||||
}else if (level.airMoistures.length > 0){
|
||||
lvlMoisture = avg(level.airMoistures)
|
||||
|
|
@ -134,33 +187,177 @@ export default function BinTableView(props: Props) {
|
|||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableCell sx={{color: "black", fontWeight: 650, fontSize: 17, textAlign: "center", backgroundColor: tempColour}}>{tempDisplay}</TableCell>
|
||||
<TableCell sx={{color: "black", fontWeight: 650, fontSize: 17, textAlign: "center", backgroundColor: moistureColour}}>{moistureDisplay}</TableCell>
|
||||
<TableCell padding="none" className={classes.cellStyle} sx={{backgroundColor: tempColour}}>{tempDisplay}</TableCell>
|
||||
<TableCell padding="none" className={classes.cellStyle} sx={{backgroundColor: moistureColour}}>{moistureDisplay}</TableCell>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
let temp = tempTarget
|
||||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
//convert to c
|
||||
temp = (temp - 32) * (5 / 9);
|
||||
|
||||
}
|
||||
let clone = cloneDeep(bin)
|
||||
if(clone.settings.inventory && updateBinCallback){
|
||||
clone.settings.inventory.targetTemperature = temp
|
||||
clone.settings.inventory.targetMoisture = moistureTarget
|
||||
updateBinCallback(clone)
|
||||
}
|
||||
}
|
||||
|
||||
const tempThreshDisplay = () => {
|
||||
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
||||
return tempTarget.toFixed(2) + " °F"
|
||||
}
|
||||
return tempTarget.toFixed(2) + " °C"
|
||||
}
|
||||
|
||||
const LegendSwatch = ({ color }: { color: string }) => (
|
||||
<Box sx={{ width: 40, height: 20, backgroundColor: color, borderRadius: 1, flexShrink: 0 }} />
|
||||
)
|
||||
|
||||
|
||||
const updateTempEntry = () => {
|
||||
return (
|
||||
<TextField
|
||||
value={tempEntry}
|
||||
onChange={e => {
|
||||
let val = parseFloat(e.target.value)
|
||||
if(!isNaN(val)){
|
||||
setTempTarget(val)
|
||||
}
|
||||
setTempEntry(e.target.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const updateMoistureEntry = () => {
|
||||
return (
|
||||
<TextField
|
||||
value={moistureEntry}
|
||||
onChange={e => {
|
||||
let val = parseFloat(e.target.value)
|
||||
if(!isNaN(val)){
|
||||
setMoistureTarget(val)
|
||||
}
|
||||
setMoistureEntry(e.target.value)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box padding={2}>
|
||||
<Table>
|
||||
{/* Level Table */}
|
||||
<Table className={classes.tableStyle}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 650, fontSize: 20, textAlign: "center"}}>Level</TableCell>
|
||||
<TableCell sx={{ fontWeight: 650, fontSize: 20, textAlign: "center"}}>Temperature</TableCell>
|
||||
<TableCell sx={{ fontWeight: 650, fontSize: 20, textAlign: "center"}}>Moisture</TableCell>
|
||||
<TableCell className={classes.thresholdCell}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<GppGood />
|
||||
<Typography fontWeight={650}>
|
||||
Targets
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell className={classes.thresholdCell}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<TemperatureIcon heightWidth={30}/>
|
||||
{enterTemp ?
|
||||
updateTempEntry()
|
||||
:
|
||||
<Typography fontWeight={650}>
|
||||
{tempThreshDisplay()}
|
||||
</Typography>
|
||||
}
|
||||
{enterTemp ?
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterTemp(false)
|
||||
//and update the bin with the new temp target
|
||||
update()
|
||||
}}
|
||||
>
|
||||
<Save />
|
||||
</IconButton>
|
||||
:
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterTemp(true)
|
||||
}}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell className={classes.thresholdCell}>
|
||||
<Box justifyContent={"center"} sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<HumidityIcon height={30} width={20}/>
|
||||
{enterMoisture ?
|
||||
updateMoistureEntry()
|
||||
:
|
||||
<Typography fontWeight={650} sx={{marginLeft: "10px"}}>
|
||||
{bin.targetMoisture()}%
|
||||
</Typography>
|
||||
}
|
||||
{enterMoisture ?
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterMoisture(false)
|
||||
//and update the bin with the new temp target
|
||||
update()
|
||||
}}
|
||||
>
|
||||
<Save />
|
||||
</IconButton>
|
||||
:
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setEnterMoisture(true)
|
||||
}}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className={classes.headerCellStyle}>Level</TableCell>
|
||||
<TableCell className={classes.headerCellStyle}>Temperature</TableCell>
|
||||
<TableCell className={classes.headerCellStyle}>Moisture</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{binLevels.map((level, i) => (
|
||||
<TableRow>
|
||||
<TableCell sx={{color: "gray", fontWeight: 650, fontSize: 17, textAlign: "center"}}>{binLevels.length - i}</TableCell>
|
||||
<TableCell padding="none" className={classes.cellStyle}>{binLevels.length - i}</TableCell>
|
||||
{levelDisplay(level)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{/* Table Legend */}
|
||||
<Box display="flex" alignItems="center" justifyContent={"space-between"} marginTop={2}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<LegendSwatch color={inBounds} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>On or below target</Typography>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<LegendSwatch color={caution} />
|
||||
<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}>
|
||||
<LegendSwatch color={warning} />
|
||||
<Typography sx={{fontWeight: 600, fontSize: 10}}>{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "+20 °F" : "+10 °C"} above target</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue