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([]); const [dataLoading, setDataLoading] = useState(false); const [capacity, setCapacity] = useState(); const [modeData, setModeData] = useState([]); 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 ( ); }; const inventoryChart = () => { if(inventoryData.length > 10){ return ( ) }else{ return ( ); } }; const binModeChart = () => { return ( ) } return ( Grain Levels Over Time {dataLoading ? : inventoryChart()} {binModeChart()} ); }