334 lines
11 KiB
TypeScript
334 lines
11 KiB
TypeScript
import {
|
|
Card,
|
|
CircularProgress,
|
|
Typography
|
|
} from "@mui/material";
|
|
import { blue, grey, red, yellow, orange } from "@mui/material/colors";
|
|
import BarGraph, { BarData } from "charts/BarGraph";
|
|
import { Bin } from "models";
|
|
import moment, { Moment } from "moment";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useBinAPI, useGlobalState } from "providers";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { Legend } from "recharts";
|
|
import { getGrainUnit } from "utils";
|
|
import BinLevelAreaGraph from "./BinLevelAreaGraph";
|
|
|
|
interface Props {
|
|
binLoading: boolean;
|
|
bin: Bin;
|
|
colour: string;
|
|
fertilizerBin: boolean;
|
|
startDate: Moment;
|
|
endDate: Moment;
|
|
customHeight?: number | string;
|
|
}
|
|
|
|
interface InventoryAt {
|
|
timestamp: number;
|
|
value: number;
|
|
}
|
|
|
|
export default function BinLevelOverTime(props: Props) {
|
|
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
|
|
const binAPI = useBinAPI();
|
|
const [{as}] = useGlobalState();
|
|
const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]);
|
|
const [dataLoading, setDataLoading] = useState(false);
|
|
const [capacity, setCapacity] = useState<number | undefined>();
|
|
|
|
const [modeData, setModeData] = useState<BarData[]>([]);
|
|
const [histDataLoading, setHistDataLoading] = useState(false);
|
|
|
|
const getFill = (mode: pond.BinMode) => {
|
|
let fill = "";
|
|
switch (mode) {
|
|
case pond.BinMode.BIN_MODE_STORAGE:
|
|
fill = yellow[500];
|
|
break;
|
|
case pond.BinMode.BIN_MODE_COOLDOWN:
|
|
fill = orange[500];
|
|
break;
|
|
case pond.BinMode.BIN_MODE_DRYING:
|
|
fill = red[500];
|
|
break;
|
|
case pond.BinMode.BIN_MODE_HYDRATING:
|
|
fill = blue[500];
|
|
break;
|
|
default:
|
|
fill = grey[500];
|
|
break;
|
|
}
|
|
return fill;
|
|
};
|
|
|
|
//loads the data from the bin history table
|
|
const loadHistoryData = useCallback(() => {
|
|
setDataLoading(true);
|
|
let cap = bin.settings.specs?.bushelCapacity;
|
|
if (fertilizerBin && cap) {
|
|
cap = cap * 35.239;
|
|
}
|
|
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
|
|
cap = cap / bin.bushelsPerTonne();
|
|
}
|
|
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
|
|
cap = cap / bin.bushelsPerTon();
|
|
}
|
|
setCapacity(cap);
|
|
binAPI
|
|
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
|
.then(resp => {
|
|
let data: BarData[] = [];
|
|
let lastBushels = -1;
|
|
let currentMode: pond.BinMode | undefined = undefined
|
|
let modeData: BarData[] = []
|
|
resp.data.history.forEach(hist => {
|
|
//build the data for the inventory bar graph
|
|
if (hist.settings?.inventory) {
|
|
let bushels = hist.settings.inventory.grainBushels ?? 0;
|
|
if (bushels !== lastBushels) {
|
|
let grainDisplay = bushels
|
|
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
|
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
|
|
}
|
|
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTon() > 1){
|
|
grainDisplay = Math.round((bushels / bin.bushelsPerTon()) * 100) / 100;
|
|
}
|
|
let newData: InventoryAt = {
|
|
timestamp: moment(hist.timestamp).valueOf(),
|
|
value: fertilizerBin
|
|
? Math.round(bushels * 35.239)
|
|
: grainDisplay
|
|
};
|
|
data.push(newData);
|
|
lastBushels = bushels;
|
|
}
|
|
}
|
|
//build the data for the mode change bar graph
|
|
let histBin = Bin.create()
|
|
histBin.settings = hist.settings ?? pond.BinSettings.create()
|
|
if(hist.settings && currentMode !== hist.settings.mode){
|
|
currentMode = hist.settings.mode
|
|
modeData.push({
|
|
timestamp: moment(hist.timestamp).valueOf(),
|
|
value: 1,
|
|
fill: getFill(currentMode)
|
|
})
|
|
}
|
|
});
|
|
let currentTime = moment().valueOf();
|
|
if (data.length === 0) {
|
|
let bushels = bin.bushels();
|
|
data.push({
|
|
value: fertilizerBin
|
|
? Math.round(bushels * 35.239)
|
|
: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
|
? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
|
|
: bushels,
|
|
timestamp: currentTime
|
|
});
|
|
}
|
|
if(modeData.length === 0){
|
|
modeData.push({
|
|
timestamp: currentTime,
|
|
value: 1,
|
|
fill: getFill(bin.settings.mode)
|
|
})
|
|
}
|
|
setInventoryData(data);
|
|
setModeData(modeData)
|
|
})
|
|
.finally(() => {
|
|
setDataLoading(false);
|
|
});
|
|
},[startDate, endDate, bin, fertilizerBin])
|
|
|
|
//loads the data from the measuremnts table as a unit measurement
|
|
const loadMeasurementData = useCallback(() => {
|
|
setDataLoading(true);
|
|
let cap = bin.settings.specs?.bushelCapacity;
|
|
if (fertilizerBin && cap) {
|
|
cap = cap * 35.239;
|
|
}
|
|
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && cap) {
|
|
cap = cap / bin.bushelsPerTonne();
|
|
}
|
|
setCapacity(cap);
|
|
let autoBarData: BarData[] = [];
|
|
|
|
//load the measurment data to build the bushel bars
|
|
binAPI
|
|
.listBinMeasurements(bin.key(), startDate, endDate, 500, 0, "asc")
|
|
.then(resp => {
|
|
let lastBushels = -1;
|
|
|
|
resp.data.measurements.forEach(objMeasurement => {
|
|
//set the bushel cable measurements
|
|
let m = pond.UnitMeasurementsForObject.fromObject(objMeasurement);
|
|
|
|
//val will be an array of only a single number since this is not a measurement that involves nodes
|
|
m.values.forEach((val, i) => {
|
|
if (val.values[0] && m.timestamps[i]) {
|
|
if (lastBushels !== val.values[0]) {
|
|
let grainDisplay = val.values[0]
|
|
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
|
|
grainDisplay = Math.round((grainDisplay / bin.bushelsPerTonne()) * 100) / 100;
|
|
}
|
|
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTon() > 1){
|
|
grainDisplay = Math.round((grainDisplay / bin.bushelsPerTon()) * 100) / 100;
|
|
}
|
|
autoBarData.push({
|
|
value: grainDisplay,
|
|
timestamp: moment(m.timestamps[i]).valueOf(),
|
|
});
|
|
lastBushels = val.values[0];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
if (autoBarData.length === 0) {
|
|
let bushels = bin.bushels();
|
|
let currentTime = moment().valueOf();
|
|
autoBarData.push({
|
|
value: fertilizerBin
|
|
? Math.round(bushels * 35.239)
|
|
: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
|
|
? Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100
|
|
: bushels,
|
|
timestamp: currentTime
|
|
});
|
|
}
|
|
setInventoryData(autoBarData);
|
|
})
|
|
.catch(err => {})
|
|
.finally(() => {
|
|
setDataLoading(false);
|
|
});
|
|
},[startDate, endDate, bin, fertilizerBin])
|
|
|
|
const modeDataFromHistory = useCallback(()=>{
|
|
if (histDataLoading) return;
|
|
setHistDataLoading(true)
|
|
binAPI
|
|
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
|
.then(resp => {
|
|
//loop through the history finding all the times the bin mode changed
|
|
let currentMode: pond.BinMode | undefined = undefined
|
|
let modeData: BarData[] = []
|
|
resp.data.history.forEach(hist => {
|
|
let histBin = Bin.create()
|
|
histBin.settings = hist.settings ?? pond.BinSettings.create()
|
|
if(hist.settings && currentMode !== hist.settings.mode){
|
|
currentMode = hist.settings.mode
|
|
modeData.push({
|
|
timestamp: moment(hist.timestamp).valueOf(),
|
|
value: 1,
|
|
fill: getFill(currentMode)
|
|
})
|
|
}
|
|
})
|
|
if(modeData.length === 0){
|
|
modeData.push({
|
|
timestamp: moment().valueOf(),
|
|
value: 1,
|
|
fill: getFill(bin.settings.mode)
|
|
})
|
|
}
|
|
setModeData(modeData)
|
|
})
|
|
.catch(err => {
|
|
|
|
})
|
|
.finally(()=>{
|
|
setHistDataLoading(false)
|
|
})
|
|
},[bin, startDate, endDate])
|
|
|
|
|
|
useEffect(() => {
|
|
//dont call a load function if loading is already in progress
|
|
if (dataLoading || bin.key() === "") return;
|
|
//check what is controlling the bin inventory
|
|
let control = bin.inventoryControl()
|
|
//for automatic lidar and cables get the data from measurements as an object measurement
|
|
if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
|
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART
|
|
){
|
|
//load the measurement data for the bar graph
|
|
loadMeasurementData()
|
|
//load the data for the storage mode changes
|
|
modeDataFromHistory()
|
|
|
|
//for manual and hybrid (lidar or in the future, cables) get the data from bin_history
|
|
}else if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL ||
|
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
|
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
|
|
){
|
|
loadHistoryData()
|
|
}
|
|
|
|
},[bin, loadMeasurementData, loadHistoryData])
|
|
|
|
const legend = () => {
|
|
return (
|
|
<Legend
|
|
verticalAlign="bottom"
|
|
payload={[
|
|
{ value: "Drying", type: "square", id: "drying", color: red[500] },
|
|
{ value: "Cooldown", type: "square", id: "cooldown", color: orange[500] },
|
|
{ value: "Storage", type: "square", id: "storage", color: yellow[500] },
|
|
{ value: "Hydrating", type: "square", id: "hydrating", color: blue[500] }
|
|
]}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const inventoryChart = () => {
|
|
if(inventoryData.length > 10){
|
|
return (
|
|
<BinLevelAreaGraph
|
|
data={inventoryData}
|
|
fill={colour}
|
|
/>
|
|
)
|
|
}else{
|
|
return (
|
|
<BarGraph
|
|
customHeight={customHeight}
|
|
data={inventoryData}
|
|
barColour={colour}
|
|
yMax={capacity}
|
|
yMin={0}
|
|
labelColour="white"
|
|
labels
|
|
useGradient
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
const binModeChart = () => {
|
|
return (
|
|
<BarGraph
|
|
data={modeData}
|
|
customHeight={100}
|
|
hideYAxis
|
|
graphLegend={legend()}
|
|
yMax={1}
|
|
yMin={0}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card raised style={{ padding: 5 }}>
|
|
<Typography style={{ fontSize: 25, fontWeight: 650, marginLeft: 20 }}>
|
|
Grain Levels Over Time
|
|
</Typography>
|
|
{dataLoading ? <CircularProgress /> : inventoryChart()}
|
|
{binModeChart()}
|
|
</Card>
|
|
);
|
|
}
|