import { Avatar, Box, Button, Card, CircularProgress, darken, DialogActions, DialogContent, DialogTitle, Grid2 as Grid, IconButton, lighten, Link, Skeleton, Slider, Switch, TextField, Theme, Typography, useTheme, } from "@mui/material"; import FullscreenIcon from "@mui/icons-material/Fullscreen"; import FullscreenExitIcon from "@mui/icons-material/FullscreenExit"; import RefreshIcon from "@mui/icons-material/Refresh"; import HumidityIcon from "component/HumidityIcon"; import TemperatureIcon from "component/TemperatureIcon"; import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber"; import useViewport from "hooks/useViewport"; import { round } from "lodash"; import { Bin, Component, Device } from "models"; import { GetComponentIcon } from "pbHelpers/ComponentType"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { useComponentAPI, useMobile } from "hooks"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { useGlobalState, useSnackbar } from "providers"; import React, { useCallback, useEffect, useState } from "react"; import moment, { Moment } from "moment"; import ResponsiveDialog from "common/ResponsiveDialog"; import { getThemeType } from "theme"; import { or } from "utils"; import { useBinAPI } from "providers/pond/binAPI"; import BindaptIcon from "products/Bindapt/BindaptIcon"; import { AccessTime, ArrowForwardIos, CheckCircleOutline, Error, InfoOutlined, TrendingDown, TrendingFlat, TrendingUp, Warning } from "@mui/icons-material"; import { Plenum } from "models/Plenum"; import { Pressure } from "models/Pressure"; import { GrainCable } from "models/GrainCable"; import GrainNodeInteractions from "./GrainNodeInteractions"; import GrainTransaction from "grain/GrainTransaction"; import { DevicePreset } from "models/DevicePreset"; import BinSVGV2 from "./BinSVGV2"; import AerationFanIcon from "products/AgIcons/AerationFanIcon"; import ObjectHeaterIcon from "products/Construction/ObjectHeaterIcon"; import { Ambient } from "models/Ambient"; import { ExtractMoisture } from "grain"; import SearchSelect, { Option } from "common/SearchSelect"; import Edit from "@mui/icons-material/Edit"; import { makeStyles } from "@mui/styles"; import ButtonGroup from "common/ButtonGroup"; import ModeChangeDialog from "./conditioning/modeChangeDialog"; import CustomGrainSelector from "grain/CustomGrainSelector"; import BinControllerDisplay from "./BinControllerDisplay"; import { useFullScreen } from "hooks/FullScreenHandle"; const useStyles = makeStyles((theme: Theme) => { return ({ cardContent: { padding: theme.spacing(1), paddingRight: theme.spacing(0) }, bgItem: { background: darken(theme.palette.background.paper, getThemeType() === "light" ? 0.05 : 0.25), borderRadius: theme.shape.borderRadius, width: "auto", marginRight: theme.spacing(1), padding: 5 }, displayBoxBinLeft: { background: darken(theme.palette.background.default, 0.05), borderRadius: 5, marginRight: -70, //negative margin to extend the box behind the bin svg paddingRight: 80, //padding to offset the text and ignore the extension paddingTop: 5, paddingBottom: 5, paddingLeft: 5 }, displayBox: { background: darken(theme.palette.background.default, 0.05), borderRadius: 5, padding: 5 }, controllerDisplay: { display: "flex", justifyContent: "space-between", paddingLeft: 5, paddingRight: 5 }, days: { margin: theme.spacing(0.5), fontSize: "16px" }, avatarIcon: { background: "transparent", width: theme.spacing(3), height: theme.spacing(3), margin: "auto", marginBottom: "auto" }, selected: { backgroundColor: "gold", "&.selected": { backgroundColor: "gold" } }, extraDetails: { padding: 1, marginTop: theme.spacing(1), display: "flex", width: "100%", flexGrow: 1, height: "100%" }, grainOVerlay: { position: "absolute", height: "10%", width: "100%", textAlign: "center", textShadow: "4px 4px 10px black" }, grainDiff: { position: "absolute", top: "10%" }, lightBox: { background: lighten(theme.palette.background.default, 0.2), borderRadius: 10, padding: 10, margin: 10 }, bottomSpacing: { marginBottom: theme.spacing(1) }, sliderValLabel: { height: 30, width: 30, left: -5, top: -20, background: "transparent" }, }); }); const average = (arr: any) => arr.reduce((p: any, c: any) => p + c, 0) / arr.length; interface GrainConditions { tempC: number; humidity: number; emc: number; tempCTrend: number; humidityTrend: number; emcTrend: number; } interface CombinedPlenum { tempHumidity?: Plenum, pressure?: Pressure } interface Props { bin: Bin; loading: boolean; components: Map; componentDevices: Map; devices: Device[]; plenums?: Plenum[]; ambient?: Ambient; cables?: GrainCable[]; pressures?: Pressure[]; refresh: (showSnack?: boolean) => void; afterUpdate?(): void; preferences?: Map; permissions?: pond.Permission[]; updateComponentCallback: (componentKey: string) => void; binPresets: DevicePreset[]; } export default function BinVisualizer(props: Props) { const { bin, plenums, ambient, pressures, loading, cables, components, devices, preferences, componentDevices, permissions, refresh, updateComponentCallback, binPresets } = props; const binAPI = useBinAPI(); const [{as}] = useGlobalState() const isMobile = useMobile(); const classes = useStyles(); const theme = useTheme(); const {fullScreenHandler, FullScreenWrapper} = useFullScreen() const viewport = useViewport(); const { openSnack } = useSnackbar(); const [fillPercentage, setFillPercentage] = useState(0); const [lidarBushels, setLidarBushels] = useState(); const [lidarPercentage, setLidarPercentage] = useState(); const [pendingGrainAmount, setPendingGrainAmount] = useState(); const tempColour = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ).colour(); const humidColour = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, quack.ComponentType.COMPONENT_TYPE_DHT ).colour(); const pressColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE).colour(); const emcColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour(); // const [showInputMoisture, setShowInputMoisture] = useState(false); const [targetMoisture, setTargetMoisture] = useState(0); const [outdoorTemp, setOutdoorTemp] = useState(0); const [outdoorHumidity, setOutdoorHumidity] = useState(0); const [modeTime, setModeTime] = useState(moment()); const [grainUpdate, setGrainUpdate] = useState(false); const [openStorageTime, setOpenStorageTime] = useState(false); const [sliderColour, setSliderCoulour] = useState("gold"); const [grainDiff, setGrainDiff] = useState(0); const [grainCables, setGrainCables] = useState([]); const iOS = typeof window !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent); const [nodeDetails, setNodeDetails] = useState(false); const [cfmLowOpen, setCFMLowOpen] = useState(false); const [cfmHighOpen, setCFMHighOpen] = useState(false); const [{ user }] = useGlobalState(); // const [newPreset, setNewPreset] = useState(pond.BinMode.BIN_MODE_NONE); const [newBinMode, setNewBinMode] = useState(pond.BinMode.BIN_MODE_NONE); const [selectedCable, setSelectedCable] = useState(); const [openNodeDialog, setOpenNodeDialog] = useState(false); const [cableDevice, setCableDevice] = useState(); const [selectedNode, setSelectedNode] = useState(0); const [devMap, setDevMap] = useState>(new Map()); //the cables with the highest and lowest temp nodes //const [highTempCable, setHighTempCable] = useState(); //const [lowTempCable, setLowTempCable] = useState(); //the switch value to determine what to display in the grain condition box const [valueDisplay, setValueDisplay] = useState<"low" | "avg" | "high">("high"); //the variables to be able to change the grain type through a unique dialog outside of the settings const [grainChangeDialog, setGrainChangeDialog] = useState(false); const grainOptions = GrainOptions(); const [isCustomInventory, setIsCustomInventory] = useState(false); const [grainOption, setGrainOption] = useState