Merge branch 'staging_environment' into upgrade_device

This commit is contained in:
Carter 2025-05-09 13:46:04 -06:00
commit 809acae6fc
31 changed files with 1027 additions and 1021 deletions

View file

@ -419,16 +419,12 @@ export default function BinCard(props: Props) {
Math.round(current * 35.239).toLocaleString() +
"/" +
Math.round(capacity * 35.239).toLocaleString() +
(lidarBushels ? "(" + Math.round(lidarBushels * 35.239).toLocaleString() + " est)" : "") +
" L"
);
}
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bin.bushelsPerTonne() > 1) {
return (
bin.grainTonnes().toLocaleString() +
(lidarBushels
? "(" + Math.round(lidarBushels / bin.bushelsPerTonne()).toLocaleString() + " est)"
: "") +
" mT " +
bin.fillPercent() +
"%"
@ -438,7 +434,6 @@ export default function BinCard(props: Props) {
current.toLocaleString() +
"/" +
capacity.toLocaleString() +
(lidarBushels ? "(" + lidarBushels.toLocaleString() + " est)" : "") +
" bu"
);
};
@ -469,10 +464,10 @@ export default function BinCard(props: Props) {
const info = () => {
const inv = bin.settings.inventory;
let bushelAmount = inv?.grainBushels;
let bushelAmount = bin.bushels();
let bushelCapacity = bin.settings.specs?.bushelCapacity;
const empty =
!inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || inv.grainBushels <= 0;
!inv || inv.empty || !bushelCapacity || bushelCapacity <= 0 || bushelAmount <= 0;
return (
<Box width={1} marginTop={0}>
<Typography align="center" color="textSecondary" noWrap style={{ fontSize: "0.65rem" }}>

View file

@ -832,7 +832,6 @@ export default function BinSVGV2(props: Props) {
cableGrainEndY = cableGrainStartY;
}
if (cableEstimate && cables.length > 0 && cableGrainPathPoints.length === cables.length) {
console.log("calc cable path");
let cablePath = cableGrainEstimate(
cableGrainPathPoints,
spacingX,

View file

@ -362,95 +362,95 @@ export default function BinVisualizer(props: Props) {
let trendCables = cables;
if (bin.key() !== "" && !loadingTrend) {
setLoadingTrend(true);
// binAPI
// .getBinsTrendData([bin.key()], 7)
// .then(resp => {
// let binTrend = resp.data.trendData[bin.key()];
// //the variables to use to build the average conditions
// let temps: number[] = [];
// let humids: number[] = [];
// let emcs: number[] = [];
// let tempTrends: number[] = [];
// let humidTrends: number[] = [];
// let emcTrends: number[] = [];
binAPI
.getBinsTrendData([bin.key()], 7)
.then(resp => {
let binTrend = resp.data.trendData[bin.key()];
//the variables to use to build the average conditions
let temps: number[] = [];
let humids: number[] = [];
let emcs: number[] = [];
let tempTrends: number[] = [];
let humidTrends: number[] = [];
let emcTrends: number[] = [];
// let lowNodeConditions: GrainConditions | undefined = undefined;
// let highNodeConditions: GrainConditions | undefined = undefined;
// let avgConditions: GrainConditions | undefined = undefined;
// trendCables.forEach(cable => {
// //only use the values below the top node
// //because splice mutates the original array make a clone in the cables so the svg still has all of them for display
// //also reverse it so that the node 1 (end of the cable) is in the first position
// let tempClone = cloneDeep(cable.temperatures).reverse();
// let humClone = cloneDeep(cable.humidities).reverse();
// let emcClone = cloneDeep(cable.grainMoistures).reverse();
// //add the cable data to the proper arrays
// if (cable.topNode > 0) {
// temps.push(...tempClone.splice(0, cable.topNode));
// humids.push(...humClone.splice(0, cable.topNode));
// emcs.push(...emcClone.splice(0, cable.topNode));
// } else {
// //if the cable has no fill set (top node) then use all of the nodes
// temps = tempClone;
// humids = humClone;
// emcs = emcClone;
// }
// //add the trend data to the proper arrays if the top node is set
// let cableTrendData = binTrend.cableTrend[cable.key()];
// tempTrends.push(cableTrendData.grainCelciusTrend);
// humidTrends.push(cableTrendData.grainHumidityTrend);
// emcTrends.push(cableTrendData.grainEmcTrend);
let lowNodeConditions: GrainConditions | undefined = undefined;
let highNodeConditions: GrainConditions | undefined = undefined;
let avgConditions: GrainConditions | undefined = undefined;
trendCables.forEach(cable => {
//only use the values below the top node
//because splice mutates the original array make a clone in the cables so the svg still has all of them for display
//also reverse it so that the node 1 (end of the cable) is in the first position
let tempClone = cloneDeep(cable.temperatures).reverse();
let humClone = cloneDeep(cable.humidities).reverse();
let emcClone = cloneDeep(cable.grainMoistures).reverse();
//add the cable data to the proper arrays
if (cable.topNode > 0) {
temps.push(...tempClone.splice(0, cable.topNode));
humids.push(...humClone.splice(0, cable.topNode));
emcs.push(...emcClone.splice(0, cable.topNode));
} else {
//if the cable has no fill set (top node) then use all of the nodes
temps = tempClone;
humids = humClone;
emcs = emcClone;
}
//add the trend data to the proper arrays if the top node is set
let cableTrendData = binTrend.cableTrend[cable.key()];
tempTrends.push(cableTrendData.grainCelciusTrend);
humidTrends.push(cableTrendData.grainHumidityTrend);
emcTrends.push(cableTrendData.grainEmcTrend);
// //if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set
// //use this cables lowest node and trend data in the condition
// if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) {
// //determine which node is the coldest so that the data displayed is for the same node
// let lowTempIndex =
// temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps));
// lowNodeConditions = {
// tempC: temps[lowTempIndex],
// humidity: humids[lowTempIndex],
// emc: emcs[lowTempIndex],
// tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
// };
// }
//if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set
//use this cables lowest node and trend data in the condition
if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) {
//determine which node is the coldest so that the data displayed is for the same node
let lowTempIndex =
temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps));
lowNodeConditions = {
tempC: temps[lowTempIndex],
humidity: humids[lowTempIndex],
emc: emcs[lowTempIndex],
tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
};
}
// //do the same for the high node
// if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
// let highTempIndex =
// temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
// highNodeConditions = {
// tempC: temps[highTempIndex],
// humidity: humids[highTempIndex],
// emc: emcs[highTempIndex],
// tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
// humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
// emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
// };
// }
// });
// //set the average conditions
// avgConditions = {
// tempC: average(temps),
// humidity: average(humids),
// emc: average(emcs),
// tempCTrend: average(tempTrends),
// humidityTrend: average(humidTrends),
// emcTrend: average(emcTrends)
// };
// setAverageConditions(avgConditions);
// setLowNodeConditions(lowNodeConditions);
// setHighNodeConditions(highNodeConditions);
// })
// .catch(err => {
// console.log(err);
// openSnack("Failed to retrieve trend data");
// })
// .finally(() => {
// setLoadingTrend(false);
// });
//do the same for the high node
if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
let highTempIndex =
temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
highNodeConditions = {
tempC: temps[highTempIndex],
humidity: humids[highTempIndex],
emc: emcs[highTempIndex],
tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
};
}
});
//set the average conditions
avgConditions = {
tempC: average(temps),
humidity: average(humids),
emc: average(emcs),
tempCTrend: average(tempTrends),
humidityTrend: average(humidTrends),
emcTrend: average(emcTrends)
};
setAverageConditions(avgConditions);
setLowNodeConditions(lowNodeConditions);
setHighNodeConditions(highNodeConditions);
})
.catch(err => {
console.log(err);
openSnack("Failed to retrieve trend data");
})
.finally(() => {
setLoadingTrend(false);
});
}
}
}, [binAPI, bin, cables, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps
@ -485,7 +485,7 @@ export default function BinVisualizer(props: Props) {
if (bin.settings.inventory && bin.settings.specs) {
const isEmpty = bin.settings.inventory?.empty === true;
const capacity = bin.settings.specs.bushelCapacity;
const grainBushels = bin.settings.inventory.grainBushels;
const grainBushels = bin.bushels();
setSliderCoulour("gold");
if (!capacity) {
setFillPercentage(null);
@ -535,7 +535,7 @@ export default function BinVisualizer(props: Props) {
const inventoryOverview = () => {
const capacity = bin.settings.specs?.bushelCapacity ?? 0;
const grainBushels = bin.settings.inventory?.grainBushels ?? 0;
const grainBushels = bin.bushels();
const isEmpty = bin.settings.inventory?.empty === true || !grainBushels || grainBushels <= 0;
const grainType = bin.settings.inventory?.grainType;
const grainTypeName = isEmpty || !grainType ? "" : GrainDescriber(grainType).name;
@ -1481,9 +1481,7 @@ export default function BinVisualizer(props: Props) {
onChange={(_, value) => {
setFillPercentage(value as number);
const capacity = bin.settings.specs ? bin.settings.specs.bushelCapacity : 0;
const current = bin.settings.inventory
? bin.settings.inventory.grainBushels
: 0;
const current = bin.bushels()
let grainAmount = ((value as number) / 100) * capacity;
if (grainAmount < current) {

View file

@ -130,6 +130,7 @@ const useStyles = makeStyles((theme: Theme) => {
borderTopLeftRadius: "6px",
borderTopRightRadius: "6px",
background: theme.palette.background.default,
fontWeight: 1000,
transform: "0.3s ease-in-out",
// "linear-gradient(rgba(150, 150, 150, 0.3),"
// + theme.palette.background.default + " 80%)"

View file

@ -123,10 +123,11 @@ export default function BinyardDisplay(props: Props) {
});
const loadBins = useCallback(() => {
setBinsLoading(true);
if (yardKey === "") return
//load the bins based on the yard key
setBinsLoading(true);
binAPI
.listBinsAndData(maxBins, 0, "asc", "name", undefined, as, undefined, [yardKey], ["binyard"])
.listBinsAndData(maxBins, 0, "asc", "name", undefined, undefined, undefined, [yardKey], ["binyard"])
.then(resp => {
let yardBins: Bin[] = [];
let grainBins: Bin[] = [];
@ -315,17 +316,10 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}>
<BinsList
gridView
valDisplay="average"
bins={grainBins}
duplicateBin={duplicateBin}
title={"Grain Bins"}
// because this only triggers in scroll view it is no longer necessary as we dont do the scroll here
// loadMore={newTranslation => {
// //only triggered in the scroll view so this will never trigger in grid view
// if (yardBins.length < binTotal) {
// loadMoreBins(contentFilter);
// setScrollTranslations({ ...scrollTranslations, bins: newTranslation });
// }
// }}
/>
</Grid>
</React.Fragment>
@ -338,16 +332,10 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}>
<BinsList
gridView
valDisplay="average"
bins={fertBins}
duplicateBin={duplicateBin}
title={"Fertilizer Bins"}
// loadMore={newTranslation => {
// //only triggered in the scroll view so this will never trigger in grid view
// if (yardBins.length < binTotal) {
// loadMoreBins(contentFilter);
// setScrollTranslations({ ...scrollTranslations, fertilizer: newTranslation });
// }
// }}
/>
</Grid>
</React.Fragment>
@ -360,6 +348,7 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}>
<BinsList
gridView
valDisplay="average"
bins={emptyBins}
duplicateBin={duplicateBin}
title={"Empty Bins"}

View file

@ -133,7 +133,7 @@ export default function DisplayDrawer(props: Props) {
</Grid>
<Grid>
<Box>
<Typography align="center" variant="h5">
<Typography sx={{ color: "black", fontWeight: 650}} align="center" variant="h5">
{title}
</Typography>
</Box>

View file

@ -3,7 +3,7 @@ import { Beenhere } from "@mui/icons-material";
import { useThemeType } from "hooks";
import moment from "moment";
import React, { useEffect, useState } from "react";
import Map, { Marker } from "react-map-gl/mapbox-legacy";
import Map, { Marker } from "react-map-gl/mapbox";
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

View file

@ -2,7 +2,8 @@ import {
Button,
DialogActions,
DialogContent,
Grid,
Grid2 as Grid,
TextField,
Theme,
Typography
} from "@mui/material";
@ -13,6 +14,8 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
import { useThemeType } from "hooks";
import { makeStyles } from "@mui/styles";
import { cloneDeep } from "lodash";
import { color } from "@mui/system";
interface Props {
startDate: Moment;
@ -26,10 +29,12 @@ type DateRange<T> = [T | null, T | null];
const useStyles = makeStyles((_theme: Theme) => {
return ({
activeButtonLight: {
border: "1px solid black"
border: "1px solid black",
color: "black"
},
acitveButtonDark: {
border: "1px solid white"
border: "1px solid white",
color: "white"
}
})
});
@ -49,7 +54,7 @@ export default function TimeBar(props: Props) {
const submitDateRange = () => {
const startDate = dateRange[0] ? dateRange[0].clone() : moment();
const endDate = dateRange[1] ? dateRange[1].clone() : moment();
const endDate = dateRange[1] ? dateRange[1].add(1, "day").clone() : moment(); //add 1 day to the end time to include that day rather than have it be the cutoff
updateDateRange(startDate, endDate, false);
setDateRangeDialog(false);
setActiveView("selectRange");
@ -85,19 +90,30 @@ export default function TimeBar(props: Props) {
onClose={() => setDateRangeDialog(false)}
aria-labelledby="date-range-dialog">
<DialogContent>
{/* <StaticDateRangePicker
disableFuture
value={dateRange}
onChange={date => setDateRange(date)}
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<DateRangeDelimiter> to </DateRangeDelimiter>
<TextField {...endProps} />
</React.Fragment>
)}
/> */}
DATE PICKER GOES HERE
<TextField
fullWidth
margin="normal"
type="date"
label="Start Time"
value={dateRange[0]?.format("YYYY-MM-DD")}
onChange={e => {
let range = cloneDeep(dateRange)
range[0] = moment(e.target.value)
setDateRange(range)
}}
/>
<TextField
fullWidth
margin="normal"
type="date"
label="End Time"
value={dateRange[1]?.format("YYYY-MM-DD")}
onChange={e => {
let range = cloneDeep(dateRange)
range[1] = moment(e.target.value)
setDateRange(range)
}}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setDateRangeDialog(false)} color="primary">
@ -115,7 +131,7 @@ export default function TimeBar(props: Props) {
<React.Fragment>
{datePickerDialog()}
<Grid container direction="row" spacing={1} wrap="nowrap">
<Grid item>
<Grid>
<Button
style={{ borderRadius: "25px" }}
className={
@ -132,7 +148,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1H</Typography>
</Button>
</Grid>
<Grid item>
<Grid>
<Button
style={{ borderRadius: "25px" }}
className={
@ -149,7 +165,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1D</Typography>
</Button>
</Grid>
<Grid item>
<Grid>
<Button
style={{ borderRadius: "25px" }}
className={
@ -166,7 +182,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1W</Typography>
</Button>
</Grid>
<Grid item>
<Grid>
<Button
style={{ borderRadius: "25px" }}
className={
@ -183,7 +199,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1M</Typography>
</Button>
</Grid>
<Grid item>
<Grid>
<Button
style={{ borderRadius: "25px" }}
className={

View file

@ -237,8 +237,7 @@ export default function ComponentForm(props: Props) {
};
useEffect(() => {
let comp = tidyComponent(form.component);
componentChanged(comp, isFormValid());
componentChanged(form.component, isFormValid());
}, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps
const isNameValid = (name: string): boolean => {
@ -950,7 +949,7 @@ export default function ComponentForm(props: Props) {
<FormControlLabel
control={
<Switch
checked={measure}
checked={measure}
onChange={toggleMeasure}
name="measure"
aria-label="measure"

View file

@ -7,6 +7,7 @@ import {
DialogTitle,
Divider,
Grid2,
IconButton,
lighten,
List,
ListItem,
@ -36,12 +37,23 @@ import { useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react";
import RemoveGroup from "./RemoveGroup";
import { pond } from "protobuf-ts/pond";
import { Add, Remove } from "@mui/icons-material";
const useStyles = makeStyles((theme: Theme) => {
return ({
deviceListContainer: {
marginBottom: theme.spacing(1)
},
listTitle: {
fontSize: 20,
fontWeight: 650
},
listButton: {
cursor: "pointer",
"&:hover": {
backgroundColor: lighten(theme.palette.background.default, 0.25)
}
},
devicesList: {
overflow: "auto",
minHeight: "25vh",
@ -87,6 +99,7 @@ export default function GroupSettings(props: Props) {
const deviceAPI = useDeviceAPI();
const snackbar = useSnackbar()
const [devices, setDevices] = useState<Device[]>([]);
const [nonGroupDevices, setNonGroupDevices] = useState<Device[]>([]);
const [group, setGroup] = useState<Group>(initialGroup ? Group.clone(initialGroup) : new Group());
const [isRemoveGroupOpen, setIsRemoveGroupOpen] = useState<boolean>(
mode === "remove" ? true : false
@ -109,13 +122,14 @@ export default function GroupSettings(props: Props) {
const loadDevices = useCallback(() => {
setLoadingDevices(true);
//this lists the devices for the user/team viewing
deviceAPI
.list(1000000, 0, "asc")
.then((response: any) => {
let rDevices: Device[] = response.data.devices
? response.data.devices.map((device: any) => Device.any(device))
: [];
//remove duplicates from the options
setDevices(rDevices);
})
.catch((_error: any) => {
@ -124,6 +138,16 @@ export default function GroupSettings(props: Props) {
.finally(() => setLoadingDevices(false));
}, [deviceAPI, as]);
useEffect(()=>{
let ungrouped: Device[] = []
devices.forEach(device => {
if(!groupDeviceNumbers.includes(device.id())){
ungrouped.push(device)
}
})
setNonGroupDevices(ungrouped)
},[groupDeviceNumbers, devices])
useEffect(() => {
if (prevInitialGroup !== initialGroup) {
setGroup(initialGroup ? Group.clone(initialGroup) : new Group());
@ -317,27 +341,58 @@ export default function GroupSettings(props: Props) {
</ListItem>
</List>
) : (
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, devices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
<Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
<React.Fragment>
{groupDevices &&
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, groupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
removeDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
{/* <Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/> */}
-
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
}
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, nonGroupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
addDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
{/* <Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/> */}
+
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</React.Fragment>
)}
</Paper>
);
@ -383,16 +438,49 @@ export default function GroupSettings(props: Props) {
</ListItem>
</List>
) : (
<React.Fragment>
{groupDevices &&
<React.Fragment>
<Typography className={classes.listTitle}>Grouped Devices</Typography>
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, groupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
removeDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
{/* <Checkbox
edge="end"
onChange={() => changeDevices(device.id())}
checked={group.settings.devices.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/> */}
<Remove />
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</React.Fragment>
}
<Typography className={classes.listTitle}>Devices You Can Add</Typography>
<List dense className={classes.devicesList}>
{filterDevices(deviceSearch, devices, [])
{filterDevices(deviceSearch, nonGroupDevices, [])
.sort((a, b: Device) => a.name().localeCompare(b.name()))
.map(device => {
const label = `checkbox-list-secondary-label-${device.id()}`;
return (
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}>
<ListItem className={classes.listButton} key={device.id()} onClick={() => {
addDevice(device.id())
}}>
<ListItemText id={label} primary={device.name()} />
<ListItemSecondaryAction>
<Checkbox
{/* <Checkbox
edge="end"
onChange={(_, checked) => {
if (checked) addDevice(device.id());
@ -402,12 +490,14 @@ export default function GroupSettings(props: Props) {
checked={groupDeviceNumbers.includes(device.id())}
inputProps={{ "aria-labelledby": label }}
disabled={!canEdit}
/>
/> */}
<Add />
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</React.Fragment>
)}
</Paper>
);

View file

@ -33,7 +33,7 @@ import { GeometryMapping, shapeFromCoords } from "models/GeometryMapping";
import { FeatureCollection, Feature } from "geojson";
import DrawController from "./mapControllers/drawController";
//import { Geometry } from "geojson";
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox-legacy";
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox";
import { getDistanceUnit } from "utils";
import { MapMouseEvent } from "mapbox-gl";
import { makeStyles } from "@mui/styles";

View file

@ -1,4 +1,4 @@
import { ControlPosition, useMap } from "react-map-gl/mapbox-legacy";
import { ControlPosition, useMap } from "react-map-gl/mapbox";
import MapboxGeocoder, { Result } from "@mapbox/mapbox-gl-geocoder";
import { useEffect, useState } from "react";

View file

@ -1,5 +1,5 @@
import { useEffect, useRef } from "react";
import { useMap } from "react-map-gl/mapbox-legacy";
import { useMap } from "react-map-gl/mapbox";
import { FeatureCollection } from "geojson";
import MapboxDraw from "@mapbox/mapbox-gl-draw";

View file

@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Layer, Source } from "react-map-gl/mapbox-legacy";
import { Layer, Source } from "react-map-gl/mapbox";
import { FeatureCollection } from "geojson";
interface Props {

View file

@ -5,7 +5,7 @@ import { useMobile } from "hooks";
import { clone } from "lodash";
import { pond } from "protobuf-ts/pond";
import React, { useCallback, useEffect, useState } from "react";
import { Marker } from "react-map-gl/mapbox-legacy";
import { Marker } from "react-map-gl/mapbox";
//interface for markers
export interface MarkerData {

View file

@ -185,6 +185,8 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
const [measurement, setMeasurement] = useState(false);
const [measurementData, setMeasurementData] = useState<Map<string, MeasurementData>>(new Map());
const measurementRef = useRef(measurementData);
const zoomRef = useRef(15)
const fieldMarkerZoom = 15
//watches for changes to the viewing as and sets the load boolean to trigger a load
useEffect(() => {
@ -591,12 +593,13 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
}
let newFM = fieldMarkers.get(newKey);
if (newFM) {
setCurrentView({
let view = {
latitude: newFM.lat(),
longitude: newFM.long(),
zoom: 12,
zoom: currentView.zoom,
transitionDuration: 1000
});
}
setCurrentView(view);
}
setObjectKey(newKey);
setFMIndexKey(newID);
@ -621,12 +624,13 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
}
let newFM = fieldMarkers.get(newKey);
if (newFM) {
setCurrentView({
let view = {
latitude: newFM.lat(),
longitude: newFM.long(),
zoom: 12,
zoom: currentView.zoom,
transitionDuration: 1000
});
}
setCurrentView(view);
}
setObjectKey(newKey);
setFMIndexKey(newID);
@ -699,7 +703,12 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
</Grid>
</Grid>
<Grid size={3}>
<Button style={{ margin: "auto" }} onClick={() => setOpenFMSettings(true)}>
<Button
style={{ margin: "auto" }}
onClick={() => {
setOpenFMSettings(true)
setAnchorFieldMarker(null)
}}>
<Edit />
</Button>
</Grid>
@ -728,13 +737,14 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
subtype: fm.type(),
mini: true,
clickFunc: (event, index) => {
console.log(zoomRef)
clickDelay();
closeDrawers();
setIgnoreFeatures(true);
setCurrentView({
latitude: fm.lat(),
longitude: fm.long(),
zoom: 12,
zoom: zoomRef.current,
transitionDuration: 0
//transitionInterpolator: new FlyToInterpolator()
});
@ -1115,14 +1125,16 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
let xOff = !mobileOffset ? -250 : undefined;
let yOff = mobileOffset ? -150 : undefined;
setCurrentView({
let view = {
latitude: lat,
longitude: long,
zoom: zoom,
transitionDuration: transDuration,
xOffset: center ? 0 : xOff,
yOffset: center ? 0 : yOff
});
}
setCurrentView(view);
zoomRef.current = zoom
};
const markerDialog = () => {

View file

@ -125,11 +125,23 @@ export class Bin {
return colour;
}
public bushels(): number {
let bushels = this.settings.inventory?.grainBushels || 0
if (this.settings.inventory?.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
bushels = this.status.grainBushels
}
return bushels
}
public fillPercent(): number {
let fill = 0;
if (this.settings.inventory && this.settings.specs) {
let bushels = this.settings.inventory.grainBushels
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
bushels = this.status.grainBushels
}
fill = Math.round(
(this.settings.inventory.grainBushels / this.settings.specs.bushelCapacity) * 100
(bushels / this.settings.specs.bushelCapacity) * 100
);
}
return fill;

View file

@ -105,7 +105,7 @@ export default function BottomNavigator(props: Props) {
return (
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
{isAdaptive && (
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="fields" />
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
)}
{isAdaptive && (
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
@ -114,7 +114,7 @@ export default function BottomNavigator(props: Props) {
<BottomNavigationAction
label="Site Map"
icon={<FieldsIcon type={getType()} />}
value="constructionsiteMap"
value="constructionMap"
/>
)}
{isOmni && (

View file

@ -160,84 +160,6 @@ export default function SideNavigator(props: Props) {
const isAdCon = IsAdCon()
return (
<List className={classes.list} component="nav">
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Bins" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/bins")}
classes={getClasses("/bin")}
>
<ListItemIcon>
<BinsIcon height={"26px"}/>
</ListItemIcon>
{open && <ListItemText primary="Bins" />}
</ListItemButton>
</Tooltip>
)}
{(isMiPCA || user.hasFeature("admin")) && (
<Tooltip title="Terminals" placement="right">
<ListItemButton
id="tour-terminals"
onClick={() => goTo("/terminals")}
classes={getClasses("/terminal")}
>
<ListItemIcon>
<PlaneIcon />
</ListItemIcon>
{open && <ListItemText primary="Terminals" />}
</ListItemButton>
</Tooltip>
)}
<Tooltip title="Devices" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/devices")}
classes={getClasses("/device")}
>
<ListItemIcon>
<BindaptIcon />
</ListItemIcon>
{open && <ListItemText primary="Devices" />}
</ListItemButton>
</Tooltip>
{(isMiVent || user.hasFeature("admin")) && (
<Tooltip title="Mines" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/mines")}
classes={getClasses("/mine")}
>
<ListItemIcon>
<MiningIcon height={"26px"} width={"26px"} />
</ListItemIcon>
{open && <ListItemText primary="Mines" />}
</ListItemButton>
</Tooltip>
)}
<Tooltip title="Teams" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/teams")}
classes={getClasses("/team")}
>
<ListItemIcon>
<People style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
</ListItemIcon>
{open && <ListItemText primary="Teams" />}
</ListItemButton>
</Tooltip>
<Tooltip title="Users" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/users")}
classes={getClasses("/user")}
>
<ListItemIcon>
<Person style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
</ListItemIcon>
{open && <ListItemText primary="Users" />}
</ListItemButton>
</Tooltip>
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Visual Farm" placement="right">
<ListItemButton
@ -252,20 +174,6 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="My Fields" placement="right">
<ListItemButton
id="tour-field-list"
onClick={() => goTo("/fields")}
classes={getClasses("/fields")}
>
<ListItemIcon>
<FieldListIcon />
</ListItemIcon>
{open && <ListItemText primary="My Fields" />}
</ListItemButton>
</Tooltip>
)}
{(isMiPCA || user.hasFeature("admin")) && (
<Tooltip title="Aviation Map" placement="right">
<ListItemButton
@ -294,6 +202,169 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Contracts" placement="right">
<ListItemButton
id="tour-contracts"
onClick={() => goTo("/contracts")}
classes={getClasses("/contracts")}
>
<ListItemIcon>
<ContractsIcon />
</ListItemIcon>
{open && <ListItemText primary="Contracts" />}
</ListItemButton>
</Tooltip>
)}
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Bins" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/bins")}
classes={getClasses("/bin")}
>
<ListItemIcon>
<BinsIcon height={"26px"}/>
</ListItemIcon>
{open && <ListItemText primary="Bins" />}
</ListItemButton>
</Tooltip>
)}
{(isMiPCA || user.hasFeature("admin")) && (
<Tooltip title="Terminals" placement="right">
<ListItemButton
id="tour-terminals"
onClick={() => goTo("/terminals")}
classes={getClasses("/terminal")}
>
<ListItemIcon>
<PlaneIcon />
</ListItemIcon>
{open && <ListItemText primary="Terminals" />}
</ListItemButton>
</Tooltip>
)}
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
<Tooltip title="Cable Estimator" placement="right">
<ListItemButton
id="tour-cable-estimator"
onClick={() => goTo("/cableEstimate")}
classes={getClasses("/cableEstimate")}
>
<ListItemIcon>
<CableIcon />
</ListItemIcon>
{open && <ListItemText primary="Cable Estimator" />}
</ListItemButton>
</Tooltip>
}
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Transactions" placement="right">
<ListItemButton
id="tour-transactions"
onClick={() => goTo("/transactions")}
classes={getClasses("/transactions")}
>
<ListItemIcon>
<SyncAlt />
</ListItemIcon>
{open && <ListItemText primary="Transactions" />}
</ListItemButton>
</Tooltip>
)}
{(isMiVent || user.hasFeature("admin")) && (
<Tooltip title="Mines" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/mines")}
classes={getClasses("/mine")}
>
<ListItemIcon>
<MiningIcon height={"26px"} width={"26px"} />
</ListItemIcon>
{open && <ListItemText primary="Mines" />}
</ListItemButton>
</Tooltip>
)}
<Tooltip title="Devices" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/devices")}
classes={getClasses("/device")}
>
<ListItemIcon>
<BindaptIcon />
</ListItemIcon>
{open && <ListItemText primary="Devices" />}
</ListItemButton>
</Tooltip>
{user.hasFeature("admin") &&
<Tooltip title="Firmware" placement="right">
<ListItemButton
id="tour-firmware"
onClick={() => goTo("/firmware")}
classes={getClasses("/firmware")}
>
<ListItemIcon>
<Memory />
</ListItemIcon>
{open && <ListItemText primary="Logs" />}
</ListItemButton>
</Tooltip>
}
{user.hasFeature("admin") &&
<Tooltip title="Logs" placement="right">
<ListItemButton
id="tour-logs"
onClick={() => goTo("/logs")}
classes={getClasses("/logs")}
>
<ListItemIcon>
<DataDuckIcon />
</ListItemIcon>
{open && <ListItemText primary="Logs" />}
</ListItemButton>
</Tooltip>
}
<Tooltip title="Teams" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/teams")}
classes={getClasses("/team")}
>
<ListItemIcon>
<People style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
</ListItemIcon>
{open && <ListItemText primary="Teams" />}
</ListItemButton>
</Tooltip>
<Tooltip title="Users" placement="right">
<ListItemButton
id="tour-dashboard"
onClick={() => goTo("/users")}
classes={getClasses("/user")}
>
<ListItemIcon>
<Person style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
</ListItemIcon>
{open && <ListItemText primary="Users" />}
</ListItemButton>
</Tooltip>
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="My Fields" placement="right">
<ListItemButton
id="tour-field-list"
onClick={() => goTo("/fields")}
classes={getClasses("/fields")}
>
<ListItemIcon>
<FieldListIcon />
</ListItemIcon>
{open && <ListItemText primary="My Fields" />}
</ListItemButton>
</Tooltip>
)}
{(isAdCon || user.hasFeature("admin")) && (
<Tooltip title="Jobsites" placement="right">
<ListItemButton
@ -334,48 +405,7 @@ export default function SideNavigator(props: Props) {
{open && <ListItemText primary="Tasks" />}
</ListItemButton>
</Tooltip>
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Transactions" placement="right">
<ListItemButton
id="tour-transactions"
onClick={() => goTo("/transactions")}
classes={getClasses("/transactions")}
>
<ListItemIcon>
<SyncAlt />
</ListItemIcon>
{open && <ListItemText primary="Transactions" />}
</ListItemButton>
</Tooltip>
)}
{(isAg || user.hasFeature("admin")) && (
<Tooltip title="Contracts" placement="right">
<ListItemButton
id="tour-contracts"
onClick={() => goTo("/contracts")}
classes={getClasses("/contracts")}
>
<ListItemIcon>
<ContractsIcon />
</ListItemIcon>
{open && <ListItemText primary="Contracts" />}
</ListItemButton>
</Tooltip>
)}
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
<Tooltip title="Cable Estimator" placement="right">
<ListItemButton
id="tour-cable-estimator"
onClick={() => goTo("/cableEstimate")}
classes={getClasses("/cableEstimate")}
>
<ListItemIcon>
<CableIcon />
</ListItemIcon>
{open && <ListItemText primary="Cable Estimator" />}
</ListItemButton>
</Tooltip>
}
{user.hasFeature("developer") &&
<Tooltip title="API documentation" placement="right">
<ListItemButton
@ -390,34 +420,6 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
}
{user.hasFeature("admin") &&
<Tooltip title="Firmware" placement="right">
<ListItemButton
id="tour-firmware"
onClick={() => goTo("/firmware")}
classes={getClasses("/firmware")}
>
<ListItemIcon>
<Memory />
</ListItemIcon>
{open && <ListItemText primary="Logs" />}
</ListItemButton>
</Tooltip>
}
{user.hasFeature("admin") &&
<Tooltip title="Logs" placement="right">
<ListItemButton
id="tour-logs"
onClick={() => goTo("/logs")}
classes={getClasses("/logs")}
>
<ListItemIcon>
<DataDuckIcon />
</ListItemIcon>
{open && <ListItemText primary="Logs" />}
</ListItemButton>
</Tooltip>
}
{user.hasFeature("admin") && window.NDEFReader &&
<Tooltip title="NFC" placement="right">
<ListItemButton

View file

@ -547,7 +547,6 @@ export default function Bin(props: Props) {
};
const tasks = () => {
console.log(showTasks())
if (showTasks()) {
return (
<React.Fragment>

View file

@ -96,6 +96,7 @@ export default function DevicePage() {
}
}
});
setDiagnosticComponents(diagComponents)
setComponents(rComponents)
let interactions: Interaction[] = [];
resp.data.interactions.forEach((interaction: pond.Interaction) => {

View file

@ -1,7 +1,7 @@
import {
GraphComponent,
GraphOrientation,
GraphPoint,
// GraphComponent,
// GraphOrientation,
// GraphPoint,
GraphType,
InteractionLine
} from "common/Graph";
@ -41,15 +41,15 @@ import {
Weight,
CapacitorCable
} from "pbHelpers/ComponentTypes";
import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
//import { multilineGrainCableData } from "pbHelpers/ComponentTypes/GrainCable";
//import { multilineCapCableData } from "./ComponentTypes/CapacitorCable";
import { findInteractionsAsSource } from "pbHelpers/Interaction";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { notNull, or } from "utils/types";
import { emptyComponentId } from "./Component";
import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
//import { emptyComponentId } from "./Component";
//import { multilinePressureCableData } from "./ComponentTypes/PressureCable";
import { describeMeasurement, MeasurementDescriber } from "./MeasurementDescriber";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
import { Sen5x } from "./ComponentTypes/Sen5x";
@ -622,194 +622,194 @@ export function GetComponentIcon(
}
//TODO: deprecated function, new graphs are handled in measurementChart (using reCharts instead of victory)
export function getGraphProps(
componentType: quack.ComponentType,
subtype: number,
measurements: pond.Measurement[],
interactions: Interaction[],
overlays: pond.ComponentOverlays[],
orientation: GraphOrientation,
filters?: GraphFilters
): any {
const componentID: quack.IComponentID = quack.ComponentID.fromObject(
or(measurements[0].measurement, {
id: emptyComponentId()
}).id as any
);
let graphProps = {
orientation: orientation
} as any;
let ext = extension(componentType, subtype);
ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
let describer = describeMeasurement(
componentMeasurement.measurementType,
componentType,
subtype
);
let range =
filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
let selectedInteractions =
filters && filters.selectedInteractions ? filters.selectedInteractions : [];
let interactionLines = getInteractionLines(
componentType,
subtype,
componentMeasurement.measurementType,
componentID,
interactions,
selectedInteractions
);
let selectedOverlays = filters?.selectedOverlays ?? [];
let overlayAreas = getOverlayAreas(
componentMeasurement.measurementType,
overlays,
selectedOverlays
);
graphProps["graphComponent" + (index + 1).toString()] = {
label: componentMeasurement.label,
data: [],
tick: describer.unit(),
colour: componentMeasurement.colour,
type: componentMeasurement.graphType,
range: range,
isBoolean:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
isPercent:
componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
(componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
interactionLines: interactionLines,
states: ext.states,
decimals: describer.decimals(),
overlays: overlayAreas
} as GraphComponent;
});
// export function getGraphProps(
// componentType: quack.ComponentType,
// subtype: number,
// measurements: pond.Measurement[],
// interactions: Interaction[],
// overlays: pond.ComponentOverlays[],
// orientation: GraphOrientation,
// filters?: GraphFilters
// ): any {
// const componentID: quack.IComponentID = quack.ComponentID.fromObject(
// or(measurements[0].measurement, {
// id: emptyComponentId()
// }).id as any
// );
// let graphProps = {
// orientation: orientation
// } as any;
// let ext = extension(componentType, subtype);
// ext.measurements.forEach((componentMeasurement: ComponentMeasurement, index: number) => {
// let describer = describeMeasurement(
// componentMeasurement.measurementType,
// componentType,
// subtype
// );
// let range =
// filters && filters.ranges && filters.ranges[index] ? filters.ranges[index] : undefined;
// let selectedInteractions =
// filters && filters.selectedInteractions ? filters.selectedInteractions : [];
// let interactionLines = getInteractionLines(
// componentType,
// subtype,
// componentMeasurement.measurementType,
// componentID,
// interactions,
// selectedInteractions
// );
// let selectedOverlays = filters?.selectedOverlays ?? [];
// let overlayAreas = getOverlayAreas(
// componentMeasurement.measurementType,
// overlays,
// selectedOverlays
// );
// graphProps["graphComponent" + (index + 1).toString()] = {
// label: componentMeasurement.label,
// data: [],
// tick: describer.unit(),
// colour: componentMeasurement.colour,
// type: componentMeasurement.graphType,
// range: range,
// isBoolean:
// componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN,
// isPercent:
// componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT ||
// (componentMeasurement.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_ANALOG &&
// subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL),
// interactionLines: interactionLines,
// states: ext.states,
// decimals: describer.decimals(),
// overlays: overlayAreas
// } as GraphComponent;
// });
//for new cables add here to get the data for multiline
if (showMultilineCable(componentType, filters)) {
let cableData: any = {};
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
cableData = multilineGrainCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
cableData = multilinePressureCableData(measurements, filters);
} else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
cableData = multilineCapCableData(measurements, filters);
}
//will loop through the keys in cableData
//IMPORTANT keys must be data1, data2, etc in the multiline function in the component
for (let i = 1; i <= Object.keys(cableData).length; i++) {
let gc = "graphComponent" + i;
if (graphProps[gc]) {
graphProps[gc].data = cableData["data" + i];
graphProps[gc].type = GraphType.MULTILINE;
}
}
} else {
measurements.forEach((reading: pond.Measurement, readIndex) => {
if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
let date = new Date(Date.parse(reading.timestamp));
for (let index = 0; index < ext.measurements.length; index++) {
let componentMeasurement: ComponentMeasurement = ext.measurements[index];
if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
break;
}
let dataPoint = null;
switch (componentMeasurement.graphType) {
case GraphType.AREA:
let avg = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
if (avg && avg.low !== null && avg.high !== null) {
dataPoint = {
x: date,
y: avg.low,
y0: avg.high
} as GraphPoint;
}
break;
case GraphType.SCATTER:
let scatter = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
if (scatter && scatter.value !== undefined && scatter.value !== null) {
dataPoint = {
x: date,
y: Number(scatter.value),
bubble: or(scatter.bubble, 0)
} as GraphPoint;
}
break;
case GraphType.RADAR:
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
// //for new cables add here to get the data for multiline
// if (showMultilineCable(componentType, filters)) {
// let cableData: any = {};
// if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
// cableData = multilineGrainCableData(measurements, filters);
// } else if (componentType === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE) {
// cableData = multilinePressureCableData(measurements, filters);
// } else if (componentType === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE) {
// cableData = multilineCapCableData(measurements, filters);
// }
// //will loop through the keys in cableData
// //IMPORTANT keys must be data1, data2, etc in the multiline function in the component
// for (let i = 1; i <= Object.keys(cableData).length; i++) {
// let gc = "graphComponent" + i;
// if (graphProps[gc]) {
// graphProps[gc].data = cableData["data" + i];
// graphProps[gc].type = GraphType.MULTILINE;
// }
// }
// } else {
// measurements.forEach((reading: pond.Measurement, readIndex) => {
// if (notNull(reading) && notNull(reading.timestamp) && notNull(reading.measurement)) {
// let date = new Date(Date.parse(reading.timestamp));
// for (let index = 0; index < ext.measurements.length; index++) {
// let componentMeasurement: ComponentMeasurement = ext.measurements[index];
// if (componentMeasurement.isErrorMeasurement(reading.measurement)) {
// break;
// }
// let dataPoint = null;
// switch (componentMeasurement.graphType) {
// case GraphType.AREA:
// let avg = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
// if (avg && avg.low !== null && avg.high !== null) {
// dataPoint = {
// x: date,
// y: avg.low,
// y0: avg.high
// } as GraphPoint;
// }
// break;
// case GraphType.SCATTER:
// let scatter = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
// if (scatter && scatter.value !== undefined && scatter.value !== null) {
// dataPoint = {
// x: date,
// y: Number(scatter.value),
// bubble: or(scatter.bubble, 0)
// } as GraphPoint;
// }
// break;
// case GraphType.RADAR:
// if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_WIND_DIRECTION) {
// let value = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
dataPoint = {
x: getWindDirection(value).dString,
y: getWindDirection(value).direction
};
}
break;
default:
//linear and bar
let value = componentMeasurement.extract(
or(reading.measurement, {} as quack.Measurement),
filters
);
// dataPoint = {
// x: getWindDirection(value).dString,
// y: getWindDirection(value).direction
// };
// }
// break;
// default:
// //linear and bar
// let value = componentMeasurement.extract(
// or(reading.measurement, {} as quack.Measurement),
// filters
// );
if (value !== undefined && value !== null) {
if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
if (value > 100) {
value = 0;
}
}
}
if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
if (readIndex < measurements.length - 1) {
//do math
//get the time of both the current reading and the reading before it
let currentTime = new Date(
Date.parse(measurements[readIndex].timestamp)
).valueOf();
let prevTime = new Date(
Date.parse(measurements[readIndex + 1].timestamp)
).valueOf();
// if (value !== undefined && value !== null) {
// if (componentType === quack.ComponentType.COMPONENT_TYPE_ANALOG_INPUT) {
// if (subtype === quack.AnalogInputSubtype.ANALOG_INPUT_SUBTYPE_FUEL) {
// if (value > 100) {
// value = 0;
// }
// }
// }
// if (componentType === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
// if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED) {
// if (readIndex < measurements.length - 1) {
// //do math
// //get the time of both the current reading and the reading before it
// let currentTime = new Date(
// Date.parse(measurements[readIndex].timestamp)
// ).valueOf();
// let prevTime = new Date(
// Date.parse(measurements[readIndex + 1].timestamp)
// ).valueOf();
//get the difference in seconds between the two
let deltaTime = (currentTime - prevTime) / 1000;
// //get the difference in seconds between the two
// let deltaTime = (currentTime - prevTime) / 1000;
//divide the ticks by the time between intervals for the average ticks per second and multiply
//by 2.4 to convert to km/h
value = (Number(value) / deltaTime) * 2.4;
} else {
value = 0;
}
}
if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
value = Math.round(value * 0.2794);
}
}
dataPoint = {
x: date,
y: Number(value)
} as GraphPoint;
}
break;
}
if (dataPoint !== null) {
graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
}
}
}
});
}
return graphProps;
}
// //divide the ticks by the time between intervals for the average ticks per second and multiply
// //by 2.4 to convert to km/h
// value = (Number(value) / deltaTime) * 2.4;
// } else {
// value = 0;
// }
// }
// if (subtype === quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_RAIN) {
// value = Math.round(value * 0.2794);
// }
// }
// dataPoint = {
// x: date,
// y: Number(value)
// } as GraphPoint;
// }
// break;
// }
// if (dataPoint !== null) {
// graphProps["graphComponent" + (index + 1).toString()].data.push(dataPoint);
// }
// }
// }
// });
// }
// return graphProps;
// }
function getWindDirection(value: number) {
//determine the general direction based on the value

View file

@ -4,8 +4,8 @@ import GrainCableLightIcon from "assets/components/grainCableLight.png";
import TemperatureHumidityDarkIcon from "assets/components/temperatureHumidityDark.png";
import TemperatureHumidityLightIcon from "assets/components/temperatureHumidityLight.png";
import { ExtractMoisture, grainName } from "grain";
import { GraphPoint } from "common/Graph";
import moment from "moment";
//import { GraphPoint } from "common/Graph";
//import moment from "moment";
import {
AreaChartData,
ComponentMeasurement,
@ -418,38 +418,38 @@ export function binSplitAt(filters?: GraphFilters): number {
}
//TODO: deprecated function with new measurements
export function multilineGrainCableData(
measurements: Array<pond.Measurement>,
filters?: GraphFilters
) {
let temperature: Array<Array<GraphPoint>> = [];
let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
let moisture: Array<Array<GraphPoint>> = [];
measurements.forEach((measurement: pond.Measurement, i) => {
let nodes = extractNodes(measurement.measurement);
let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
for (let j = 0; j < selectedNodes.length; j++) {
if (i === 0) {
temperature[j] = [];
humidity[j] = [];
moisture[j] = [];
}
let nodeIndex = selectedNodes[j];
if (nodeIndex < nodes.length) {
let node = nodes[nodeIndex];
let ts = moment(measurement.timestamp);
temperature[j].push({ x: ts, y: node.temperature });
humidity[j].push({ x: ts, y: node.humidity });
let grain =
isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
? pond.Grain.GRAIN_NONE
: or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
moisture[j].push({
x: ts,
y: ExtractMoisture(grain, node.temperature, node.humidity)
});
}
}
});
return { data1: temperature, data2: moisture, data3: humidity };
}
// export function multilineGrainCableData(
// measurements: Array<pond.Measurement>,
// filters?: GraphFilters
// ) {
// let temperature: Array<Array<GraphPoint>> = [];
// let humidity: Array<Array<GraphPoint>> = []; //humidity is not being used by the graphs
// let moisture: Array<Array<GraphPoint>> = [];
// measurements.forEach((measurement: pond.Measurement, i) => {
// let nodes = extractNodes(measurement.measurement);
// let selectedNodes = filters ? or(filters.selectedNodes, []) : [];
// for (let j = 0; j < selectedNodes.length; j++) {
// if (i === 0) {
// temperature[j] = [];
// humidity[j] = [];
// moisture[j] = [];
// }
// let nodeIndex = selectedNodes[j];
// if (nodeIndex < nodes.length) {
// let node = nodes[nodeIndex];
// let ts = moment(measurement.timestamp);
// temperature[j].push({ x: ts, y: node.temperature });
// humidity[j].push({ x: ts, y: node.humidity });
// let grain =
// isBinSplit(filters) && nodeIndex >= binSplitAt(filters)
// ? pond.Grain.GRAIN_NONE
// : or(filters, { grainType: pond.Grain.GRAIN_NONE }).grainType;
// moisture[j].push({
// x: ts,
// y: ExtractMoisture(grain, node.temperature, node.humidity)
// });
// }
// }
// });
// return { data1: temperature, data2: moisture, data3: humidity };
// }

View file

@ -110,6 +110,10 @@ export class MeasurementDescriber {
}
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
this.details.graph = GraphType.AREA;
if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){
this.details.label = "Cable ID"
this.details.unit = ""
}
}
if (componentType === quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT) {
this.details.path = "vpd.celciusTimes10";
@ -194,6 +198,10 @@ export class MeasurementDescriber {
if (componentType === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
this.details.label = "Humidity";
this.details.graph = GraphType.AREA;
if(componentSubtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_DIAG){
this.details.label = "Cable Type"
this.details.unit = ""
}
// can be used for testing node splitting on a cable if you dont have a component that uses it
// this.details.nodeDetails = {
// colours: ["white", "black"],

View file

@ -126,6 +126,10 @@ export interface IBinAPIContext {
showErrors?: boolean,
otherTeam?: string
) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>;
getBinsTrendData: (
bins: string[],
days: number
) => Promise<AxiosResponse<pond.GetBinsTrendDataResponse>>;
}
export const BinAPIContext = createContext<IBinAPIContext>({} as IBinAPIContext);
@ -629,6 +633,12 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
})
};
const getBinsTrendData = (bins: string[], days: number) => {
return get<pond.GetBinsTrendDataResponse>(
pondURL("/binTrends?bins=" + bins.toString() + "&days=" + days + (as ? "&as=" + as : ""))
);
};
return (
<BinAPIContext.Provider
value={{
@ -655,7 +665,8 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
listHistoryBetween,
listBinPrefs,
updateTopNodes,
listBinMeasurements
listBinMeasurements,
getBinsTrendData
}}>
{children}
</BinAPIContext.Provider>

View file

@ -88,7 +88,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const addDevice = (group: number, device: number) => {
let url = "/groups/" + group + "/devices/" + device + "/add";
if (as) url = pondURL(url + as)
if (as) url = url + "?as=" + as
return new Promise<AxiosResponse>((resolve, reject) => {
post(pondURL(url), group).then(resp => {
return resolve(resp)
@ -100,7 +100,7 @@ export default function GroupProvider(props: PropsWithChildren<Props>) {
const removeDevice = (group: number, device: number) => {
let url = "/groups/" + group + "/devices/" + device + "/remove";
if (as) url = pondURL(url + as)
if (as) url = url + "?as=" + as
return new Promise<AxiosResponse>((resolve, reject) => {
post(pondURL(url), group).then(resp => {
return resolve(resp)