From b00f10b831c246167f94df46672d6475a151f95a Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 28 May 2026 11:18:40 -0600 Subject: [PATCH] finished the bin sensor view to display connected components for a bin --- src/bin/BinSensorCard.tsx | 105 +++ src/bin/binSensorsDisplay.tsx | 780 ++++++++++++++++++ src/bin/binSummary/BinSummary.tsx | 20 +- src/bin/binSummary/components/binDetails.tsx | 2 +- src/bin/binSummary/components/binSensors.tsx | 156 ---- .../components => }/binTableView.tsx | 0 src/grain/GrainDescriber.ts | 75 +- src/grain/GrainMoisture.ts | 8 - src/models/Ambient.ts | 5 + src/models/Controller.ts | 20 +- src/models/GrainCable.ts | 4 + src/models/Headspace.ts | 5 + src/models/Plenum.ts | 5 + src/models/Pressure.ts | 12 +- src/pages/Bin.tsx | 5 - src/theme/theme.ts | 13 +- 16 files changed, 983 insertions(+), 232 deletions(-) create mode 100644 src/bin/BinSensorCard.tsx create mode 100644 src/bin/binSensorsDisplay.tsx delete mode 100644 src/bin/binSummary/components/binSensors.tsx rename src/bin/{binSummary/components => }/binTableView.tsx (100%) diff --git a/src/bin/BinSensorCard.tsx b/src/bin/BinSensorCard.tsx new file mode 100644 index 0000000..f15dda9 --- /dev/null +++ b/src/bin/BinSensorCard.tsx @@ -0,0 +1,105 @@ +import { AccessTime, MoreVert } from "@mui/icons-material"; +import { Avatar, Box, Card, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography, useTheme } from "@mui/material"; +import moment from "moment"; +import React from "react"; + +interface SensorRow { + label: string; + data: string; +} + +interface CableRow { + label: string; + min: string; + avg: string; + max: string; +} + +interface Props { + name: string; + icon?: string; + tag: string; + lastReading: string; + onMenuClick: (event: React.MouseEvent) => void; + rows?: SensorRow[]; + cableRows?: CableRow[]; + /** + * the time in hours that a reading is considered 'good' + * defaults to 6 + */ + staleLimit?: number; +} + + +export default function BinSensorCard(props: Props) { + const { name, icon, tag, lastReading, onMenuClick, rows, cableRows, staleLimit = 6 } = props + const theme = useTheme() + const isStale = moment().diff(moment(lastReading), "hours") > staleLimit + + return ( + + + + + + {icon && + + } + {name} + - {tag} + + + + + + + + {cableRows ? ( + <> + + + + Min + Avg + Max + + + + {cableRows.map((row) => ( + + {row.label} + {row.min} + {row.avg} + {row.max} + + ))} + + + ) : ( + + {rows?.map((row) => ( + + {row.label} + {row.data} + + ))} + + )} +
+ + + + + {moment(lastReading).fromNow()} + + + +
+
+ ) +} \ No newline at end of file diff --git a/src/bin/binSensorsDisplay.tsx b/src/bin/binSensorsDisplay.tsx new file mode 100644 index 0000000..e77866d --- /dev/null +++ b/src/bin/binSensorsDisplay.tsx @@ -0,0 +1,780 @@ +import { AccessTime, MoreVert } from "@mui/icons-material"; +import { Avatar, Box, Button, Card, Grid2, IconButton, Menu, MenuItem, Typography, useTheme } from "@mui/material"; +import BinSensorCard from "bin/BinSensorCard"; +import { ExtractMoisture } from "grain"; +import { useThemeType } from "hooks"; +import { Bin, Component } from "models"; +import { Ambient } from "models/Ambient"; +import { CO2 } from "models/CO2"; +import { Controller } from "models/Controller"; +import { GrainCable } from "models/GrainCable"; +import { Headspace } from "models/Headspace"; +import { Plenum } from "models/Plenum"; +import { Pressure } from "models/Pressure"; +import moment from "moment"; +import { GetComponentIcon } from "pbHelpers/ComponentType"; +import { pond } from "protobuf-ts/pond"; +import { quack } from "protobuf-ts/quack"; +import { useBinAPI, useComponentAPI, useGlobalState, useSnackbar } from "providers"; +import React, { useEffect, useState } from "react"; +import { avg } from "utils"; + +interface Props { + bin: Bin + components: Map; + componentDevices: Map; + preferences?: Map; + setPreferences: React.Dispatch< + React.SetStateAction | undefined> + >; + +} + +export default function BinSensorsDisplay(props: Props){ + const {bin, components, componentDevices, preferences, setPreferences} = props + const themeType = useThemeType() + const [{user, as}] = useGlobalState() + const binAPI = useBinAPI() + const componentAPI = useComponentAPI() + const theme = useTheme() + const snackbar = useSnackbar() + const [cables, setCables] = useState([]) + const [plenums, setPlenums] = useState([]) + const [ambient, setAmbient] = useState([]) + const [pressures, setPressures] = useState([]) + const [fans, setFans] = useState([]) + const [heaters, setHeaters] = useState([]) + //the above component preferences all have only components that are one type of sensor, however headspace has components of three different sensors T/H, CO2, and Lidar + const [headspaceTH, setHeadspaceTH] = useState([]) //the components in the headspace that measure temp/humidity + const [headspaceCO2, setHeadspaceCO2] = useState([]) //the CO2 components in the headspace + const [headspaceLidar, setHeadspaceLidar] = useState([]) //the lidar components in the headspace + + const [unassignedComponents, setUnassignedComponents] = useState([]) + + //using counts so i dont have to have to check multiple arrays for sensors and controllers + const [sensorCount, setSensorCount] = useState(0) + const [controllerCount, setControllerCount] = useState(0) + + const [anchorEl, setAnchorEl] = React.useState(null); + //only used to determine what preferences are options for the selected component ie. DHT can be a plenum or a headspace + const [selectedComponentType, setSelectedComponentType] = useState< + quack.ComponentType | undefined + >(undefined); + const [componentUnassigned, setComponentUnnassigned] = useState(false); + const [selectedComponentKey, setSelectedComponentKey] = useState(""); + const [selectedComponentSubtype, setSelectedComponentSubtype] = useState(0); + const [selectedComponentTopNode, setSelectedComponentTopNode] = useState(0); + const [showGraphs, setShowGraphs] = useState(false); + + //organize the components into the correct 'positions' using the bins preferences + useEffect(()=>{ + let unassigned: Component[] = [] + let cables: GrainCable[] = [] + let plenums: Plenum[] = [] + let ambients: Ambient[] = [] + let pressures: Pressure[] = [] + let fans: Controller[] = [] + let heaters: Controller[] = [] + let headspaceTH: Headspace[] = [] + let headspaceCO2: CO2[] = [] + let headspaceLidar: Component[] = [] + let sensorCount = 0 + let controllerCount = 0 + + components.forEach(comp => { + if(preferences){ + let pref = preferences.get(comp.key()) + switch(pref?.type){ + case pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE: + cables.push(GrainCable.create(comp)) + sensorCount++ + break; + case pond.BinComponent.BIN_COMPONENT_PLENUM: + plenums.push(Plenum.create(comp)) + sensorCount++ + break; + case pond.BinComponent.BIN_COMPONENT_AMBIENT: + ambients.push(Ambient.create(comp)) + sensorCount++ + break; + case pond.BinComponent.BIN_COMPONENT_PRESSURE: + pressures.push(Pressure.create(comp)) + sensorCount++ + break; + case pond.BinComponent.BIN_COMPONENT_FAN: + fans.push(Controller.create(comp)) + controllerCount++ + break; + case pond.BinComponent.BIN_COMPONENT_HEATER: + heaters.push(Controller.create(comp)) + controllerCount++ + break; + case pond.BinComponent.BIN_COMPONENT_HEADSPACE: + if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) { + headspaceTH.push(Headspace.create(comp)); + } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) { + headspaceLidar.push(comp); + } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) { + let coComp = CO2.create(comp) + headspaceCO2.push(coComp); + } + sensorCount++ + break; + default: + unassigned.push(comp) + } + }else{ + unassigned.push(comp) + } + }) + // console.log(cables) + // console.log(unassigned) + setSensorCount(sensorCount) + setControllerCount(controllerCount) + setCables(cables) + setPlenums(plenums) + setPressures(pressures) + setAmbient(ambients) + setFans(fans) + setHeaters(heaters) + setHeadspaceTH(headspaceTH) + setHeadspaceCO2(headspaceCO2) + setHeadspaceLidar(headspaceLidar) + setUnassignedComponents(unassigned) + },[components, preferences]) + + const selectType = (component: string, type: pond.BinComponent, topNode?: number) => { + setAnchorEl(null); + if (!preferences) return + let p = preferences.get(component); + if (p) { + p.type = type; + p.node = topNode ?? 0; + binAPI + .updateComponentPreferences(bin.key(), component, p, as) + .then(() => { + snackbar.success("Component preferences updated"); + preferences.set(component, p!); + setPreferences(new Map(preferences)); + }) + .catch(() => { + snackbar.error("Component preference update failed"); + }); + } + }; + + const getOptions = () => { + let options: JSX.Element[] = []; + + if (!componentUnassigned) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_UNKNOWN); + }}> + Remove + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_AMBIENT); + }}> + Ambient + + ); + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PLENUM); + }}> + Plenum + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_DHT || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_LIDAR || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_CO2 + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEADSPACE); + }}> + Headspace + + ); + } + if ( + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE || + selectedComponentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE + ) { + options.push( + { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_PRESSURE); + }}> + Plenum Pressure + + ); + } + if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) { + options.push( + { + selectType( + selectedComponentKey, + pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE, + selectedComponentTopNode + ); + }}> + Grain Cable + + ); + } + if (selectedComponentType === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT) { + options.push( + { + if ( + selectedComponentSubtype === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER + ) { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_HEATER); + } else if ( + selectedComponentSubtype === + quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN + ) { + selectType(selectedComponentKey, pond.BinComponent.BIN_COMPONENT_FAN); + } + }}> + Heaters & Fans + + ); + } + + if (options.length === 0) { + return No Available Options; + } else { + return options; + } + }; + + const assignmentMenu = () => { + return ( + setAnchorEl(null)} + keepMounted + disableAutoFocusItem> + {getOptions()} + + ); + }; + + const cableCard = (cable: GrainCable) => { + const component = cable.asComponent() + let icon = GetComponentIcon(component.type(), component.subType(), themeType) + let unit = user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C" + + const hasHumidity = cable.aveHumidity() !== 0 + const hasMoisture = cable.grainMoistures.length > 0 + + const rows: { label: string; min: string; avg: string; max: string }[] = [ + { + label: `Temp ${unit}`, + min: cable.minTemp(user.tempUnit()).toFixed(2), + avg: cable.aveTemp(user.tempUnit()).toFixed(2), + max: cable.maxTemp(user.tempUnit()).toFixed(2), + }, + ...(hasHumidity ? [{ + label: "RH %", + min: cable.minHumidity().toFixed(2), + avg: cable.aveHumidity().toFixed(2), + max: cable.maxHumidity().toFixed(2), + }] : []), + ...(hasMoisture ? [{ + label: "EMC %", + min: cable.minMoisture().toFixed(2), + avg: cable.aveMoisture().toFixed(2), + max: cable.maxMoisture().toFixed(2), + }] : []), + ] + + return ( + { + setComponentUnnassigned(false); + setSelectedComponentType(cable.type()); + setSelectedComponentKey(cable.key()); + setSelectedComponentTopNode(cable.settings.grainFilledTo); + setAnchorEl(event.currentTarget); + }}/> + ) + } + + const tempHumidCard = (sensor: Plenum | Ambient | Headspace, showEMC?: boolean) => { + let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType) + let prefDisplay = "" + switch (preferences?.get(sensor.key())?.type){ + case pond.BinComponent.BIN_COMPONENT_PLENUM: + prefDisplay = "Plenum" + break; + case pond.BinComponent.BIN_COMPONENT_AMBIENT: + prefDisplay = "Ambient" + break; + case pond.BinComponent.BIN_COMPONENT_HEADSPACE: + prefDisplay = "Headspace" + } + + let rows: {label: string, data: string}[] = [ + // build temperature row data + { + label: "Temp " + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C"), + data: (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? sensor.temperature * (9 / 5) + 32 : sensor.temperature).toFixed(2) + }, + // build humidity row + { + label: "RH %", + data: sensor.humidity.toFixed(2) + } + ] + // build moisture row assuming the bin has a grain type to use to calculatee the moisture + if(showEMC && bin.grain() !== pond.Grain.GRAIN_INVALID && bin.grain() !== pond.Grain.GRAIN_NONE){ + let emc = ExtractMoisture( + bin.grain(), + sensor.temperature, + sensor.humidity, + bin.customGrain() + ) + rows.push( + { + label: "EMC %", + data: emc.toFixed(2) + } + ) + } + + return ( + { + setComponentUnnassigned(false); + setSelectedComponentType(sensor.type()); + setSelectedComponentKey(sensor.key()); + setAnchorEl(event.currentTarget); + }} + /> + ) + } + + const pressureCard = (pressure: Pressure) => { + let icon = GetComponentIcon(pressure.settings.type, pressure.settings.subtype, themeType) + let rows: {label: string, data: string}[] = [ + { + label: "Pressure " + (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"), + data: (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? pressure.pascals/249 : pressure.pascals/1000).toFixed(2) + } + ] + + if(pressure.airflow){ + rows.push({ + label: "Airflow CFM", + data: pressure.airflow.toFixed(2) + }) + } + + return ( + { + setComponentUnnassigned(false); + setSelectedComponentType(pressure.type()); + setSelectedComponentKey(pressure.key()); + setAnchorEl(event.currentTarget); + }} + /> + ) + } + const co2Card = (sensor: CO2) => { + let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType) + let rows: {label: string, data: string}[] = [ + { + label: "CO2 ppm", + data: sensor.ppm.toFixed(2) + } + ] + + return ( + { + setComponentUnnassigned(false); + setSelectedComponentType(sensor.type()); + setSelectedComponentKey(sensor.key()); + setAnchorEl(event.currentTarget); + }} + /> + ) + } + + const lidarCard = (sensor: Component) => { + let icon = GetComponentIcon(sensor.settings.type, sensor.settings.subtype, themeType) + let rows: {label: string, data: string}[] = [] + let lastReading = moment.now().toString() + sensor.lastMeasurement.forEach(unitMeasurement => { + let label = "" + let value = 0 + if(sensor.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR){ + if(unitMeasurement.type === quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM){ + label = "Lidar " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? " ft" : " M") + if(unitMeasurement.timestamps.length > 0){ + lastReading = unitMeasurement.timestamps[0] + } + if (unitMeasurement.values.length > 0){ + //distance is stored in cm so needs to be converted + value = avg(unitMeasurement.values[0].values) + value = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? value/30.48 : value/100 + } + + } + } + rows.push({label: label, data: value.toFixed(2)}) + }) + + return ( + { + setComponentUnnassigned(false); + setSelectedComponentType(sensor.type()); + setSelectedComponentKey(sensor.key()); + setAnchorEl(event.currentTarget); + }} + /> + + ) + } + + const controllerCard = (controller: Controller) => { + const component = controller.asComponent() + let icon = GetComponentIcon(component.type(), component.subType(), themeType) + const isStale = moment().diff(moment(controller.lastReading), "hours") > 6 + const isAuto = controller.mode === quack.OutputMode.OUTPUT_MODE_AUTO + const isOn = controller.mode === quack.OutputMode.OUTPUT_MODE_ON + const isOff = controller.mode === quack.OutputMode.OUTPUT_MODE_OFF + + const prefType = preferences?.get(controller.key())?.type + const tag = prefType === pond.BinComponent.BIN_COMPONENT_FAN ? "Fan" : "Heater" + + const segStyle = (active: boolean, activeColor?: string): React.CSSProperties => ({ + flex: 1, + padding: "5px 0", + fontSize: 11, + fontWeight: 500, + textAlign: "center", + cursor: "pointer", + borderRadius: 0, + backgroundColor: active ? (activeColor ?? theme.palette.success.main) : "transparent", + color: active ? "#fff" : theme.palette.text.secondary, + border: "none", + transition: "background-color 0.15s", + }) + + return ( + + + + + + + {controller.name()} + - {tag} + + ) => { + setComponentUnnassigned(false) + setSelectedComponentType(controller.type()) + setSelectedComponentKey(controller.key()) + setAnchorEl(event.currentTarget) + }} + > + + + + + + Current state + + + + {controller.on ? "Running" : isAuto ? "Standby" : "Off"} + + + + + + {[ + { label: "Auto", mode: quack.OutputMode.OUTPUT_MODE_AUTO, active: isAuto }, + { label: "On", mode: quack.OutputMode.OUTPUT_MODE_ON, active: isOn }, + { label: "Off", mode: quack.OutputMode.OUTPUT_MODE_OFF, active: isOff }, + ].map(({ label, mode, active }, i) => ( + { + //this will update the components settings here to change the mode + console.log("changing mode: " + mode) + controller.settings.defaultOutputState = mode + let device = componentDevices.get(controller.key()) + if(device){ + componentAPI.update(device, controller.settings).then(resp => { + console.log("updated controller") + if (prefType === pond.BinComponent.BIN_COMPONENT_FAN) { + setFans(prev => [...prev]) + } else { + setHeaters(prev => [...prev]) + } + }).catch(err => { + console.log("there was a problem updating the controller") + }) + } + + }} + sx={{ + ...segStyle(active, label === "Off" ? theme.palette.action.selected : undefined), + borderLeft: i > 0 ? `0.5px solid ${theme.palette.divider}` : "none", + }} + > + {label} + + ))} + + + + + + {moment(controller.lastReading).fromNow()} + + + + + + ) + } + + const unassignedComponentCard = (component: Component) => { + let icon = GetComponentIcon(component.type(), component.subType(), themeType) + const lastReading = component.lastMeasurement.length > 0 + ? component.lastMeasurement[0].timestamps[0] ?? "" + : "" + const isStale = moment().diff(moment(lastReading), "hours") > 6 + + return ( + + + + + + {component.name()} + + ) => { + setComponentUnnassigned(true) + setSelectedComponentType(component.type()) + setSelectedComponentKey(component.key()) + setSelectedComponentSubtype(component.subType()) + setSelectedComponentTopNode(0) + setAnchorEl(event.currentTarget) + }} + > + + + + + + + + {lastReading ? moment(lastReading).fromNow() : "No readings yet"} + + + + + + ) + } + + return ( + + {assignmentMenu()} + {sensorCount > 0 && + + + Sensors + + + + {cables.map(cable => { + return( + + {cableCard(cable)} + + ) + } + )} + {plenums.map(plenum => { + return( + + {tempHumidCard(plenum)} + + ) + } + )} + {pressures.map(pressure => { + return( + + {pressureCard(pressure)} + + ) + })} + {ambient.map(ambient => { + return( + + {tempHumidCard(ambient)} + + ) + } + )} + {headspaceTH.map(h => { + return( + + {tempHumidCard(h)} + + ) + })} + + {headspaceCO2.map(co2 => { + return( + + {co2Card(co2)} + + ) + })} + {headspaceLidar.map(lidar => { + return ( + + {lidarCard(lidar)} + + ) + })} + + + } + + {controllerCount > 0 && + + Controllers + + {heaters.map(heater => { + return ( + + {controllerCard(heater)} + + ) + })} + {fans.map(fan => { + return ( + + {controllerCard(fan)} + + ) + })} + + + } + {unassignedComponents.length > 0 && + + Unassigned Components + + {unassignedComponents.map(comp => ( + + {unassignedComponentCard(comp)} + + ))} + + + } + + ) +} \ No newline at end of file diff --git a/src/bin/binSummary/BinSummary.tsx b/src/bin/binSummary/BinSummary.tsx index fcc0672..61e6980 100644 --- a/src/bin/binSummary/BinSummary.tsx +++ b/src/bin/binSummary/BinSummary.tsx @@ -2,18 +2,16 @@ import { Box, Card, Grid2, LinearProgress, Typography } from "@mui/material"; import { useMobile } from "hooks"; import { Bin, Component, Device } from "models"; import Bin3dVisualizer from "./components/bin3dVisualizer"; -import BinTableView from "./components/binTableView"; // import GrassIcon from "@mui/icons-material/Grass" import { grey, orange, red } from "@mui/material/colors"; import { AccessTime, Spa } from "@mui/icons-material"; import moment from "moment"; -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"; import { Plenum } from "models/Plenum"; -import BinSensors from "./components/binSensors"; +import BinSensorsDisplay from "../binSensorsDisplay"; interface Props { bin: Bin @@ -73,20 +71,6 @@ export default function BinSummary(props: Props){ ) } - const sensorData = () => { - return ( - sensor data - ) - } - - const controllerStatus = () => { - return ( - controller status - ) - } - - - return ( @@ -191,7 +175,7 @@ export default function BinSummary(props: Props){ - + diff --git a/src/bin/binSummary/components/binDetails.tsx b/src/bin/binSummary/components/binDetails.tsx index aa21d84..008eed0 100644 --- a/src/bin/binSummary/components/binDetails.tsx +++ b/src/bin/binSummary/components/binDetails.tsx @@ -1,7 +1,7 @@ import { Box, Tab, Tabs } from "@mui/material"; import React from "react"; import { useState } from "react"; -import BinTableView from "./binTableView"; +import BinTableView from "../../binTableView"; import { Bin, Component, Device } from "models"; import Alerts from "objects/objectInteractions/Alerts"; import BinAlerts from "./binAlerts"; diff --git a/src/bin/binSummary/components/binSensors.tsx b/src/bin/binSummary/components/binSensors.tsx deleted file mode 100644 index 602f622..0000000 --- a/src/bin/binSummary/components/binSensors.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Box, Grid2, Typography } from "@mui/material"; -import { Bin, Component } from "models"; -import { Ambient } from "models/Ambient"; -import { CO2 } from "models/CO2"; -import { Controller } from "models/Controller"; -import { GrainCable } from "models/GrainCable"; -import { Headspace } from "models/Headspace"; -import { Plenum } from "models/Plenum"; -import { Pressure } from "models/Pressure"; -import { pond } from "protobuf-ts/pond"; -import { quack } from "protobuf-ts/quack"; -import React, { useEffect, useState } from "react"; - -interface Props { - bin: Bin - components: Map; - preferences?: Map; - setPreferences: React.Dispatch< - React.SetStateAction | undefined> - >; -} - -export default function BinSensors(props: Props){ - const {bin, components, preferences, setPreferences} = props - const [cables, setCables] = useState([]) - const [plenums, setPlenums] = useState([]) - const [ambient, setAmbient] = useState([]) - const [pressures, setPressures] = useState([]) - const [fans, setFans] = useState([]) - const [heaters, setHeaters] = useState([]) - //the above component preferences all have only components that are one type of sensor, however headspace has components of three different sensors T/H, CO2, and Lidar - const [headspaceTH, setHeadspaceTH] = useState([]) //the components in the headspace that measure temp/humidity - const [headspaceCO2, setHeadspaceCO2] = useState([]) //the CO2 components in the headspace - const [headspaceLidar, setHeadspaceLidar] = useState([]) //the lidar components in the headspace - - const [unassignedComponents, setUnassignedComponents] = useState([]) - - //using counts so i dont have to have to check multiple arrays for sensors and controllers - const [sensorCount, setSensorCount] = useState(0) - const [controllerCount, setControllerCount] = useState(0) - - - //organize the components into the correct 'positions' using the bins preferences - useEffect(()=>{ - let unassigned: Component[] = [] - let cables: GrainCable[] = [] - let plenums: Plenum[] = [] - let ambients: Ambient[] = [] - let pressures: Pressure[] = [] - let fans: Controller[] = [] - let heaters: Controller[] = [] - let headspaceTH: Headspace[] = [] - let headspaceCO2: CO2[] = [] - let headspaceLidar: Component[] = [] - let sensorCount = 0 - let controllerCount = 0 - - components.forEach(comp => { - if(preferences){ - let pref = preferences.get(comp.key()) - switch(pref?.type){ - case pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE: - cables.push(GrainCable.create(comp)) - sensorCount++ - break; - case pond.BinComponent.BIN_COMPONENT_PLENUM: - plenums.push(Plenum.create(comp)) - sensorCount++ - break; - case pond.BinComponent.BIN_COMPONENT_AMBIENT: - ambients.push(Ambient.create(comp)) - sensorCount++ - break; - case pond.BinComponent.BIN_COMPONENT_PRESSURE: - pressures.push(Pressure.create(comp)) - sensorCount++ - break; - case pond.BinComponent.BIN_COMPONENT_FAN: - fans.push(Controller.create(comp)) - controllerCount++ - break; - case pond.BinComponent.BIN_COMPONENT_HEATER: - heaters.push(Controller.create(comp)) - controllerCount++ - break; - case pond.BinComponent.BIN_COMPONENT_HEADSPACE: - if (comp.type() === quack.ComponentType.COMPONENT_TYPE_DHT) { - headspaceTH.push(Headspace.create(comp)); - } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_LIDAR) { - headspaceLidar.push(comp); - } else if (comp.type() === quack.ComponentType.COMPONENT_TYPE_CO2) { - let coComp = CO2.create(comp) - headspaceCO2.push(coComp); - } - sensorCount++ - break; - default: - unassigned.push(comp) - } - }else{ - unassigned.push(comp) - } - }) - // console.log(cables) - // console.log(unassigned) - setCables(cables) - setPlenums(plenums) - setPressures(pressures) - setAmbient(ambients) - setFans(fans) - setHeaters(heaters) - setHeadspaceTH(headspaceTH) - setHeadspaceCO2(headspaceCO2) - setHeadspaceLidar(headspaceLidar) - setUnassignedComponents(unassigned) - },[components, preferences]) - - const cableDisplay = (cable: GrainCable) => { - return ( - - - - ) - } - - return ( - - {sensorCount > 0 && - - Sensors - {cables.length > 0 && - - Cables - - {cables.map(cable => { - return( - - {cableDisplay(cable)} - - ) - })} - - - } - {plenums.length } - - } - - Controllers - {/* do all of the fans and heaters here */} - - Unassigned Components - {/* list the components that have no assignment on the bin */} - - ) -} \ No newline at end of file diff --git a/src/bin/binSummary/components/binTableView.tsx b/src/bin/binTableView.tsx similarity index 100% rename from src/bin/binSummary/components/binTableView.tsx rename to src/bin/binTableView.tsx diff --git a/src/grain/GrainDescriber.ts b/src/grain/GrainDescriber.ts index 58a8766..5db2cd5 100644 --- a/src/grain/GrainDescriber.ts +++ b/src/grain/GrainDescriber.ts @@ -14,12 +14,11 @@ import WheatImg from "assets/grain/wheat.jpg"; import { Option } from "common/SearchSelect"; import { cloneDeep } from "lodash"; import { pond } from "protobuf-ts/pond"; -import { Equation } from "./GrainMoisture"; export interface GrainExtension { name: string; group: string; - equation: Equation; + equation: pond.MoistureEquation; a: number; b: number; c: number; @@ -38,7 +37,7 @@ const defaultSetTemp = 30.0; const defaultGrain: GrainExtension = { name: "None", group: "", - equation: Equation.none, + equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE, a: 0, b: 0, c: 0, @@ -58,7 +57,7 @@ export const GrainExtensions: Map = new Map([ { name: "Custom Type", group: "", - equation: Equation.none, + equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE, a: 0, b: 0, c: 0, @@ -75,7 +74,7 @@ export const GrainExtensions: Map = new Map([ { name: "Barley", group: "Barley", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 475.12, b: 0.14843, c: 71.996, @@ -93,7 +92,7 @@ export const GrainExtensions: Map = new Map([ { name: "Buckwheat", group: "Buckwheat", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 103540000, b: 0.1646, c: 15853000, @@ -111,7 +110,7 @@ export const GrainExtensions: Map = new Map([ { name: "Canola", group: "Canola", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 3.489, b: -0.010553, c: 1.86, @@ -130,7 +129,7 @@ export const GrainExtensions: Map = new Map([ { name: "Rapeseed", group: "Canola", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 3.0026, b: -0.0048967, c: 1.7607, @@ -148,7 +147,7 @@ export const GrainExtensions: Map = new Map([ { name: "Corn (Henderson)", group: "Corn", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000066612, b: 1.9677, c: 42.143, @@ -166,7 +165,7 @@ export const GrainExtensions: Map = new Map([ { name: "Corn (Chung-Pfost)", group: "Corn", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 374.34, b: 0.18662, c: 31.696, @@ -184,7 +183,7 @@ export const GrainExtensions: Map = new Map([ { name: "Corn (Oswin)", group: "Corn", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 15.303, b: -0.10164, c: 3.0358, @@ -202,7 +201,7 @@ export const GrainExtensions: Map = new Map([ { name: "Maize White", group: "Corn", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000066612, b: 1.9677, c: 70.143, @@ -220,7 +219,7 @@ export const GrainExtensions: Map = new Map([ { name: "Maize Yellow", group: "Corn", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000066612, b: 1.9677, c: 65.143, @@ -238,7 +237,7 @@ export const GrainExtensions: Map = new Map([ { name: "Oats (Henderson)", group: "Oats", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000085511, b: 2.0087, c: 37.811, @@ -256,7 +255,7 @@ export const GrainExtensions: Map = new Map([ { name: "Oats (Chung-Pfost)", group: "Oats", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 442.85, b: 0.21228, c: 35.803, @@ -274,7 +273,7 @@ export const GrainExtensions: Map = new Map([ { name: "Oats (Oswin)", group: "Oats", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 12.412, b: -0.060707, c: 2.9397, @@ -292,7 +291,7 @@ export const GrainExtensions: Map = new Map([ { name: "Peanuts", group: "Peanuts", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 8.6588, b: -0.057904, c: 2.6204, @@ -310,7 +309,7 @@ export const GrainExtensions: Map = new Map([ { name: "Long Grain Rice", group: "Rice", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000041276, b: 2.1191, c: 49.828, @@ -328,7 +327,7 @@ export const GrainExtensions: Map = new Map([ { name: "Medium Grain Rice", group: "Rice", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000035502, b: 2.31, c: 27.396, @@ -346,7 +345,7 @@ export const GrainExtensions: Map = new Map([ { name: "Short Grain Rice", group: "Rice", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.000048524, b: 2.0794, c: 45.646, @@ -364,7 +363,7 @@ export const GrainExtensions: Map = new Map([ { name: "Sorghum", group: "Sorghum", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 797.33, b: 0.18159, c: 52.238, @@ -382,7 +381,7 @@ export const GrainExtensions: Map = new Map([ { name: "Soybeans", group: "Soybeans", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 228.2, b: 0.2072, c: 30, @@ -400,7 +399,7 @@ export const GrainExtensions: Map = new Map([ { name: "Sunflower", group: "Sunflower", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.00031, b: 1.7459, c: 66.603, @@ -418,7 +417,7 @@ export const GrainExtensions: Map = new Map([ { name: "Durum Wheat", group: "Wheat", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 13.101, b: -0.052626, c: 2.9987, @@ -436,7 +435,7 @@ export const GrainExtensions: Map = new Map([ { name: "Hard Red Wheat", group: "Wheat", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 610.34, b: 0.15526, c: 93.213, @@ -454,7 +453,7 @@ export const GrainExtensions: Map = new Map([ { name: "Wheat SAWOS", group: "Wheat", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 610.34, b: 0.15526, c: 93.213, @@ -472,7 +471,7 @@ export const GrainExtensions: Map = new Map([ { name: "Un-retted Flax", group: "Flax", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 5.11, b: Math.pow(-8.46 * 10, -3), c: 2.26, @@ -490,7 +489,7 @@ export const GrainExtensions: Map = new Map([ { name: "Dew-retted Flax", group: "Flax", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 6.5, b: Math.pow(-1.68 * 10, -2), c: 3.2, @@ -508,7 +507,7 @@ export const GrainExtensions: Map = new Map([ { name: "Yellow Peas", group: "Peas", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 14.81, b: -0.109, c: 3.019, @@ -526,7 +525,7 @@ export const GrainExtensions: Map = new Map([ { name: "Blaze Lentils", group: "Lentils", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 5.39, b: -0.015, c: 2.273, @@ -544,7 +543,7 @@ export const GrainExtensions: Map = new Map([ { name: "Redberry Lentils", group: "Lentils", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 4.749, b: -0.0116, c: 2.066, @@ -562,7 +561,7 @@ export const GrainExtensions: Map = new Map([ { name: "Robin Lentils", group: "Lentils", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 5.176, b: -0.0065, c: 2.337, @@ -580,7 +579,7 @@ export const GrainExtensions: Map = new Map([ { name: "Dry Beans Red", group: "Dry Beans", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 4.2669, b: -0.013382, c: 1.6933, @@ -596,7 +595,7 @@ export const GrainExtensions: Map = new Map([ { name: "Dry Beans Black", group: "Dry Beans", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 5.2003, b: -0.022685, c: 1.9656, @@ -612,7 +611,7 @@ export const GrainExtensions: Map = new Map([ { name: "Rye (Henderson)", group: "Rye", - equation: Equation.henderson, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON, a: 0.00006343, b: 2.2060, c: 13.1810, @@ -628,7 +627,7 @@ export const GrainExtensions: Map = new Map([ { name: "Rye (Chung-Pfost)", group: "Rye", - equation: Equation.chungPfost, + equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST, a: 461.0230, b: 0.1840, c: 36.7410, @@ -644,7 +643,7 @@ export const GrainExtensions: Map = new Map([ { name: "Rye (Halsey)", group: "Rye", - equation: Equation.halsey, + equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY, a: 4.2970, b: 0.380, c: 2.2710, @@ -660,7 +659,7 @@ export const GrainExtensions: Map = new Map([ { name: "Rye (Oswin)", group: "Rye", - equation: Equation.oswin, + equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN, a: 11.8870, b: 0.0210, c: 3.2620, diff --git a/src/grain/GrainMoisture.ts b/src/grain/GrainMoisture.ts index 808f302..28b6387 100644 --- a/src/grain/GrainMoisture.ts +++ b/src/grain/GrainMoisture.ts @@ -1,14 +1,6 @@ import { pond } from "protobuf-ts/pond"; import GrainDescriber from "./GrainDescriber"; -export enum Equation { - "none", - "oswin", - "halsey", - "henderson", - "chungPfost" -} - const toERH = (humidity: number): number => { return humidity >= 100 ? 0.99999 : humidity / 100; }; diff --git a/src/models/Ambient.ts b/src/models/Ambient.ts index 4fd906a..d2c7577 100644 --- a/src/models/Ambient.ts +++ b/src/models/Ambient.ts @@ -10,6 +10,7 @@ export class Ambient { public status: pond.ComponentStatus = pond.ComponentStatus.create(); public temperature: number = -127; public humidity: number = 200; + public lastReading: string = "" public static create(comp: Component): Ambient { let my = new Ambient(); @@ -18,6 +19,9 @@ export class Ambient { if (comp.status.measurement.length > 0) { comp.status.measurement.forEach(um => { + if (um.timestamps[0]){ + my.lastReading = um.timestamps[0] + } if (um.values[0]) { if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { my.temperature = or(um.values[0].values[0], -127); @@ -39,6 +43,7 @@ export class Ambient { hum = humAndTemp["relativeHumidityTimes100"]; } } + my.lastReading = comp.status.lastMeasurement?.timestamp ?? "" my.temperature = or(temp, -1270) / 10; my.humidity = or(hum, 20000) / 100; } diff --git a/src/models/Controller.ts b/src/models/Controller.ts index 0ee12af..4e7170c 100644 --- a/src/models/Controller.ts +++ b/src/models/Controller.ts @@ -9,13 +9,30 @@ export class Controller { public settings: pond.ComponentSettings = pond.ComponentSettings.create(); public status: pond.ComponentStatus = pond.ComponentStatus.create(); public on = false; + public mode: quack.OutputMode | undefined + public lastReading: string = ""; public static create(comp: Component): Controller { let my = new Controller(); my.settings = comp.settings; my.status = comp.status; + my.mode = comp.settings.defaultOutputState - my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value); + //this is deprecated because it is our old measurement structure + // my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value); + + if (comp.status.lastGoodMeasurement.length > 0) { + comp.status.measurement.forEach(um => { + if (um.timestamps[0]){ + my.lastReading = um.timestamps[0] + } + if (um.values[0]) { + if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN) { + my.on = Boolean(um.values[0].values[0]); + } + } + }); + } return my; } @@ -25,6 +42,7 @@ export class Controller { my.settings = comp.settings ? comp.settings : pond.ComponentSettings.create(); my.status = comp.status ? comp.status : pond.ComponentStatus.create(); + my.on = Boolean(my.status.lastMeasurement?.measurement?.booleanOutput?.value); return my; diff --git a/src/models/GrainCable.ts b/src/models/GrainCable.ts index 70dc257..dff0dab 100644 --- a/src/models/GrainCable.ts +++ b/src/models/GrainCable.ts @@ -158,6 +158,10 @@ export class GrainCable { return this.humidities.reduce((p: any, c: any) => p + c, 0) / this.humidities.length; } + public aveMoisture() { + return this.grainMoistures.reduce((p: any, c: any) => p + c, 0) / this.grainMoistures.length; + } + public maxTemp(unit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS) { if (unit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) return this.farenheit(Math.max(...this.temperatures)); diff --git a/src/models/Headspace.ts b/src/models/Headspace.ts index bb4342b..51bd7fa 100644 --- a/src/models/Headspace.ts +++ b/src/models/Headspace.ts @@ -10,6 +10,7 @@ export class Headspace { public status: pond.ComponentStatus = pond.ComponentStatus.create(); public temperature: number = -127; public humidity: number = 200; + public lastReading: string = ""; public static create(comp: Component): Headspace { let my = new Headspace(); @@ -18,6 +19,9 @@ export class Headspace { if (comp.status.measurement.length > 0) { comp.status.measurement.forEach(um => { + if (um.timestamps[0]){ + my.lastReading = um.timestamps[0] + } if (um.values[0]) { if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { my.temperature = um.values[0].values[0]; @@ -39,6 +43,7 @@ export class Headspace { hum = humAndTemp["relativeHumidityTimes100"]; } } + my.lastReading = comp.status.lastMeasurement?.timestamp ?? "" my.temperature = or(temp, -1270) / 10; my.humidity = or(hum, 20000) / 100; } diff --git a/src/models/Plenum.ts b/src/models/Plenum.ts index 24378d3..b88b8a0 100644 --- a/src/models/Plenum.ts +++ b/src/models/Plenum.ts @@ -10,6 +10,7 @@ export class Plenum { public status: pond.ComponentStatus = pond.ComponentStatus.create(); public temperature: number = -127; public humidity: number = 200; + public lastReading: string = "" public static create(comp: Component): Plenum { let my = new Plenum(); @@ -18,6 +19,9 @@ export class Plenum { if (comp.status.measurement.length > 0) { comp.status.measurement.forEach(um => { + if (um.timestamps[0]) { + my.lastReading = um.timestamps[0]; + } if (um.values[0]) { if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { my.temperature = or(um.values[0].values[0], -127); @@ -41,6 +45,7 @@ export class Plenum { } my.temperature = or(temp, -1270) / 10; my.humidity = or(hum, 20000) / 100; + my.lastReading = comp.status.lastMeasurement?.timestamp ?? "" } return my; diff --git a/src/models/Pressure.ts b/src/models/Pressure.ts index fc07dfe..de0b189 100644 --- a/src/models/Pressure.ts +++ b/src/models/Pressure.ts @@ -9,7 +9,9 @@ export class Pressure { public settings: pond.ComponentSettings = pond.ComponentSettings.create(); public status: pond.ComponentStatus = pond.ComponentStatus.create(); public pascals: number = 0; + public airflow: number | undefined public fanId: number = 0; + public lastReading: string = "" public static create(comp: Component): Pressure { let my = new Pressure(); @@ -19,19 +21,23 @@ export class Pressure { //getting the value from the unitmeasurements in status instead of the old style measurements in the status if (comp.status.measurement.length > 0) { comp.status.measurement.forEach(um => { + if (um.timestamps[0]){ + my.lastReading = um.timestamps[0] + } if (um.values[0] && um.values[0].values.length > 0) { if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE) { my.pascals = um.values[0].values[0]; } //TODO-CS: could expand this to have the fan cfm in the pressure model as well - // if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){ - // my.fanCFM = um.values[0].values[0] - // } + if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_CFM){ + my.airflow = um.values[0].values[0] + } } }); } else { //if no unit measurements in status use the old measurements in status let pre = comp.status?.lastMeasurement?.measurement?.pressure?.pascals; + my.lastReading = comp.status.lastMeasurement?.timestamp ?? "" if (pre) { my.pascals = pre; } diff --git a/src/pages/Bin.tsx b/src/pages/Bin.tsx index f410bd2..8998fe8 100644 --- a/src/pages/Bin.tsx +++ b/src/pages/Bin.tsx @@ -215,11 +215,6 @@ export default function Bin(props: Props) { const [binPresets, setBinPresets] = useState([]); const [missedReadings, setMissedReadings] = useState(0); - //3d bin variables/toggle - const [showGrain, setShowGrain] = useState(false) - const [showHotspots, setShowHotspots] = useState(false) - const [showHeatmap, setShowHeatmap] = useState(false) - const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => { setValue(newValue); }; diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 3b2883a..1bc7f7f 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -48,6 +48,15 @@ function makePaletteColor(name: keyof typeof Colours) { export const getTheme = (mode: 'light' | 'dark') => createTheme({ + components: { + MuiPaper: { + styleOverrides: { + root: { + backgroundImage: 'none', // disables MUI's elevation overlay in dark mode + }, + }, + }, + }, ...baseTheme, palette: { ...baseTheme.palette, @@ -57,8 +66,8 @@ export const getTheme = (mode: 'light' | 'dark') => ...(mode === 'dark' ? { background: { - default: '#121212', - paper: '#1e1e1e', + default: '#0A1118', // was #0F1923 — push it darker + paper: '#141F2A', // was #1A2530 — slightly darker, less grey/blue }, } : {