added tons of files, got the bin page to render

This commit is contained in:
Carter 2025-03-12 12:41:20 -06:00
parent a027b8a96f
commit 9bbbf0940e
34 changed files with 10492 additions and 19 deletions

View file

@ -0,0 +1,451 @@
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<number>();
const [nodeHum, setNodeHum] = useState<number>();
const [nodeMoist, setNodeMoist] = useState<number | undefined>();
const [{ user }] = useGlobalState();
const tempDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE);
const humDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT);
const moistureDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC);
const [topNode, setTopNode] = 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<Interaction[]>([]);
const { openSnack } = useSnackbar();
const [newInteraction, setNewInteraction] = useState(false);
useEffect(() => {
setDisplayNode(selectedNode);
}, [selectedNode]);
useEffect(() => {
interactionAPI.listInteractionsByComponent(device.id(), cable.location()).then(resp => {
setCableInteractions(resp);
});
}, [cable, device, interactionAPI]);
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);
}
}, [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"])
.then(resp => {
openSnack("EMC set on cable " + cable.name());
updateComponentCallback(cable.key());
})
.catch(err => {});
};
const submit = () => {
componentAPI
.update(device.id(), cable.settings, [binKey], ["bin"])
.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)
.then(resp => {})
.catch(err => {});
});
})
.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)
.then(resp => {
openSnack("Updated cables top node");
updateComponentCallback(cable.key());
})
.catch(err => {});
setTopNode(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 (
<Box>
<Typography style={{ fontSize: 20, fontWeight: 650 }}>Latest Reading</Typography>
<Typography style={{ fontSize: 10 }}> {moment(cable.lastReading).fromNow()}</Typography>
<Grid container direction="row" spacing={2} style={{ padding: 10 }}>
<Grid item xs={6}>
<Card className={classes.readingCard}>
<Box display="flex" justifyContent="center" alignItems="center">
<TemperatureIcon />
<Typography style={{ color: tempDescriber.colour(), fontWeight: 650 }}>
{displayTemp() + tempDescriber.GetUnit()}
</Typography>
</Box>
<Typography style={{ textAlign: "center", color: "grey", fontWeight: 650 }}>
Temperature
</Typography>
</Card>
</Grid>
<Grid item xs={6}>
<Card className={classes.readingCard}>
<Box display="flex" justifyContent="center" alignItems="center">
<HumidityIcon />
<Typography style={{ color: humDescriber.colour(), fontWeight: 650 }}>
{nodeHum + humDescriber.GetUnit()}
</Typography>
</Box>
<Typography style={{ textAlign: "center", color: "grey", fontWeight: 650 }}>
Humidity
</Typography>
</Card>
</Grid>
<Grid item xs={6}>
{/* only show the emc if the bin grain type and the cable grain type match */}
{nodeMoist && cable.settings.grainType === grain ? (
<Card className={classes.readingCard}>
<Box display="flex" justifyContent="center" alignItems="center">
<HumidityIcon />
<Typography style={{ color: moistureDescriber.colour(), fontWeight: 650 }}>
{nodeMoist + moistureDescriber.GetUnit()}
</Typography>
</Box>
<Typography style={{ textAlign: "center", color: "grey", fontWeight: 650 }}>
Grain EMC
</Typography>
</Card>
) : (
<Card>
<CardActionArea
className={classes.readingCard}
onClick={() => {
setEMC();
}}>
<Box display="flex" justifyContent="center" alignItems="center">
<HumidityIcon />
</Box>
<Typography style={{ textAlign: "center", color: "grey", fontWeight: 650 }}>
Set EMC
</Typography>
</CardActionArea>
</Card>
)}
</Grid>
<Grid item xs={6}>
<Card>
<CardActionArea className={classes.readingCard} onClick={() => goToComponent()}>
<Box display="flex" justifyContent="center" alignItems="center">
<GraphIcon />
</Box>
<Typography style={{ textAlign: "center", color: "grey", fontWeight: 650 }}>
See Graphs
</Typography>
</CardActionArea>
</Card>
</Grid>
</Grid>
</Box>
);
};
const fillNodeSet = () => {
return (
<Box style={{ marginTop: 10 }}>
<Typography style={{ fontSize: 20, fontWeight: 650 }}>Top Node Control</Typography>
<FormControlLabel
style={{ marginTop: 10 }}
control={
<Checkbox
checked={topNode}
onChange={e => {
updateTopNode(e.target.checked);
}}
name="topNode"
/>
}
label={
<React.Fragment>
<Typography style={{ fontSize: 15, fontWeight: 650 }}>
Set as top node in grain
</Typography>
<Typography style={{ fontSize: 15 }}>
If checked this will set interactions to ignore all nodes above it. Interactions can
still be adjusted manually to use all nodes
</Typography>
</React.Fragment>
}
/>
</Box>
);
};
const interactionDisplay = () => {
let component = Component.create();
component.settings = cable.settings;
component.status = cable.status;
return (
<Box marginTop={2}>
<Typography style={{ fontSize: 20, fontWeight: 650 }}>Interactions</Typography>
<Box marginTop={1} marginBottom={1}>
<InteractionsOverview
component={component}
components={interactionComponents}
device={device}
interactions={cableInteractions}
permissions={permissions}
refreshCallback={() => {
interactionAPI
.listInteractionsByComponent(device.id(), cable.location())
.then(resp => {
setCableInteractions(resp);
});
}}
/>
</Box>
<Box display="flex" justifyContent="center">
<IconButton
color="primary"
disabled={!canWrite(permissions)}
aria-label="Add Interaction"
onClick={() => {
setNewInteraction(true);
}}
className={classes.greenButton}>
<AddIcon />
</IconButton>
</Box>
</Box>
);
};
return (
<React.Fragment>
<ResponsiveDialog open={open} onClose={() => closeDialog()} fullWidth={isMobile}>
<DialogTitle style={{ padding: 10 }}>
<Box display="flex" justifyContent="center" marginBottom={2}>
<Typography style={{ fontSize: 30, fontWeight: 650 }}>{cable.name()}</Typography>
</Box>
<AppBar className={classes.appBar} color="secondary">
<Grid
container
direction="row"
justifyContent="space-between"
alignItems="center"
wrap="nowrap">
<Grid item>
<Button onClick={() => displayPrev()}>
<ArrowBackIos style={{ color: "black" }} />
</Button>
</Grid>
<Grid item>
<Box>
<Typography align="center" variant="h5">
Node {displayNode}
</Typography>
</Box>
</Grid>
<Grid item>
<Button onClick={() => displayNext()}>
<ArrowForwardIos style={{ color: "black" }} />
</Button>
</Grid>
</Grid>
</AppBar>
</DialogTitle>
<DialogContent>
{latestReading()}
{fillNodeSet()}
{interactionDisplay()}
</DialogContent>
<DialogActions>
<Button onClick={closeDialog} color="primary">
Cancel
</Button>
<Button onClick={submit} color="primary">
Submit
</Button>
</DialogActions>
</ResponsiveDialog>
<InteractionSettings
isDialogOpen={newInteraction}
initialComponent={cable.asComponent()}
mode="add"
device={device}
components={interactionComponents}
closeDialogCallback={() => {
setNewInteraction(false);
}}
refreshCallback={() => {
interactionAPI.listInteractionsByComponent(device.id(), cable.location()).then(resp => {
setCableInteractions(resp);
});
}}
canEdit={canWrite(permissions)}
/>
</React.Fragment>
);
}