import { AppBar, Box, Button, Card, CardActionArea, Checkbox, DialogActions, DialogContent, DialogTitle, FormControlLabel, Grid, IconButton, Typography } from "@mui/material"; import { ArrowBackIos, ArrowForwardIos } from "@mui/icons-material"; import HumidityIcon from "component/HumidityIcon"; import TemperatureIcon from "component/TemperatureIcon"; import { useMobile, useSnackbar } from "hooks"; import InteractionSettings from "interactions/InteractionSettings"; import InteractionsOverview from "interactions/InteractionsOverview"; import { clone } from "lodash"; import { Component, Device } from "models"; import { GrainCable } from "models/GrainCable"; import { Interaction } from "models/Interaction"; import moment from "moment"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { canWrite } from "pbHelpers/Permission"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { useBinAPI, useComponentAPI, useGlobalState, useInteractionsAPI } from "providers"; import AddIcon from "@mui/icons-material/AddCircle"; import React, { useEffect, useState } from "react"; // import { useHistory } from "react-router"; import { green } from "@mui/material/colors"; import ResponsiveDialog from "common/ResponsiveDialog"; import GraphIcon from "products/CommonIcons/graphIcon"; import { makeStyles } from "@mui/styles"; import { useNavigate } from "react-router-dom"; interface Props { open: boolean; device: Device; cable: GrainCable; binKey: string; selectedNode: number; close: () => void; grain: pond.Grain; interactionComponents: Component[]; permissions: pond.Permission[]; updateComponentCallback: (componentKey: string) => void; } const useStyles = makeStyles(() => { return ({ dialog: { maxWidth: 350 }, appBar: { position: "static", borderRadius: 30 }, readingCard: { padding: 10 }, greenButton: { color: green["600"], padding: 0 } }); }); export default function GrainNodeInteractions(props: Props) { const { open, selectedNode, device, cable, close, binKey, grain, interactionComponents, permissions, updateComponentCallback } = props; const [nodeTemp, setNodeTemp] = useState(); const [nodeHum, setNodeHum] = useState(); const [nodeMoist, setNodeMoist] = useState(); const [{ user, as }] = useGlobalState(); const tempDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, undefined, undefined, user); const humDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, undefined, undefined, user); const moistureDescriber = describeMeasurement( quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC, quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, undefined, undefined, user); const [topNode, setTopNode] = useState(false); const [excluded, setExcluded] = useState(false); const componentAPI = useComponentAPI(); const interactionAPI = useInteractionsAPI(); const binAPI = useBinAPI(); const [displayNode, setDisplayNode] = useState(0); const classes = useStyles(); const isMobile = useMobile(); // const history = useHistory(); const navigate = useNavigate() const [cableInteractions, setCableInteractions] = useState([]); const { openSnack } = useSnackbar(); const [newInteraction, setNewInteraction] = useState(false); useEffect(() => { setDisplayNode(selectedNode); }, [selectedNode]); useEffect(() => { interactionAPI.listInteractionsByComponent(device.id(), cable.location(), undefined, as).then(resp => { setCableInteractions(resp); }); }, [cable, device, interactionAPI, as]); useEffect(() => { //do the reversing to make the selected node match the array in the let revTemps = clone(cable.temperatures); revTemps.reverse(); setNodeTemp(revTemps[displayNode - 1]); let revHums = clone(cable.humidities); revHums.reverse(); setNodeHum(revHums[displayNode - 1]); let revMoist = clone(cable.grainMoistures); revMoist.reverse(); setNodeMoist(revMoist[displayNode - 1]); //determine whether the node being looked at is the active "top fill" node if (displayNode === cable.settings.grainFilledTo) { setTopNode(true); } else { setTopNode(false); } //determine if the node is excluded if (cable.excludedNodes.includes(displayNode - 1)){ setExcluded(true) }else{ setExcluded(false) } }, [cable, displayNode]); const goToComponent = () => { navigate("/devices/" + device.id() + "/components/" + cable.key()); }; const closeDialog = () => { setDisplayNode(selectedNode); close(); }; const setEMC = () => { let settings = cable.settings; settings.defaultMutations = [pond.Mutator.MUTATOR_EMC]; settings.grainType = grain; componentAPI .update(device.id(), settings, [binKey], ["bin"], as) .then(resp => { openSnack("EMC set on cable " + cable.name()); updateComponentCallback(cable.key()); }) .catch(err => {}); }; const submit = () => { componentAPI .update(device.id(), cable.settings, [binKey], ["bin"], as) .then(resp => { //after updating the component update the interactions with the new subtypes //TODO-CS: make function to update multiple interactions at once to avoid this loop if it becomes a problem cableInteractions.forEach(interaction => { interactionAPI .updateInteraction(device.id(), interaction.settings, as) .then(resp => {}) .catch(err => {}); }); updateComponentCallback(cable.key()) }) .catch(err => { openSnack("Failed to update component"); }); closeDialog(); }; const updateTopNode = (checked: boolean) => { if (topNode && !checked) { cable.settings.grainFilledTo = 0; cableInteractions.forEach(interaction => { interaction.settings.subtype = 0; interaction.settings.nodeOne = 0; interaction.settings.nodeTwo = 0; }); } else if (checked) { cable.settings.grainFilledTo = displayNode; cableInteractions.forEach(interaction => { interaction.settings.subtype = Interaction.upToSubtype(displayNode); interaction.settings.nodeOne = displayNode; }); } let pref = pond.BinComponentPreferences.create({ type: pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE, node: cable.settings.grainFilledTo }); //update the bins preferences to have the new top node for the cable binAPI .updateComponentPreferences(binKey, cable.key(), pref, as) .then(resp => { openSnack("Updated cables top node"); updateComponentCallback(cable.key()); }) .catch(err => {}); setTopNode(checked); }; const updateNodeExclusion = (checked: boolean) => { if (checked) { cable.settings.excludedNodes.push(selectedNode-1) } else { cable.settings.excludedNodes.splice(cable.settings.excludedNodes.indexOf(selectedNode - 1), 1) } setExcluded(checked) } const displayTemp = () => { let tempFinal = nodeTemp ?? 0; if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { tempFinal = tempFinal * 1.8 + 32; } return tempFinal.toFixed(2); }; const displayNext = () => { let i = displayNode; if (i === cable.temperatures.length) { i = 1; } else { i = i + 1; } setDisplayNode(i); }; const displayPrev = () => { let i = displayNode; if (i === 1) { i = cable.temperatures.length; } else { i = i - 1; } setDisplayNode(i); }; const latestReading = () => { return ( Latest Reading {moment(cable.lastReading).fromNow()} {displayTemp() + tempDescriber.GetUnit()} Temperature {nodeHum + humDescriber.GetUnit()} Humidity {/* only show the emc if the bin grain type and the cable grain type match */} {nodeMoist && cable.settings.grainType === grain ? ( {nodeMoist + moistureDescriber.GetUnit()} Grain EMC ) : ( { setEMC(); }}> Set EMC )} goToComponent()}> See Graphs ); }; const nodeControl = () => { return ( Node Control { updateNodeExclusion(e.target.checked); }} name="excludedNode" /> } label={ Exclude Node If checked this will set the node to be disabled and will not be used for any calculations done for the bin } /> { updateTopNode(e.target.checked); }} name="topNode" /> } label={ Set as top node in grain If checked this will set interactions to ignore all nodes above it. Interactions can still be adjusted manually to use all nodes } /> ); }; const interactionDisplay = () => { let component = Component.create(); component.settings = cable.settings; component.status = cable.status; return ( Interactions { interactionAPI .listInteractionsByComponent(device.id(), cable.location(), undefined, as) .then(resp => { setCableInteractions(resp); }); }} /> { setNewInteraction(true); }} className={classes.greenButton}> ); }; return ( closeDialog()} fullWidth={isMobile}> {cable.name()} Node {displayNode} {latestReading()} {nodeControl()} {interactionDisplay()} { setNewInteraction(false); }} refreshCallback={() => { interactionAPI.listInteractionsByComponent(device.id(), cable.location(), undefined, as).then(resp => { setCableInteractions(resp); }); }} canEdit={canWrite(permissions)} /> ); }