diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index 5f4d93a..a425ea6 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -24,7 +24,7 @@ import RemoveSelfFromObject from "user/RemoveSelfFromObject"; import ShareObject from "user/ShareObject"; import { isOffline } from "utils/environment"; import ObjectTeams from "teams/ObjectTeams"; -// import BinDuplication from "./BinDuplication"; +import BinDuplication from "./BinDuplication"; import BinsIcon from "products/Bindapt/BinsIcon"; import HelpIcon from "@mui/icons-material/Help"; import BinSensors from "./BinSensors"; @@ -269,14 +269,14 @@ export default function BinActions(props: Props) { closeDialogCallback={() => setOpenState({ ...openState, users: false })} refreshCallback={refreshCallback} /> - {/* { setOpenState({ ...openState, duplication: false }); }} bin={bin} refreshCallback={refreshCallback} - /> */} + /> )} - {/* - - */} void; + refreshCallback: () => void; +} + +export default function BinDuplication(props: Props) { + const { open, bin, closeDialog, refreshCallback } = props; + const [binDupName, setBinDupName] = useState("(copy of) " + bin.name()); + const binAPI = useBinAPI(); + const { openSnack } = useSnackbar(); + + useEffect(() => { + setBinDupName("(copy of) " + bin.name()); + }, [bin]); + + const duplicate = () => { + let settings = bin.settings; + settings.name = binDupName; + binAPI + .addBin(settings) + .then(resp => { + openSnack("Successfully duplicated bin"); + }) + .catch(err => { + openSnack("Failed to duplicate bin"); + }); + refreshCallback(); + closeDialog(); + }; + + return ( + + Create Duplicate Bin + + + This will create a new bin with the same settings as {bin.name()}. + + setBinDupName(e.target.value)} + /> + + + + + + + ); +} diff --git a/src/bin/BinSettings.tsx b/src/bin/BinSettings.tsx index 2c97be1..f970b2b 100644 --- a/src/bin/BinSettings.tsx +++ b/src/bin/BinSettings.tsx @@ -374,7 +374,6 @@ export default function BinSettings(props: Props) { let options: Option[] = [] libracartAPI.listDestinations(0,0,undefined, as) .then(resp=>{ - console.log(resp) //set the options for the search select resp.data.destinations.forEach(d => { let newOp: Option = { @@ -486,7 +485,6 @@ export default function BinSettings(props: Props) { form.inventory.inventoryControl = inventoryControl form.inventory.autoThreshold = autoFillThreshold } - console.log(form) binAPI .updateBin(bin.key(), form, as) .then(response => { diff --git a/src/bin/BinsList.tsx b/src/bin/BinsList.tsx index a9e517a..3ae7428 100644 --- a/src/bin/BinsList.tsx +++ b/src/bin/BinsList.tsx @@ -30,7 +30,6 @@ const useStyles = makeStyles((_theme) => { position: "relative", minHeight: "233px", height: "auto !important", - width: "184px", padding: 2 }, hidden: { @@ -113,7 +112,7 @@ export default function BinsList(props: Props) { return ( {bins.map((b, i) => - isMobile ? ( + ( - ) : ( - { - !duplicate && goToBin(i) - }}> - - ) )} diff --git a/src/bin/graphs/BinComponentGraph.tsx b/src/bin/graphs/BinComponentGraph.tsx index 6b593a9..3c5a625 100644 --- a/src/bin/graphs/BinComponentGraph.tsx +++ b/src/bin/graphs/BinComponentGraph.tsx @@ -184,7 +184,7 @@ export default function BinComponentGraph(props: Props) { UnitMeasurement.create(m, user)) + lastMeasurement.map(m => UnitMeasurement.create(m, user)) )} /> } diff --git a/src/bin/graphs/BinLevelAreaGraph.tsx b/src/bin/graphs/BinLevelAreaGraph.tsx new file mode 100644 index 0000000..fc13182 --- /dev/null +++ b/src/bin/graphs/BinLevelAreaGraph.tsx @@ -0,0 +1,67 @@ +import { useTheme } from "@mui/material"; +import MaterialChartTooltip from "charts/MaterialChartTooltip"; +import moment from "moment"; +import { Area, AreaChart, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts"; + +export interface LevelAreaData { + timestamp: number; + value: number; +} + +interface Props { + data: LevelAreaData[] + fill: string + customHeight?: string | number +} + +export default function BinLevelAreaGraph(props: Props) { + const { data, customHeight, fill } = props + const theme = useTheme(); + const now = moment(); + + + return ( + + + { + let t = moment(timestamp); + return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD"); + }} + scale="time" + type="number" + tick={{ fill: theme.palette.text.primary }} + stroke={theme.palette.divider} + interval="preserveStartEnd" + /> + + + moment(timestamp).format("lll")} + content={(props: TooltipProps) => { + return ( + { + return value + " Bushels"; + }} + /> + ); + }} + /> + + + ) +} \ No newline at end of file diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 63bb51d..d9c98cf 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -1,21 +1,18 @@ import { - Box, Card, CircularProgress, - FormControlLabel, - Grid, - Switch, Typography } from "@mui/material"; import { blue, grey, red, yellow, orange } from "@mui/material/colors"; -import BarGraph, { BarData, RefArea } from "charts/BarGraph"; +import BarGraph, { BarData } from "charts/BarGraph"; import { Bin } from "models"; import moment, { Moment } from "moment"; -import { pond, quack } from "protobuf-ts/pond"; +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; @@ -27,12 +24,16 @@ interface Props { 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 [data, setData] = useState([]); - const [modeAreas, setModeAreas] = useState([]); + const [inventoryData, setInventoryData] = useState([]); const [dataLoading, setDataLoading] = useState(false); const [capacity, setCapacity] = useState(); @@ -76,33 +77,15 @@ export default function BinLevelOverTime(props: Props) { .listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString()) .then(resp => { let data: BarData[] = []; - let modeAreas: RefArea[] = []; let lastBushels = -1; - - //values for ref areas - let x1 = 0; - let x2 = 0; - let y1 = -50; - let y2 = -200; - - let currentMode: pond.BinMode; - + 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 settings = pond.BinSettings.fromObject(hist.settings); let bushels = hist.settings.inventory.grainBushels ?? 0; - if (bushels !== lastBushels || currentMode !== settings.mode) { - x1 = moment(hist.timestamp).valueOf(); - x2 = moment(hist.timestamp).valueOf(); - modeAreas.push({ - x1: x1, - x2: x2, - y1: cap ? cap * -0.1 : y1, - y2: cap ? cap * -0.2 : y2, - fill: getFill(settings.mode) - }); - currentMode = settings.mode; - let newData: BarData = { + if (bushels !== lastBushels) { + let newData: InventoryAt = { timestamp: moment(hist.timestamp).valueOf(), value: fertilizerBin ? Math.round(bushels * 35.239) @@ -114,10 +97,21 @@ export default function BinLevelOverTime(props: Props) { 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.settings.inventory?.grainBushels ?? 0; - let currentTime = moment().valueOf(); + let bushels = bin.bushels(); data.push({ value: fertilizerBin ? Math.round(bushels * 35.239) @@ -126,17 +120,16 @@ export default function BinLevelOverTime(props: Props) { : bushels, timestamp: currentTime }); - - modeAreas.push({ - x1: currentTime, - x2: currentTime, - y1: cap ? cap * -0.1 : y1, - y2: cap ? cap * -0.2 : y2, - fill: getFill(bin.settings.mode) - }); } - setData(data); - setModeAreas(modeAreas); + if(modeData.length === 0){ + modeData.push({ + timestamp: currentTime, + value: 1, + fill: getFill(bin.settings.mode) + }) + } + setInventoryData(data); + setModeData(modeData) }) .finally(() => { setDataLoading(false); @@ -180,7 +173,7 @@ export default function BinLevelOverTime(props: Props) { }); }); if (autoBarData.length === 0) { - let bushels = bin.settings.inventory?.grainBushels ?? 0; + let bushels = bin.bushels(); let currentTime = moment().valueOf(); autoBarData.push({ value: fertilizerBin @@ -191,7 +184,7 @@ export default function BinLevelOverTime(props: Props) { timestamp: currentTime }); } - setData(autoBarData); + setInventoryData(autoBarData); }) .catch(err => {}) .finally(() => { @@ -220,6 +213,13 @@ export default function BinLevelOverTime(props: Props) { }) } }) + if(modeData.length === 0){ + modeData.push({ + timestamp: moment().valueOf(), + value: 1, + fill: getFill(bin.settings.mode) + }) + } setModeData(modeData) }) .catch(err => { @@ -271,23 +271,30 @@ export default function BinLevelOverTime(props: Props) { }; const inventoryChart = () => { - return ( - 10){ + return ( + + ) + }else{ + return ( + 0 ? legend() : undefined} labelColour="white" labels useGradient - /> - ); + /> + ); + } }; - const autoBinModeChart = () => { + const binModeChart = () => { return ( {dataLoading ? : inventoryChart()} - {/* when using auto will need to make a new chart just for the bin modes since the auto inventory comes from another place */} - {(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR || - bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC || - bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART) && autoBinModeChart()} + {binModeChart()} ); } diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 79238ea..ef99665 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -59,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => { }, stickyHeader: { position: "sticky", - // backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)", + backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)", top: 0, zIndex: 300 //giving a really high z-index to make sure nothing is in front of it } diff --git a/src/common/time/PeriodSelect.tsx b/src/common/time/PeriodSelect.tsx index 66e4f9c..d510ebc 100644 --- a/src/common/time/PeriodSelect.tsx +++ b/src/common/time/PeriodSelect.tsx @@ -28,6 +28,13 @@ export default function PeriodSelect(props: Props) { } }, [unit, prevUnit, onChange, value]); + useEffect(()=>{ + let initialUnit: TimeUnit = bestUnit(initialMs); + let initialValue = milliToX(initialMs, initialUnit).toString(); + setValue(initialValue) + setUnit(initialUnit) + },[initialMs]) + const changeValue = (event: any) => { let value = event.target.value; setValue(value); diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index 08d6117..99568da 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -699,6 +699,86 @@ export default function ComponentForm(props: Props) { ); }; + const componentMode = () => { + const { component, coefficient, offset } = form; + return ( + + + + + } + label="Mode" + labelPlacement="top" + className={classes.switchControl} + disabled={!canEdit} + /> + + + + {compMode.mode.entryA && ( + + {compMode.mode.entryA === "select" && + compMode.mode.dictionaryA.map((elem: any) => { + if (!elem.restricted || (elem.restricted && user.hasAdmin())) { + return ( + + {elem.key} + + ); + } + return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined + })} + + )} + + + {compMode.mode.entryB && ( + + {compMode.mode.entryB === "select" && + compMode.mode.dictionaryB.map((elem: any) => ( + + {elem.key} + + ))} + + )} + + + ) + } + const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => { const { component } = form; return describeMeasurement(measurementType, component.type(), component.subType()); @@ -1046,87 +1126,17 @@ export default function ComponentForm(props: Props) { } - {!compMode ? ( - device.featureSupported("individualCalibrations") ? ( - individualCalibration() - ) : ( - blanketCalibration() - ) - ) : ( + {device.featureSupported("individualCalibrations") ? ( - - - - } - label="Mode" - labelPlacement="top" - className={classes.switchControl} - disabled={!canEdit} - /> - - - - {compMode.mode.entryA && ( - - {compMode.mode.entryA === "select" && - compMode.mode.dictionaryA.map((elem: any) => { - if (!elem.restricted || (elem.restricted && user.hasAdmin())) { - return ( - - {elem.key} - - ); - } - return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined - })} - - )} - - - {compMode.mode.entryB && ( - - {compMode.mode.entryB === "select" && - compMode.mode.dictionaryB.map((elem: any) => ( - - {elem.key} - - ))} - - )} - + {compMode && componentMode()} + {individualCalibration()} + ) : ( + !compMode ? ( + blanketCalibration() + ):( + componentMode() + ) )} ([]); const { openSnack } = useSnackbar(); - //const [{ as }] = useGlobalState(); - // const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; - const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; + const [{ as }] = useGlobalState(); const submitNewOrganization = () => { @@ -176,28 +174,45 @@ export default function LibraCartAccess() { return ( - - - + + + + + + + + + + - - + + - - + + + LibraCart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform. + + {newOrgDialog()} diff --git a/src/models/Component.ts b/src/models/Component.ts index 9b96599..473d85d 100644 --- a/src/models/Component.ts +++ b/src/models/Component.ts @@ -114,7 +114,7 @@ export class Component { return 0; } - public addressDescription = (deviceProduct?: pond.DeviceProduct) => { + public addressDescription(deviceProduct?: pond.DeviceProduct): string { if ( this.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY || (this.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 && diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx index 477aa05..9145a6a 100644 --- a/src/pages/Bins.tsx +++ b/src/pages/Bins.tsx @@ -1214,37 +1214,6 @@ export default function Bins(props: Props) { } ]} /> - {/* - { - setCardValDisplay("low"); - }}> - Low - - { - setCardValDisplay("average"); - }}> - Average - - { - setCardValDisplay("high"); - }}> - High - - */} @@ -1270,41 +1239,6 @@ export default function Bins(props: Props) { } ]} /> - {/* - { - setBinView("grid"); - sessionStorage.setItem("binsView", "grid"); - }}> - - */} - {/* hidden at dustins request so that grid view and list are the only two */} - {/* { - setBinView("scroll"); - sessionStorage.setItem("binsView", "scroll"); - }}> - - */} - {/* { - setBinView("list"); - sessionStorage.setItem("binsView", "list"); - }}> - - - */} { diff --git a/src/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx index d4f8bb5..b56f65b 100644 --- a/src/providers/pond/libracartProxyAPI.tsx +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -4,6 +4,7 @@ import { pond } from "protobuf-ts/pond"; import React, { createContext, PropsWithChildren, useContext } from "react"; //import { or } from "utils"; import { pondURL } from "./pond"; +import { useGlobalState } from "providers/StateContainer"; export interface ILibraCartProxyAPIContext { //add new organization @@ -33,6 +34,7 @@ export interface ILibraCartProxyAPIContext { libracartKey?: string, as?: string ) => Promise>; + syncData: () => Promise } export const LibraCartProxyAPIContext = createContext( @@ -43,6 +45,7 @@ interface Props {} export default function LibraCartProvider(props: PropsWithChildren) { const { children } = props; + const [{as}] = useGlobalState(); const { post, get, put } = useHTTP(); const addAccount = ( @@ -104,13 +107,18 @@ export default function LibraCartProvider(props: PropsWithChildren) { ) } + const syncData = () => { + return get(pondURL("/libracartImport" + (as ? "?as=" + as : ""))) + } + return ( {children}