some tweaks to the 3d bin and nodes, finished up the table view

This commit is contained in:
csawatzky 2026-05-06 13:08:04 -06:00
parent 325d3ea7a0
commit 619f9b7bf4
9 changed files with 291 additions and 146 deletions

View file

@ -21,7 +21,7 @@ export interface NodeData {
} }
export function BuildNodeData(cables: CableData[]): NodeData[] { export function BuildNodeData(cables: CableData[]): NodeData[] {
const nodeRadius = 20 const nodeRadius = 15
let nodeData: NodeData[] = [] let nodeData: NodeData[] = []
cables.forEach((cable, cableIndex) => { cables.forEach((cable, cableIndex) => {

View file

@ -14,6 +14,7 @@ import { pond } from "protobuf-ts/pond";
import GrainCableFill from "../Systems/Inventory/GrainCableFill"; import GrainCableFill from "../Systems/Inventory/GrainCableFill";
import TempHeatMap from "../Systems/Heatmap/TempHeatMap"; import TempHeatMap from "../Systems/Heatmap/TempHeatMap";
import NodePointCloud from "../Systems/Heatmap/NodePointCloud"; import NodePointCloud from "../Systems/Heatmap/NodePointCloud";
import { Box } from "@mui/material";
interface Props { interface Props {
bin: Bin bin: Bin
@ -35,8 +36,6 @@ interface Props {
export default function Bin3dView(props: Props) { export default function Bin3dView(props: Props) {
const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture } = props const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture } = props
const binCenter = useMemo(() => new Vector3(0, 0, 0), []);
// Compute the initial camera radius so the full bin fits in frame. // Compute the initial camera radius so the full bin fits in frame.
// Uses the perspective camera FOV (default 75°) with a padding factor. // Uses the perspective camera FOV (default 75°) with a padding factor.
// Both the diameter and total height are considered so neither is clipped. // Both the diameter and total height are considered so neither is clipped.
@ -129,7 +128,6 @@ export default function Bin3dView(props: Props) {
cableData={cableData} cableData={cableData}
nodeData={nodeData} nodeData={nodeData}
bin={bin} bin={bin}
binCenter={binCenter}
onNodeClick={(node, cable) => { onNodeClick={(node, cable) => {
if (nodeClick) nodeClick(node, cable) if (nodeClick) nodeClick(node, cable)
}} }}

View file

@ -1,15 +1,13 @@
import GrainCable from "./GrainCable"; import GrainCable from "./GrainCable";
import { Vector3 } from "three";
import { Bin } from "models"; import { Bin } from "models";
import React from "react"; import React from "react";
import { CableData } from "../../Data/BuildCableData"; import { CableData } from "../../Data/BuildCableData";
import { NodeData } from "../../Data/BuildNodeData"; import { NodeData } from "../../Data/BuildNodeData";
interface Props { interface Props {
cableData: CableData[] cableData: CableData[],
nodeData: NodeData[] nodeData: NodeData[],
bin: Bin bin: Bin,
binCenter: Vector3,
renderOrder?: number, renderOrder?: number,
displayTemp?: boolean, displayTemp?: boolean,
displayMoisture?: boolean, displayMoisture?: boolean,
@ -17,7 +15,7 @@ interface Props {
} }
export default function BinCables(props: Props){ export default function BinCables(props: Props){
const {bin, binCenter, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props const {bin, cableData, nodeData, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
return ( return (
<React.Fragment> <React.Fragment>
{cableData.map((cable, i) => { {cableData.map((cable, i) => {
@ -29,7 +27,6 @@ export default function BinCables(props: Props){
cable={cable} cable={cable}
nodes={cableNodes} nodes={cableNodes}
bin={bin} bin={bin}
binCenter={binCenter}
displayTemp={displayTemp} displayTemp={displayTemp}
displayMoisture={displayMoisture} displayMoisture={displayMoisture}
onNodeClick={(node) => { onNodeClick={(node) => {

View file

@ -1,21 +1,18 @@
import Sphere, { SphereGeometry } from "3dModels/Shapes/3D/Sphere" import Sphere, { SphereGeometry } from "3dModels/Shapes/3D/Sphere"
import { useFrame, useThree } from "@react-three/fiber" import { useFrame, useThree } from "@react-three/fiber"
import { Billboard, Text } from "@react-three/drei" import { Text } from "@react-three/drei"
import React, { useMemo, useState } from "react" import React, { useMemo } from "react"
import { Euler, Group, Vector3 } from "three" import { Euler, Group, Vector3 } from "three"
import { describeMeasurement } from "pbHelpers/MeasurementDescriber" import { green, grey, orange, red } from "@mui/material/colors";
import { quack } from "protobuf-ts/quack"
import { useGlobalState } from "providers" import { useGlobalState } from "providers"
import { pond } from "protobuf-ts/pond" import { pond } from "protobuf-ts/pond"
import Ring, { RingGeometry } from "3dModels/Shapes/3D/Ring" import Ring, { RingGeometry } from "3dModels/Shapes/3D/Ring"
import { NodeData } from "../../Data/BuildNodeData" import { NodeData } from "../../Data/BuildNodeData"
import { TempToColour } from "bin/3dView/utils/tempToColour"
interface Props { interface Props {
node: NodeData node: NodeData
binCenter: Vector3
lowerThreshold: number
upperThreshold: number, upperThreshold: number,
targetMoisture: number,
renderOrder?: number, renderOrder?: number,
displayTemp?: boolean, displayTemp?: boolean,
displayMoisture?: boolean, displayMoisture?: boolean,
@ -23,33 +20,31 @@ interface Props {
} }
export default function CableNode(props: Props) { export default function CableNode(props: Props) {
const { node, binCenter, lowerThreshold, upperThreshold, onNodeClick, renderOrder, displayTemp, displayMoisture } = props const { node, upperThreshold, targetMoisture, onNodeClick, renderOrder, displayTemp, displayMoisture } = props
const { camera } = useThree(); const { camera } = useThree();
const [{ user }] = useGlobalState(); const [{ user }] = useGlobalState();
// const [showLabel, setShowLabel] = useState(false); // const [showLabel, setShowLabel] = useState(false);
const labelGroupRef = React.useRef<Group>(null); const ROW_HEIGHT = 45;
const ROW_HEIGHT = 35; const PADDING = 15;
const PADDING = 20; const LABEL_WIDTH = 220;
const flagRef = React.useRef<Group>(null);
//this can be used to control label visibility through the cameras zoom level useFrame(() => {
// useFrame(() => { if (!flagRef.current || !showLabel) return;
// const distance = camera.position.distanceTo(binCenter)
// Get camera's right direction in world space
// const SHOW_DISTANCE = 15; const cameraRight = new Vector3();
// const HIDE_DISTANCE = 16; camera.getWorldDirection(cameraRight); // forward
const up = new Vector3(0, 1, 0);
// if (!showLabel && distance < SHOW_DISTANCE) { cameraRight.crossVectors(cameraRight, up).normalize(); // right = forward × up
// setShowLabel(true);
// } else if (showLabel && distance > HIDE_DISTANCE) { const pos = node.position.clone();
// setShowLabel(false); pos.addScaledVector(cameraRight, LABEL_WIDTH / 2); // offset right in screen space
// } pos.addScaledVector(camera.position.clone().sub(node.position).normalize(), 1); // nudge toward camera
// const scale = distance * 0.07; flagRef.current.position.copy(pos);
flagRef.current.quaternion.copy(camera.quaternion);
// if (labelGroupRef.current) { });
// labelGroupRef.current.scale.set(scale, scale, 1);
// }
// });
const tempDisplay = useMemo(() => { const tempDisplay = useMemo(() => {
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
@ -63,7 +58,7 @@ export default function CableNode(props: Props) {
if (node.excluded) { if (node.excluded) {
if (!anyToggleOn) return []; if (!anyToggleOn) return [];
return [{ text: "Excluded", color: "red" }]; return [{ text: "Excluded", color: grey[700] }];
} }
const r: { text: string; color: string }[] = []; const r: { text: string; color: string }[] = [];
@ -72,7 +67,9 @@ export default function CableNode(props: Props) {
if (displayTemp && node.celcius !== 0) { if (displayTemp && node.celcius !== 0) {
r.push({ r.push({
text: tempDisplay, text: tempDisplay,
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour() // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE).colour()
//will be green when below threshold, orange when it goeas above but by less than 5 degrees, and red if more than 5 over
color: node.celcius > upperThreshold ? node.celcius > upperThreshold + 5 ? red[700] : orange[800]: green[800]
}); });
} }
@ -81,21 +78,25 @@ export default function CableNode(props: Props) {
if (node.moisture && node.moisture > 0) { if (node.moisture && node.moisture > 0) {
r.push({ r.push({
text: `${node.moisture.toFixed(2)}% EMC`, text: `${node.moisture.toFixed(2)}% EMC`,
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour() // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour()
color: node.moisture > targetMoisture ? node.moisture > targetMoisture + 0.5 ? red[700] : orange[800]: green[800]
}); });
} else if (node.humidity && node.humidity > 0) { }
r.push({ //apparently we dont want to display humidity at all
text: `${node.humidity.toFixed(2)}%`, // else if (node.humidity && node.humidity > 0) {
color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour() // r.push({
}); // text: `${node.humidity.toFixed(2)}%`,
} // // color: describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT).colour()
// color: "black"
// });
// }
} }
const hasData = r.length > 0; const hasData = r.length > 0;
// Only show top node if there's actual data // Only show top node if there's actual data
if (node.topNode && hasData) { if (node.topNode && hasData) {
r.unshift({ text: "Top Node", color: "yellow" }); r.unshift({ text: "Top Node", color: grey[700] });
} }
return r; return r;
@ -117,12 +118,8 @@ export default function CableNode(props: Props) {
const topNodeRotation = React.useMemo(() => new Euler(-Math.PI / 2, 0, 0), []); const topNodeRotation = React.useMemo(() => new Euler(-Math.PI / 2, 0, 0), []);
const labelPosition = node.position.clone();
const labelHeight = rows.length * ROW_HEIGHT + PADDING; const labelHeight = rows.length * ROW_HEIGHT + PADDING;
const dir = camera.position.clone().sub(node.position).normalize();
labelPosition.add(dir.multiplyScalar(1));
const getRowY = (index: number) => { const getRowY = (index: number) => {
const totalHeight = rows.length * ROW_HEIGHT; const totalHeight = rows.length * ROW_HEIGHT;
return totalHeight / 2 - (index * ROW_HEIGHT) - ROW_HEIGHT / 2; return totalHeight / 2 - (index * ROW_HEIGHT) - ROW_HEIGHT / 2;
@ -130,9 +127,7 @@ export default function CableNode(props: Props) {
const nodeColour = () => { const nodeColour = () => {
if (node.excluded) return "grey"; if (node.excluded) return "grey";
if (!node.inGrain) return "white"; return "white";
const c = TempToColour(node, lowerThreshold, upperThreshold, "node");
return c !== null ? c.getStyle() : "white";
}; };
const handleClick = (e: any) => { const handleClick = (e: any) => {
@ -144,49 +139,46 @@ export default function CableNode(props: Props) {
return ( return (
<React.Fragment> <React.Fragment>
{(!node.inGrain || !showLabel) && <React.Fragment>
<React.Fragment> <Sphere
<Sphere geometry={geometry}
geometry={geometry} position={node.position}
position={node.position} colour={nodeColour()}
colour={nodeColour()} onClick={handleClick}
onClick={handleClick} renderOrder={renderOrder}
renderOrder={renderOrder}
/>
/> {node.topNode && (
{node.topNode && ( <Ring geometry={topNodeGeo} position={topNodePosition} rotation={topNodeRotation} renderOrder={renderOrder}/>
<Ring geometry={topNodeGeo} position={topNodePosition} rotation={topNodeRotation} renderOrder={renderOrder}/> )}
)} </React.Fragment>
</React.Fragment>
}
{node.inGrain && showLabel && {node.inGrain && showLabel &&
<Billboard position={labelPosition}> <group ref={flagRef} renderOrder={999} onClick={handleClick}>
<group ref={labelGroupRef} renderOrder={999} onClick={handleClick}> <mesh position={[0, 0, -1]}>
<mesh position={[0, 0, -1]}> <planeGeometry args={[LABEL_WIDTH, labelHeight]} />
<planeGeometry args={[200, labelHeight]} /> <meshBasicMaterial
<meshBasicMaterial color="white"
color="white" transparent
transparent opacity={0.9}
opacity={0.9} depthWrite={false}
depthWrite={false} depthTest={false}
depthTest={false} />
/> </mesh>
</mesh> {rows.map((row, i) => (
{rows.map((row, i) => ( <Text
<Text key={i}
key={i} position={[0, getRowY(i), 0]}
position={[0, getRowY(i), 0]} fontSize={35}
fontSize={30} fontWeight={650}
color={row.color} color={row.color}
anchorX="center" anchorX="center"
anchorY="middle" anchorY="middle"
> >
{row.text} {row.text}
</Text> </Text>
))} ))}
</group> </group>
</Billboard> }
}
</React.Fragment> </React.Fragment>
) )
} }

View file

@ -7,17 +7,16 @@ import { CableData } from "../../Data/BuildCableData"
import { NodeData } from "../../Data/BuildNodeData" import { NodeData } from "../../Data/BuildNodeData"
interface Props { interface Props {
cable: CableData cable: CableData,
nodes: NodeData[] nodes: NodeData[],
bin: Bin bin: Bin,
binCenter: Vector3,
renderOrder?: number, renderOrder?: number,
displayTemp?: boolean, displayTemp?: boolean,
displayMoisture?: boolean displayMoisture?: boolean,
onNodeClick?: (node: NodeData) => void onNodeClick?: (node: NodeData) => void
} }
export default function GrainCable(props: Props) { export default function GrainCable(props: Props) {
const {cable, nodes, bin, binCenter, onNodeClick, renderOrder, displayTemp, displayMoisture} = props const {cable, nodes, bin, onNodeClick, renderOrder, displayTemp, displayMoisture} = props
const calculateHeight = () => { const calculateHeight = () => {
return cable.topPosition.distanceTo(cable.bottomPosition) return cable.topPosition.distanceTo(cable.bottomPosition)
@ -44,7 +43,7 @@ export default function GrainCable(props: Props) {
<React.Fragment> <React.Fragment>
<Cylinder geometry={geometry} position={calculateCenter()} opacity={0.5} renderOrder={renderOrder} depthWrite={false} depthTest={false}/> <Cylinder geometry={geometry} position={calculateCenter()} opacity={0.5} renderOrder={renderOrder} depthWrite={false} depthTest={false}/>
{nodes.map((node, i) => ( {nodes.map((node, i) => (
<CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} binCenter={binCenter} lowerThreshold={bin.lowerTempThreshold()} upperThreshold={bin.upperTempThreshold()}/> <CableNode displayTemp={displayTemp} displayMoisture={displayMoisture} onNodeClick={onNodeClick} key={"node " + i} renderOrder={renderOrder} node={node} targetMoisture={bin.targetMoisture()} upperThreshold={bin.upperTempThreshold()}/>
))} ))}
</React.Fragment> </React.Fragment>
) )

View file

@ -47,12 +47,12 @@ export default function BinSummary(props: Props){
<Box padding={2}> <Box padding={2}>
<Grid2 container spacing={2}> <Grid2 container spacing={2}>
<Grid2 size={isMobile ? 12 : 8}> <Grid2 size={isMobile ? 12 : 8}>
<Card> <Card raised>
{bin3dView()} {bin3dView()}
</Card> </Card>
</Grid2> </Grid2>
<Grid2 size={isMobile ? 12 : 4}> <Grid2 size={isMobile ? 12 : 4}>
<Card> <Card raised sx={{height: "100%"}}>
{tableView()} {tableView()}
</Card> </Card>
</Grid2> </Grid2>

View file

@ -1,6 +1,7 @@
import { Box, Checkbox, Grid2, FormControlLabel } from "@mui/material"; import { Box, Checkbox, FormControlLabel, List, ListItem } from "@mui/material";
import Bin3dView from "bin/3dView/Scene/Bin3dView"; import Bin3dView from "bin/3dView/Scene/Bin3dView";
import { Bin } from "models"; import { Bin } from "models";
import React from "react";
import { useState } from "react"; import { useState } from "react";
interface Props { interface Props {
@ -19,13 +20,13 @@ export default function bin3dVisualizer(props: Props){
return 0 return 0
} }
return ( const controlsOverlay = () => {
<Box> return (
<Grid2 container spacing={1}> <React.Fragment>
<Grid2 size={3}> <List>
<FormControlLabel <ListItem>
<FormControlLabel
control={ control={
<Checkbox <Checkbox
value={showHeatmap} value={showHeatmap}
@ -37,7 +38,9 @@ export default function bin3dVisualizer(props: Props){
} }
label={"Toggle Heatmap"} label={"Toggle Heatmap"}
/> />
<FormControlLabel </ListItem>
<ListItem>
<FormControlLabel
control={ control={
<Checkbox <Checkbox
value={showHotspots} value={showHotspots}
@ -49,7 +52,9 @@ export default function bin3dVisualizer(props: Props){
} }
label={"Toggle Hotspots"} label={"Toggle Hotspots"}
/> />
<FormControlLabel </ListItem>
<ListItem>
<FormControlLabel
control={ control={
<Checkbox <Checkbox
value={showFill} value={showFill}
@ -61,7 +66,9 @@ export default function bin3dVisualizer(props: Props){
} }
label={"Toggle Hotspots"} label={"Toggle Hotspots"}
/> />
<FormControlLabel </ListItem>
<ListItem>
<FormControlLabel
control={ control={
<Checkbox <Checkbox
value={showTemp} value={showTemp}
@ -73,7 +80,10 @@ export default function bin3dVisualizer(props: Props){
} }
label={"Toggle Temp"} label={"Toggle Temp"}
/> />
<FormControlLabel </ListItem>
<ListItem>
<FormControlLabel
control={ control={
<Checkbox <Checkbox
value={showMoisture} value={showMoisture}
@ -85,21 +95,38 @@ export default function bin3dVisualizer(props: Props){
} }
label={"Toggle Moisture"} label={"Toggle Moisture"}
/> />
</Grid2> </ListItem>
<Grid2 size={9}> </List>
<Box height={750}>
<Bin3dView
bin={bin}
scale={100}
showTempHeatmap={showHeatmap} </React.Fragment>
showTemp={showTemp} )
showMoisture={showMoisture} }
showGrain={showFill}
showHotspots={showHotspots} const binViewer = () => {
/> return (
</Box> <Bin3dView
</Grid2> bin={bin}
</Grid2> scale={100}
showTempHeatmap={showHeatmap}
showTemp={showTemp}
showMoisture={showMoisture}
showGrain={showFill}
showHotspots={showHotspots}
/>
)
}
return (
<React.Fragment>
<Box position={"relative"} height={750}>
{binViewer()}
<Box position={"absolute"} top={10} left={10}>
{controlsOverlay()}
</Box>
</Box> </Box>
</React.Fragment>
) )
} }

View file

@ -1,37 +1,165 @@
import { Box } from "@mui/material"; import { Box, Table, TableBody, TableCell, TableHead, TableRow } from "@mui/material";
import { green, grey, orange, red } from "@mui/material/colors";
import { Bin } from "models"; import { Bin } from "models";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import React from "react";
import { useMemo } from "react"; import { useMemo } from "react";
import { avg, celsiusToFahrenheit } from "utils";
interface Props { interface Props {
bin: Bin bin: Bin
} }
interface LevelAvg { interface BinLevel {
temp: number grainTemps: number[]
moisture: number airTemps: number[]
inGrain: boolean grainMoistures: number[]
airMoistures: number[]
} }
export default function BinTableView(props: Props) { export default function BinTableView(props: Props) {
const {bin} = props const {bin} = props
const [{user}] = useGlobalState()
const cableLevelAverages = useMemo(() => { const binLevels = useMemo(() => {
const cables = bin.status.grainCables const cables = bin.status.grainCables
let levels: LevelAvg[] = [] let levels: BinLevel[] = []
console.log(cables)
//note for future self, the first item in the measurement arrays is the lowest node on the cable //note for future self, the first item in the measurement arrays is the lowest node on the cable
//the zones we are creating here will match with the node number because most cables will either have the same number of nodes or only have a difference of 1 or 2 //the zones we are creating here will match with the node number because most cables will either have the same number of nodes or only have a difference of 1 or 2
//example: cable 1 has 5 nodes cable 2 has 7 nodes, the first five nodes of the cables will match up and average together for each zone //example: cable 1 has 5 nodes cable 2 has 7 nodes, the first five nodes of the cables will match up and average together for each zone
//and then zones 6 and 7 will be made using only cable 2 because cable 1 doesn't have them //and then zones 6 and 7 will be made using only cable 2 because cable 1 doesn't have them
cables.forEach(cable => { cables.forEach(cable => {
cable.celcius.forEach((c, index) => {
//remember index will be 0 based but the top node starts at 1 for the lowest cable
let inGrain = index < cable.topNode
if (levels[index]){
if(cable.excludedNodes.includes(index+1)) return
if(inGrain){
levels[index].grainTemps.push(c)
//need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
//cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
//based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
//also note that we are only doing moisture and not humidity, it seems like users dont care about humidity
levels[index].grainMoistures.push(cable.moisture[index] ?? 0)
}
}else{
levels[index].airTemps.push(c)
//need to check that the cable not only has the moisture mutation on it, but also that it reads humidity
//cables that dont read humidity return an array of 0 values so if the average of all of the values is 0 then it is a temp only cable
if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
//based on all the other checks and the fact that the indexes between the array should match this should never be 0 but put a guard here just in case
levels[index].airMoistures.push(cable.moisture[index] ?? 0)
}
}
}else{
let newlevel: BinLevel = {
grainTemps: [],
grainMoistures: [],
airTemps: [],
airMoistures: []
}
if(!cable.excludedNodes.includes(index)){
if(inGrain){
newlevel.grainTemps.push(c)
if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
newlevel.grainMoistures.push(cable.moisture[index] ?? 0)
}
}else{
newlevel.airTemps.push(c)
if(cable.moisture.length > 0 && avg(cable.moisture) > 0){
newlevel.airMoistures.push(cable.moisture[index] ?? 0)
}
}
}
levels.push(newlevel)
}
})
}) })
//flip the levels so that the highest nodes are on the top
levels.reverse()
return levels return levels
},[bin]) },[bin])
const levelDisplay = (level: BinLevel) => {
let tempDisplay = "--"
let moistureDisplay = "--"
let lvlTemp
let lvlMoisture
let tempColour: string = grey[600]
let moistureColour: string = grey[600]
//determine the temp
if(level.grainTemps.length > 0){
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 the average temp is greater than 5 degrees over
tempColour = orange[600]
}else {
tempColour = green[300]
}
}else if (level.airTemps.length > 0){
lvlTemp = avg(level.airTemps)
}
if (lvlTemp){
if(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
lvlTemp = celsiusToFahrenheit(lvlTemp)
}
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]
}else if(lvlMoisture > bin.targetMoisture()){
//if the average temp is greater than 5 degrees over
moistureColour = orange[600]
}else {
moistureColour = green[300]
}
}else if (level.airMoistures.length > 0){
lvlMoisture = avg(level.airMoistures)
}
if(lvlMoisture){
moistureDisplay = lvlMoisture.toFixed(2) + "%"
}
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>
</React.Fragment>
)
}
return ( return (
<Box>Table View</Box> <Box padding={2}>
<Table>
<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>
</TableRow>
</TableHead>
<TableBody>
{binLevels.map((level, i) => (
<TableRow>
<TableCell sx={{color: "gray", fontWeight: 650, fontSize: 17, textAlign: "center"}}>{binLevels.length - i}</TableCell>
{levelDisplay(level)}
</TableRow>
))}
</TableBody>
</Table>
</Box>
) )
} }

View file

@ -292,4 +292,8 @@ export class Bin {
public lowerTempThreshold(): number { public lowerTempThreshold(): number {
return this.settings.lowTemp return this.settings.lowTemp
} }
public targetMoisture(): number {
return this.settings.inventory?.targetMoisture ?? 0
}
} }