added tons of files, got the bin page to render
This commit is contained in:
parent
a027b8a96f
commit
9bbbf0940e
34 changed files with 10492 additions and 19 deletions
451
src/bin/GrainNodeInteractions.tsx
Normal file
451
src/bin/GrainNodeInteractions.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue