refactor the bin level over time

the auto inventory stuff pulls from the measurements and the manual/hybrid stuff pulls from the history, the auto will also create a new chart underneath for the bin modes
This commit is contained in:
csawatzky 2025-07-07 14:11:30 -06:00
parent 54a826bc2d
commit 67cafbe2ed
2 changed files with 133 additions and 96 deletions

View file

@ -13,7 +13,7 @@ import { Bin } from "models";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment";
import { pond, quack } from "protobuf-ts/pond"; import { pond, quack } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers"; import { useBinAPI, useGlobalState } from "providers";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Legend } from "recharts"; import { Legend } from "recharts";
import { getGrainUnit } from "utils"; import { getGrainUnit } from "utils";
@ -31,24 +31,13 @@ export default function BinLevelOverTime(props: Props) {
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props; const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
const binAPI = useBinAPI(); const binAPI = useBinAPI();
const [{as}] = useGlobalState(); const [{as}] = useGlobalState();
const [showCable, setShowCable] = useState(false); const [data, setData] = useState<BarData[]>([]);
const [manualData, setManualData] = useState<BarData[]>([]);
const [cableData, setCableData] = useState<BarData[]>([]);
const [modeAreas, setModeAreas] = useState<RefArea[]>([]); const [modeAreas, setModeAreas] = useState<RefArea[]>([]);
// const defaultDateRange = GetDefaultDateRange();
// const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
// const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
const [dataLoading, setDataLoading] = useState(false); const [dataLoading, setDataLoading] = useState(false);
const [cableDataLoading, setCableDataLoading] = useState(false);
const [capacity, setCapacity] = useState<number | undefined>(); const [capacity, setCapacity] = useState<number | undefined>();
// const updateDateRange = (newStartDate: any, newEndDate: any) => { const [modeData, setModeData] = useState<BarData[]>([]);
// let range = GetDefaultDateRange(); const [histDataLoading, setHistDataLoading] = useState(false);
// range.start = newStartDate;
// range.end = newEndDate;
// setStartDate(newStartDate);
// setEndDate(newEndDate);
// };
const getFill = (mode: pond.BinMode) => { const getFill = (mode: pond.BinMode) => {
let fill = ""; let fill = "";
@ -72,8 +61,8 @@ export default function BinLevelOverTime(props: Props) {
return fill; return fill;
}; };
useEffect(() => { //loads the data from the bin history table
if (dataLoading || bin.key() === "") return; const loadHistoryData = useCallback(() => {
setDataLoading(true); setDataLoading(true);
let cap = bin.settings.specs?.bushelCapacity; let cap = bin.settings.specs?.bushelCapacity;
if (fertilizerBin && cap) { if (fertilizerBin && cap) {
@ -146,48 +135,125 @@ export default function BinLevelOverTime(props: Props) {
fill: getFill(bin.settings.mode) fill: getFill(bin.settings.mode)
}); });
} }
setManualData(data); setData(data);
setModeAreas(modeAreas); setModeAreas(modeAreas);
}) })
.finally(() => { .finally(() => {
setDataLoading(false); setDataLoading(false);
}); });
}, [binAPI, bin, startDate, endDate, fertilizerBin]); // eslint-disable-line react-hooks/exhaustive-deps },[startDate, endDate, bin, fertilizerBin])
useEffect(() => { //loads the data from the measuremnts table as a unit measurement
if (cableDataLoading || bin.key() === "") return; const loadMeasurementData = useCallback(() => {
setCableDataLoading(true); 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 binAPI
.listBinMeasurements(bin.key(), startDate, endDate, 500, 0, "asc", undefined, undefined, undefined, undefined, as) .listBinMeasurements(bin.key(), startDate, endDate, 500, 0, "asc")
.then(resp => { .then(resp => {
let lastBushels = -1;
resp.data.measurements.forEach(objMeasurement => { resp.data.measurements.forEach(objMeasurement => {
//set the bushel cable measurements //set the bushel cable measurements
let m = pond.UnitMeasurementsForObject.fromObject(objMeasurement); let m = pond.UnitMeasurementsForObject.fromObject(objMeasurement);
if (m.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_CABLE) {
let cableBarData: BarData[] = [];
let lastBushels = -1;
//val will be an array of only a single number since this is not a measurement that involves nodes //val will be an array of only a single number since this is not a measurement that involves nodes
m.values.forEach((val, i) => { m.values.forEach((val, i) => {
if (val.values[0] && m.timestamps[i]) { if (val.values[0] && m.timestamps[i]) {
if (lastBushels !== val.values[0]) { if (lastBushels !== val.values[0]) {
cableBarData.push({ autoBarData.push({
value: val.values[0], value: val.values[0],
timestamp: moment(m.timestamps[i]).valueOf() timestamp: moment(m.timestamps[i]).valueOf(),
}); });
lastBushels = val.values[0]; lastBushels = val.values[0];
} }
} }
}); });
setCableData(cableBarData);
}
}); });
if (autoBarData.length === 0) {
let bushels = bin.settings.inventory?.grainBushels ?? 0;
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
});
}
setData(autoBarData);
}) })
.catch(err => {}) .catch(err => {})
.finally(() => { .finally(() => {
setCableDataLoading(false); setDataLoading(false);
}); });
}, [binAPI, bin, startDate, endDate, fertilizerBin, as]); // eslint-disable-line react-hooks/exhaustive-deps },[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)
})
}
})
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
){
//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 = () => { const legend = () => {
return ( return (
@ -203,15 +269,16 @@ export default function BinLevelOverTime(props: Props) {
); );
}; };
const manualChart = () => { const inventoryChart = () => {
return ( return (
<BarGraph <BarGraph
customHeight={customHeight} customHeight={customHeight}
data={manualData} data={data}
refAreas={modeAreas} refAreas={modeAreas}
barColour={colour} barColour={colour}
yMax={capacity} yMax={capacity}
graphLegend={legend()} yMin={0}
graphLegend={modeAreas.length > 0 ? legend() : undefined}
labelColour="white" labelColour="white"
labels labels
useGradient useGradient
@ -219,62 +286,28 @@ export default function BinLevelOverTime(props: Props) {
); );
}; };
const cableChart = () => { const autoBinModeChart = () => {
if (cableData.length === 0) {
return (
<Box height={customHeight} paddingTop={"25%"}>
<Typography style={{ textAlign: "center", fontSize: 25, fontWeight: 650 }}>
No cable estimates for selected time frame
</Typography>
</Box>
);
}
return ( return (
<BarGraph <BarGraph
customHeight={customHeight} data={modeData}
data={cableData} customHeight={100}
//refAreas={modeAreas} hideYAxis
barColour={colour}
yMax={capacity}
yMin={0}
graphLegend={legend()} graphLegend={legend()}
labelColour="white" yMax={1}
labels yMin={0}
useGradient
/> />
); )
}; }
return ( return (
<Card raised style={{ padding: 5 }}> <Card raised style={{ padding: 5 }}>
<Grid
container
direction="row"
alignContent="center"
alignItems="center"
justifyContent="space-between">
<Grid>
<Typography style={{ fontSize: 25, fontWeight: 650, marginLeft: 20 }}> <Typography style={{ fontSize: 25, fontWeight: 650, marginLeft: 20 }}>
Grain Levels Over Time Grain Levels Over Time
</Typography> </Typography>
</Grid> {dataLoading ? <CircularProgress /> : inventoryChart()}
<Grid> {/* when using auto will need to make a new chart just for the bin modes since the auto inventory comes from another place */}
<FormControlLabel {(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
control={ bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) && autoBinModeChart()}
<Switch
checked={showCable}
onChange={(_, checked) => {
setShowCable(checked);
}}
color="primary"
/>
}
label="Cable Estimates"
/>
</Grid>
</Grid>
{dataLoading ? <CircularProgress /> : showCable ? cableChart() : manualChart()}
</Card> </Card>
); );
} }

View file

@ -15,6 +15,7 @@ import {
export interface BarData { export interface BarData {
timestamp: number; timestamp: number;
value: number; value: number;
fill?: string;
} }
export interface RefArea { export interface RefArea {
@ -36,8 +37,9 @@ export interface AxisLabel {
interface Props { interface Props {
data: BarData[]; data: BarData[];
barColour: string; barColour?: string;
refAreas?: RefArea[]; refAreas?: RefArea[];
hideYAxis?: boolean;
yMin?: number; yMin?: number;
yMax?: number; yMax?: number;
graphLegend?: JSX.Element; graphLegend?: JSX.Element;
@ -60,6 +62,7 @@ export default function BarGraph(props: Props) {
data, data,
barColour, barColour,
refAreas, refAreas,
hideYAxis,
yMin, yMin,
yMax, yMax,
graphLegend, graphLegend,
@ -103,6 +106,7 @@ export default function BarGraph(props: Props) {
{graphLegend} {graphLegend}
<YAxis <YAxis
domain={[yMin ?? "dataMin", yMax ?? "auto"]} domain={[yMin ?? "dataMin", yMax ?? "auto"]}
hide={hideYAxis}
interval="preserveStartEnd" interval="preserveStartEnd"
scale={"linear"} scale={"linear"}
label={yLabel} label={yLabel}