Merge branch 'dev_environment' of gitlab.com:brandx/bxt-app into dev_environment

This commit is contained in:
Carter 2025-04-22 15:15:57 -06:00
commit eb7efada69
39 changed files with 346 additions and 651 deletions

View file

@ -108,7 +108,7 @@ export default function BinComponents(props: Props) {
let mounted = true;
setDevicesLoading(true);
deviceAPI
.list(100000, 0, "asc", "name")
.list(100000, 0, "asc", "name",undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,as)
.then(response => {
if (mounted) {
let data = response.data.devices ? response.data.devices : [];
@ -135,7 +135,7 @@ export default function BinComponents(props: Props) {
return () => {
mounted = false;
};
}, [deviceAPI]);
}, [deviceAPI, as]);
useEffect(() => {
loadDevices();
@ -149,7 +149,7 @@ export default function BinComponents(props: Props) {
comps = [];
setComponentsLoading(true);
componentAPI
.list(selectedDevice, false, [selectedDevice.toString()], ["device"], true)
.list(selectedDevice, false, [selectedDevice.toString()], ["device"], true, as)
.then(resp => {
let d = pond.ListComponentsResponse.fromObject(resp.data);
d.components.forEach(comp => {
@ -171,7 +171,7 @@ export default function BinComponents(props: Props) {
});
}
},
[selectedDevice, componentAPI, deviceComponents, snackbar]
[selectedDevice, componentAPI, deviceComponents, snackbar, as]
);
useEffect(() => {
@ -326,7 +326,7 @@ export default function BinComponents(props: Props) {
settings.grainType = binGrain;
settings.defaultMutations = [pond.Mutator.MUTATOR_EMC];
componentAPI
.update(device, settings)
.update(device, settings, undefined, undefined, as)
.then(resp => {
snackbar.info("Component added to bin, and updated cables grain type");
})

View file

@ -150,29 +150,12 @@ const useStyles = makeStyles((theme: Theme) => {
bottomSpacing: {
marginBottom: theme.spacing(1)
},
sliderRoot: {},
sliderThumb: {
left: 23
},
mobileSliderThumb: {
left: 28
},
sliderTrack: {
height: "100%",
},
sliderRail: {
height: "100%",
},
sliderValLabel: {
// left: -20,
height: 30,
width: 30,
top: -15,
//top: -15,
background: "transparent"
},
sliderLabel: {
background: "gold"
}
});
});
@ -1483,10 +1466,10 @@ export default function BinVisualizer(props: Props) {
orientation="vertical"
value={fillPercentage}
classes={{
root: classes.sliderRoot,
rail: classes.sliderRail,
track: classes.sliderTrack,
thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
// root: classes.sliderRoot,
// rail: classes.sliderRail,
// track: classes.sliderTrack,
// thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
valueLabel: classes.sliderValLabel,
}}
style={{ height: "100%", color: sliderColour }}

View file

@ -147,7 +147,7 @@ export default function GrainNodeInteractions(props: Props) {
settings.defaultMutations = [pond.Mutator.MUTATOR_EMC];
settings.grainType = grain;
componentAPI
.update(device.id(), settings, [binKey], ["bin"])
.update(device.id(), settings, [binKey], ["bin"], as)
.then(resp => {
openSnack("EMC set on cable " + cable.name());
updateComponentCallback(cable.key());
@ -157,7 +157,7 @@ export default function GrainNodeInteractions(props: Props) {
const submit = () => {
componentAPI
.update(device.id(), cable.settings, [binKey], ["bin"])
.update(device.id(), cable.settings, [binKey], ["bin"], as)
.then(resp => {
//after updating the component update the interactions with the new subtypes
//TODO-CS: make function to update multiple interactions at once to avoid this loop if it becomes a problem

View file

@ -11,6 +11,7 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { useComponentAPI, useSnackbar } from "hooks";
import { Component } from "models";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
// import { MatchParams } from "navigation/Routes";
// import { useRouteMatch } from "react-router";
import { useEffect, useState } from "react";
@ -40,6 +41,7 @@ export default function GraphSettings(props: Props) {
currentMax,
newChart
} = props;
const [{as}] = useGlobalState();
const [newMutation, setNewMutation] = useState<pond.Mutator>(0);
const componentAPI = useComponentAPI();
const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? "";
@ -58,7 +60,7 @@ export default function GraphSettings(props: Props) {
defaultMutations.push(newMutation);
}
componentAPI
.update(parseInt(deviceID), comp.settings)
.update(parseInt(deviceID), comp.settings, undefined, undefined, as)
.then(() => openSnack("Mutations Updated"));
closeDialog();
};
@ -70,7 +72,7 @@ export default function GraphSettings(props: Props) {
defaultMuts.splice(defaultMuts.indexOf(mutation));
}
componentAPI
.update(parseInt(deviceID), comp.settings)
.update(parseInt(deviceID), comp.settings, undefined, undefined, as)
.then(() => openSnack("Mutations Updated"));
closeDialog();
};

View file

@ -20,6 +20,7 @@ import { Component, Device } from "models";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";
interface TabPanelProps {
children?: React.ReactNode;
@ -83,6 +84,7 @@ export default function DeviceLinkDrawer(props: Props) {
devicePrefChanged
} = props;
const [value, setValue] = useState(0);
const [{as}] = useGlobalState();
const deviceAPI = useDeviceAPI();
const classes = useStyles();
const isMobile = useMobile();
@ -114,7 +116,13 @@ export default function DeviceLinkDrawer(props: Props) {
undefined,
undefined,
undefined,
true
true,
undefined,
undefined,
undefined,
undefined,
undefined,
as
)
.then(resp => {
let devMap = new Map<string, pond.ComprehensiveDevice>();
@ -131,7 +139,7 @@ export default function DeviceLinkDrawer(props: Props) {
setLoadingDevices(false);
});
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [deviceAPI]);
}, [deviceAPI, as]);
useEffect(() => {
let searchedDevices: Map<string, pond.ComprehensiveDevice> = new Map();

View file

@ -2,6 +2,7 @@ import { Dialog, DialogActions, DialogContent, DialogTitle, Grid2, TextField } f
import CancelSubmit from "common/CancelSubmit";
import { useComponentAPI, useSnackbar } from "hooks";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import { useState } from "react";
import { or } from "utils";
@ -13,12 +14,13 @@ interface Props {
export default function AddComponentManualDialog (props: Props) {
const { open, onClose, device } = props;
const [{as}] = useGlobalState();
const componentAPI = useComponentAPI();
const snackbar = useSnackbar();
const [component, setComponent] = useState<pond.ComponentSettings>(pond.ComponentSettings.create())
const onSubmit = () => {
componentAPI.add(device, component).then(() => {
componentAPI.add(device, component, as).then(() => {
snackbar.success("Component added")
})
}

View file

@ -22,7 +22,7 @@ import {
import ResponsiveDialog from "common/ResponsiveDialog";
import { Component, Device } from "models";
import { pond, quack } from "protobuf-ts/pond";
import { useComponentAPI, useSnackbar } from "providers";
import { useComponentAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { CableInfo } from "./ComponentDiagnostics";
import ComponentForm from "./ComponentForm";
@ -67,6 +67,7 @@ interface CompStep {
export default function AddCompsFromDiag(props: Props) {
const { diagComponent, port, cableInfo, open, closeDialog, device, refreshCallback } = props;
const [steps, setSteps] = useState<CompStep[]>([]);
const [{as}] = useGlobalState();
const classes = useStyles();
const [currentStep, setCurrentStep] = useState(0);
const [components, setComponents] = useState<Component[]>([]);
@ -125,7 +126,7 @@ export default function AddCompsFromDiag(props: Props) {
c.components.push(component.settings);
});
compAPI
.addMultiComponents(device.id(), c)
.addMultiComponents(device.id(), c, as)
.then(resp => {
success("Components added to Device");
})

View file

@ -82,7 +82,7 @@ export default function ComponentActions(props: Props) {
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
const [componentSettingsMode, setComponentSettingsMode] = useState<string>("");
const [isJSON, setIsJSON] = useState(false);
const [{ user }] = useGlobalState();
const [{ user, as }] = useGlobalState();
const openMoreMenu = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
setAnchorEl(event.currentTarget);
@ -182,6 +182,7 @@ export default function ComponentActions(props: Props) {
isJSON={isJSON}
// newMeasurements={newStructure}
user={user}
as={as}
/>
</React.Fragment>
);

View file

@ -120,7 +120,7 @@ export default function ComponentCard(props: Props) {
const navigate = useNavigate()
const location = useLocation()
const [sensors, setSensors] = useState<Sensor[]>([]);
const [{ user, showErrors }] = useGlobalState();
const [{ user, showErrors, as }] = useGlobalState();
const updateControllerState = (checked: boolean) => {
let updatedComponent = cloneDeep(component);
@ -130,7 +130,7 @@ export default function ComponentCard(props: Props) {
let describe = controllerModeLabel(newMode);
componentAPI
.update(device.id(), updatedComponent.settings)
.update(device.id(), updatedComponent.settings, undefined, undefined, as)
.then(() => {
success(component.name() + "'s mode was set to " + describe);
refreshCallback(updatedComponent);

View file

@ -16,7 +16,7 @@ import moment from "moment";
import { getHumanReadableAddress } from "pbHelpers/AddressType";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useComponentAPI } from "providers";
import { useComponentAPI, useGlobalState } from "providers";
import React, { useEffect, useState } from "react";
// import { getThemeType } from "theme";
import AddCompsFromDiag from "./AddCompsFromDiag";
@ -60,6 +60,7 @@ const useStyles = makeStyles((theme: Theme) => {
export default function ComponentDiagnostics(props: Props) {
const { component, device, refreshCallback } = props;
const [{as}] = useGlobalState();
const [portNumber, setPortNumber] = useState("");
const compAPI = useComponentAPI();
const classes = useStyles();
@ -91,7 +92,12 @@ export default function ComponentDiagnostics(props: Props) {
moment().toISOString(),
2,
0,
"desc"
"desc",
undefined,
undefined,
undefined,
undefined,
as
)
.then(resp => {
setMeasurements(resp.data.measurements);
@ -100,7 +106,7 @@ export default function ComponentDiagnostics(props: Props) {
setLoadingMeasurements(false);
});
}
}, [component, device, compAPI]); //eslint-disable-line react-hooks/exhaustive-deps
}, [component, device, compAPI, as]); //eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
let cableIDs: number[] = [];

View file

@ -25,6 +25,7 @@ import { pond, quack } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
import { useThemeType } from "../hooks/useThemeType";
import { useGlobalState } from "providers";
const useStyles = makeStyles((_theme: Theme) => {
const themeType = useThemeType()
@ -87,6 +88,7 @@ const filteredComponents = (components: Component[]) => {
export default function ComponentOrder(props: ListProps) {
const { open, close, device, devicePreferences } = props;
const [{as}] = useGlobalState();
const deviceAPI = useDeviceAPI();
const { error, success } = useSnackbar();
const prevDisplayOrder = usePrevious(devicePreferences.childDisplayOrder);
@ -113,7 +115,7 @@ export default function ComponentOrder(props: ListProps) {
let updatedPreferences = cloneDeep(devicePreferences);
updatedPreferences.childDisplayOrder = componentOrder.map(c => c.locationString());
deviceAPI
.updatePreferences(device.id(), updatedPreferences)
.updatePreferences(device.id(), updatedPreferences, undefined, undefined, as)
.then(() => success("Successfully update the component display order"))
.catch(() => {
error("Error occured while updating the component display order");

View file

@ -64,6 +64,7 @@ import {
} from "@mui/icons-material"
import CancelSubmit from "common/CancelSubmit";
import { useGlobalState } from "providers";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -158,6 +159,7 @@ export default function ComponentSettings(props: Props) {
addressTypeRestriction,
deviceComponentPrefs
} = props;
const [{as}] = useGlobalState();
const prevComponent = usePrevious(props.component);
const prevIsDialogOpen = usePrevious(isDialogOpen);
const steps = [0, 1];
@ -304,7 +306,7 @@ export default function ComponentSettings(props: Props) {
//component.settings.calibrationCoefficient = Number(form.coefficient);
if (cableID > 0) component.settings.addressType = cableID + 8;
componentAPI
.add(device.id(), component.settings)
.add(device.id(), component.settings, as)
.then((response: any) => {
success(component.name() + " was successfully added!");
refresh();
@ -318,13 +320,13 @@ export default function ComponentSettings(props: Props) {
const updateComponent = () => {
const component = formComponent;
componentAPI
.update(device.id(), component.settings, getContextKeys(), getContextTypes())
.update(device.id(), component.settings, getContextKeys(), getContextTypes(), as)
.then((response: any) => {
success(component.name() + " was successfully updated!");
let updatedPrefs = pond.DeviceComponentPreferences.create();
updatedPrefs.excludedNodes = excludedNodes;
deviceAPI
.updateComponentPreferences(device.id(), component.key(), updatedPrefs)
.updateComponentPreferences(device.id(), component.key(), updatedPrefs, undefined, undefined, as)
.then(resp => {
console.log("Preferences updated");
})
@ -343,7 +345,7 @@ export default function ComponentSettings(props: Props) {
const removeComponent = () => {
componentAPI
.remove(device.id(), formComponent.key(), getContextKeys(), getContextTypes())
.remove(device.id(), formComponent.key(), getContextKeys(), getContextTypes(), as)
.then((response: any) => {
success(formComponent.name() + " was successfully removed!");
refresh();

View file

@ -43,6 +43,7 @@ interface Props extends WithStyles<typeof styles> {
isJSON?: boolean;
newMeasurements?: boolean;
user?: User;
as?: string
}
interface State {
@ -91,9 +92,9 @@ class ExportDataSettings extends React.Component<Props, State> {
};
submit = () => {
const { device, component, user } = this.props;
const { device, component, user, as } = this.props;
const { startDate, endDate } = this.state;
const { sampleMeasurements, listUnitMeasurements } = this.context;
const { /*sampleMeasurements*/ listUnitMeasurements } = this.context;
this.setState({ isLoading: true });
// if (newMeasurements) {
@ -107,7 +108,9 @@ class ExportDataSettings extends React.Component<Props, State> {
"desc",
undefined,
[device.id().toString()],
["device"]
["device"],
undefined,
as
)
.then(resp => {
let measurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));

View file

@ -15,6 +15,7 @@ import DateSelect from "common/time/DateSelect";
import { useDeviceAPI } from "hooks";
import { Component, Device } from "models";
import { Moment } from "moment";
import { useGlobalState } from "providers";
import React, { useState } from "react";
import { downloadJSON } from "utils";
@ -27,6 +28,7 @@ interface Props {
export default function ArcGISDeviceData(props: Props) {
const { open, close, device, components } = props;
const [{as}] = useGlobalState();
const [startDate, setStartDate] = useState<Moment>(GetDefaultDateRange().start);
const [endDate, setEndDate] = useState<Moment>(GetDefaultDateRange().end);
const [includedComponents, setIncludedComponents] = useState<Component[]>([]);
@ -89,7 +91,8 @@ export default function ArcGISDeviceData(props: Props) {
limit,
0,
"asc",
"timestamp"
"timestamp",
as
)
.then(resp => {
downloadJSON(resp.data, filename + ".json");

View file

@ -15,6 +15,7 @@ import { useDeviceAPI, useSnackbar } from "hooks";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useState } from "react";
import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -36,6 +37,7 @@ export default function Connection(props: Props) {
const deviceAPI = useDeviceAPI();
const { success, error } = useSnackbar();
const { deviceID, deviceName, open, close } = props;
const [{as}] = useGlobalState();
const [gateway, setGateway] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [passwordVisible, setPasswordVisible] = useState<boolean>(false);
@ -71,7 +73,7 @@ export default function Connection(props: Props) {
let keys = getContextKeys();
let types = getContextTypes();
deviceAPI
.setWifi(deviceID, gateway, password, keys, types)
.setWifi(deviceID, gateway, password, keys, types, as)
.then(() => success("Connection settings sent to " + deviceName))
.catch(() => error("Failed to send connection settings to " + deviceName))
.finally(() => handleClose());

View file

@ -193,7 +193,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
comps = [];
setLoading(true);
let interactionPromise = interactionAPI.listInteractionsByDevice(deviceIndex, undefined, as);
let componentPromise = componentAPI.list(deviceIndex, undefined, [binKey], ["bin"], true);
let componentPromise = componentAPI.list(deviceIndex, undefined, [binKey], ["bin"], true, as);
let dComponents = new Map<string, Component[]>();
Promise.all([componentPromise, interactionPromise])
.then(([compResp, interactionResp]) => {
@ -367,13 +367,13 @@ export default function DevicePresets(props: DevicePresetsProps) {
//if the device id was passed in the mode was set to storage and we need to set them to off
heaters.forEach(heater => {
componentAPI
.update(deviceIndex, heater.settings)
.update(deviceIndex, heater.settings, undefined, undefined, as)
.then()
.catch();
});
fans.forEach(fan => {
componentAPI
.update(deviceIndex, fan.settings)
.update(deviceIndex, fan.settings, undefined, undefined, as)
.then()
.catch();
});

View file

@ -89,7 +89,7 @@ const deviceFromForm = (device: Device): Device => {
export default function DeviceSettings(props: Props) {
const classes = useStyles();
const [{ user }] = useGlobalState();
const [{ user, as }] = useGlobalState();
const navigate = useNavigate();
const { success, error } = useSnackbar();
const deviceAPI = useDeviceAPI();
@ -254,7 +254,7 @@ export default function DeviceSettings(props: Props) {
deviceForm.settings.extensionComponents = [compExtOne, compExtTwo, compExtThree];
deviceForm.settings.mutations = linearMutations;
deviceAPI
.update(deviceID, deviceForm.settings)
.update(deviceID, deviceForm.settings, as)
.then((_response: any) => {
success(deviceName + " was successfully updated!");
close();
@ -277,7 +277,7 @@ export default function DeviceSettings(props: Props) {
const removeDevice = () => {
const deviceName = deviceForm.name();
deviceAPI
.remove(device.id())
.remove(device.id(), as)
.then((_response: any) => {
success(deviceName + " was successfully deleted!");
navigate("/");

View file

@ -104,7 +104,8 @@ export default function Device(props: Props) {
undefined,
[deviceID.toString()],
["device"],
true
true,
as
);
let interactionsPromise = interactionsAPI.listInteractionsByDevice(deviceID, undefined, as);
let groupPromise: Promise<any> = Promise.resolve(undefined);
@ -343,7 +344,7 @@ export default function Device(props: Props) {
let updatedPreferences = cloneDeep(preferences);
updatedPreferences.notify = !preferences.notify;
deviceAPI
.updatePreferences(deviceID, updatedPreferences)
.updatePreferences(deviceID, updatedPreferences, undefined, undefined, as)
.then(() => setPreferences(updatedPreferences))
.catch(() => {
error(

View file

@ -26,7 +26,7 @@ import {
import { GetDeviceProductIcon, GetDeviceProductLabel } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useComponentAPI, useSnackbar } from "providers";
import { useComponentAPI, useGlobalState, useSnackbar } from "providers";
import React, { useState } from "react";
import DeviceSVG, { PortInformation } from "./deviceSVG";
import { makeStyles } from "@mui/styles";
@ -69,6 +69,7 @@ const useStyles = makeStyles((theme: Theme) => {
export default function DeviceWizard(props: Props) {
const { device, components, refreshCallback } = props;
const [{as}] = useGlobalState();
const [componentDialogOpen, setComponentDialogOpen] = useState(false);
const [availableOffsets, setAvailableOffsets] = useState<OffsetAvailabilityMap>(new Map());
const [availablePositions, setAvailablePositions] = useState<DeviceAvailabilityMap>(new Map());
@ -159,7 +160,7 @@ export default function DeviceWizard(props: Props) {
componentSettings.reportPeriodMs = 600000;
componentSettings.name = "Port " + selectedPort.label + " Auto Detect";
compAPI
.add(device.id(), componentSettings)
.add(device.id(), componentSettings, as)
.then(resp => {
success("Added Auto Detect Component to Port");
})

View file

@ -23,6 +23,7 @@ import { Backpack, Device } from "models";
import { backpackOptions, genericBackpacks } from "pbHelpers/Backpack";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
import { useGlobalState } from "providers";
interface Props {
isOpen: boolean;
@ -34,6 +35,7 @@ export default function ProvisionDevice(props: Props) {
const deviceAPI = useDeviceAPI();
const backpackAPI = useBackpackAPI();
const { error, success, warning, info } = useSnackbar();
const [{as}] = useGlobalState();
const { isOpen, closeDialogCallback, refreshCallback } = props;
const [provisioned, setProvisioned] = useState<boolean>(false);
const [idHex, setIdHex] = useState<string>("");
@ -98,7 +100,7 @@ export default function ProvisionDevice(props: Props) {
setDeviceLoading(true);
const deviceID = parseInt(idHex, 16);
deviceAPI
.get(deviceID)
.get(deviceID, undefined, undefined, undefined, as)
.then(response => {
let rDevice = Device.any(response.data);
setDevice(rDevice);
@ -149,7 +151,7 @@ export default function ProvisionDevice(props: Props) {
const submitProvision = () => {
setProvisioned(false);
deviceAPI
.add("", "", assembleBackpack())
.add("", "", assembleBackpack(), as)
.then((response: any) => {
let updatedIdHex = response.data.idHex;
let updatedKeyHex = response.data.keyHex;
@ -173,7 +175,7 @@ export default function ProvisionDevice(props: Props) {
settings.name = name;
settings.description = description;
deviceAPI
.update(deviceID, settings)
.update(deviceID, settings, as)
.then(() => {
success("Device " + deviceID.toString() + " was successfully updated");
})

View file

@ -1,388 +0,0 @@
import {
Alert,
AlertTitle,
Box,
Button,
Card,
CardActions,
CardContent,
CardHeader,
darken,
Divider,
Grid2 as Grid,
InputAdornment,
Skeleton,
Step,
StepLabel,
Stepper,
TextField,
Theme,
Typography
} from "@mui/material";
import { GrainOptions, MoistureToHumidity } from "grain";
import GrainDryingChart, { GrainDryingPoint } from "charts/GrainDryingChart";
import SearchSelect, { Option } from "common/SearchSelect";
import { DateRange, GetDefaultDateRange } from "common/time/DateRange";
import DateSelect from "common/time/DateSelect";
import { useComponentAPI } from "hooks";
import { cloneDeep } from "lodash";
import { Component, Device } from "models";
import moment, { Moment } from "moment";
import { extension } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useEffect, useState } from "react";
import { FindPlenumSensors } from "./GrainUtils";
import { makeStyles } from "@mui/styles";
const useStyles = makeStyles((theme: Theme) => {
const bg = darken(theme.palette.background.paper, theme.palette.mode === "light" ? 0.1 : 0.15);
return ({
card: {
backgroundColor: bg
},
stepper: {
padding: 0
},
header: {
paddingBottom: theme.spacing(1)
},
actions: {
paddingTop: theme.spacing(1)
},
gutter: {
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`
}
});
});
interface Props {
devices: Device[];
loading: boolean;
}
const devicesToOptions = (devices: Device[]): Option[] => {
return devices.map(d => {
return { value: d.id().toString(), label: d.name() } as Option;
});
};
//NOREF: has no references in codebase, is this not used anywhere?
export default function GrainDryingCalculator(props: Props) {
const classes = useStyles();
const componentAPI = useComponentAPI();
const grainOptions = GrainOptions();
const { devices, loading } = props;
const [device, setDevice] = useState<Option | undefined>(undefined);
const [deviceOptions, setDeviceOptions] = useState(devicesToOptions(devices));
const [components, setComponents] = useState<Component[]>([]);
const [componentOptions, setComponentOptions] = useState<Option[]>([]);
const [component, setComponent] = useState<Option | undefined>(undefined);
const [dateRange, setDateRange] = useState<DateRange>(GetDefaultDateRange());
const [grainType, setGrainType] = useState<Option | undefined>(
grainOptions.find(o => o.value === pond.Grain[pond.Grain.GRAIN_NONE])
);
const [rawGrainMoisture, setRawGrainMoisture] = useState<string>("15.0");
const [samples, setSamples] = useState<pond.Measurement[]>([]);
const [componentsLoading, setComponentsLoading] = useState(false);
const [samplesLoading, setSamplesLoading] = useState(false);
const steps = [0, 1, 2];
const [activeStep, setActiveStep] = useState(0);
const [chartData, setChartData] = useState<GrainDryingPoint[]>([]);
useEffect(() => {
setDeviceOptions(devicesToOptions(devices));
}, [devices]);
useEffect(() => {
let options = FindPlenumSensors(components).map(
c =>
({
value: c.key(),
label: c.name()
} as Option)
);
setComponentOptions(options);
setComponent(options[0] ? options[0] : undefined);
}, [components]);
useEffect(() => {
if (device) {
const deviceID = Number(device.value);
setComponentsLoading(true);
componentAPI
.list(deviceID)
.then(response => {
let data = response.data.components ? response.data.components : [];
let rComponents: Component[] = data.map((c: any) => {
return Component.create(pond.Component.fromObject(c));
});
setComponents(rComponents);
})
.catch(() => {
setComponents([]);
})
.finally(() => {
setComponentsLoading(false);
});
}
}, [componentAPI, device]);
useEffect(() => {
if (device && component) {
const deviceID = Number(device.value);
const componentKey = String(component.value);
setSamplesLoading(true);
componentAPI
.sampleMeasurements(deviceID, componentKey, dateRange.start, dateRange.end, 500)
.then(response => {
let measurements: pond.Measurement[] | undefined = response.data.measurements;
let newSamples = measurements
? cloneDeep(measurements).sort((a, b) => {
return b.timestamp > a.timestamp ? 1 : -1;
})
: [];
setSamples(newSamples);
})
.catch(() => {
setSamples([]);
})
.finally(() => {
setSamplesLoading(false);
});
}
}, [componentAPI, device, component, dateRange]);
// T = temperature im degrees Celsius
// RH = relative humidity (%)
const calculateVPD = (T: number, RH: number) => {
const es = 0.6108 * Math.exp((17.27 * T) / (T + 237.3));
const ea = (RH / 100) * es;
return ea - es;
};
const calculateEffectiveVPD = (initialVPD: number, curVPD: number) => {
return initialVPD - curVPD;
};
useEffect(() => {
let data: GrainDryingPoint[] = [];
let curComponent = component
? components.find(c => c.key() === component.value.toString())
: undefined;
if (curComponent) {
let ext = extension(curComponent.settings.type, curComponent.settings.subtype);
const grainMoisture = parseFloat(rawGrainMoisture) || 15.0;
samples.forEach(s => {
let measurement = s.measurement;
if (measurement) {
let T = 0;
let RH = 0;
ext.measurements.forEach(cm => {
if (cm.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
T = cm.extract(measurement as quack.Measurement);
}
if (cm.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
RH = cm.extract(measurement as quack.Measurement);
}
});
const grain = grainType
? pond.Grain[grainType.value as keyof typeof pond.Grain]
: pond.Grain.GRAIN_NONE;
let initialRH = MoistureToHumidity(grain, T, grainMoisture);
let initialVPD = calculateVPD(T, initialRH);
let vpd = calculateVPD(T, RH);
data.push({
timestamp: moment(s.timestamp).valueOf(),
dryScore: calculateEffectiveVPD(initialVPD, vpd)
});
}
});
}
setChartData(data);
}, [samples, rawGrainMoisture, grainType, component, components]);
const grainMoistureInvalid = () => {
return rawGrainMoisture === "" || isNaN(Number(rawGrainMoisture));
};
const initialSetup = () => {
const invalidGrainMoisture = grainMoistureInvalid();
return (
<Box marginX={1}>
<Typography variant="subtitle1" color="textSecondary" gutterBottom align="center">
Please input your grain type and moisture
</Typography>
<Box paddingY={1}>
<SearchSelect
selected={grainType}
changeSelection={o => setGrainType(o ? o : undefined)}
label="Grain Type"
options={grainOptions}
group
/>
</Box>
<Box paddingY={1}>
<TextField
value={rawGrainMoisture}
onChange={event => setRawGrainMoisture(event.target.value)}
fullWidth
label="Grain Moisture"
variant="outlined"
error={invalidGrainMoisture}
helperText={invalidGrainMoisture ? "Invalid number" : ""}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<InputAdornment position="end">% WB</InputAdornment>
</InputAdornment>
)
}}
/>
</Box>
</Box>
);
};
const setupChart = () => {
return (
<Box>
<Typography variant="subtitle1" color="textSecondary" gutterBottom align="center">
Please select your plenum moisture sensor
</Typography>{" "}
<Grid container spacing={2}>
<Grid size={{ xs: 12 }}>
<SearchSelect
label="Device"
selected={device}
disabled={loading}
options={deviceOptions}
changeSelection={option => setDevice(option ? option : undefined)}
/>
</Grid>
<Grid size={{ xs: 12 }}>
<SearchSelect
label="Moisture Sensor"
selected={component}
options={componentOptions}
disabled={!device}
loading={componentsLoading}
changeSelection={option => setComponent(option ? option : undefined)}
/>
</Grid>
</Grid>
</Box>
);
};
const analyzeChart = () => {
return (
<Box>
{samplesLoading ? (
<Skeleton variant="rectangular" />
) : chartData.length > 1 ? (
<GrainDryingChart data={chartData} />
) : (
<Alert severity="info" variant="outlined">
<AlertTitle>No data to display graph</AlertTitle>
<Box>Tips:</Box>
<Box> - increase the time range</Box>
<Box> - select an active moisture sensor</Box>
</Alert>
)}
<DateSelect
updateDateRange={(newStart: Moment, newEnd: Moment) =>
setDateRange({ start: newStart, end: newEnd })
}
startDate={dateRange.start}
endDate={dateRange.end}
/>
<Typography variant="caption" color="textSecondary">
Effective Vapor Pressure Deficit (VPD) represents the potential for moisture to be added
or removed
</Typography>
</Box>
);
};
const stepContent = () => {
switch (activeStep) {
case 1:
return setupChart();
case 2:
return analyzeChart();
default:
return initialSetup();
}
};
const stepComplete = (): boolean => {
switch (activeStep) {
case 0:
return grainType !== undefined && !grainMoistureInvalid();
case 1:
return device !== undefined && component !== undefined;
default:
return true;
}
};
const actions = () => {
return (
<CardActions className={classes.actions}>
<Box display="flex" width="100%" justifyContent="space-between">
<Button
color="primary"
onClick={() => setActiveStep(activeStep - 1)}
disabled={activeStep <= 0}>
Back
</Button>
<Button
color="primary"
onClick={() => setActiveStep(activeStep + 1)}
disabled={activeStep >= steps.length - 1 || !stepComplete()}>
Next
</Button>
</Box>
</CardActions>
);
};
if (loading) return <Skeleton variant="rectangular" width="100%" height="300px" />;
return (
<Card elevation={4} className={classes.card}>
<CardHeader
title="Grain Drying Calculator"
subheader={
<Typography variant="body2" color="textSecondary">
Find out how well your grain is drying
</Typography>
}
className={classes.header}
/>
<CardContent className={classes.gutter}>
<Stepper activeStep={activeStep} className={classes.stepper} alternativeLabel>
<Step key={0}>
<StepLabel>Grain Moisture</StepLabel>
</Step>
<Step key={1}>
<StepLabel>Select Plenum</StepLabel>
</Step>
<Step key={2}>
<StepLabel>Analyze</StepLabel>
</Step>
</Stepper>
<Box paddingY={1}>
<Divider />
</Box>
<Box paddingTop={1}>{stepContent()}</Box>
</CardContent>
{actions()}
</Card>
);
}

View file

@ -1,5 +1,4 @@
export * from "./GrainDescriber";
export * from "./GrainDryingCalculator";
export * from "./GrainMoisture";
export * from "./GrainUse";
export * from "./GrainUtils";

View file

@ -32,6 +32,7 @@ import { useDeviceAPI, useGroupAPI, usePrevious, useSnackbar } from "hooks";
import { Device, Group } from "models";
import { filterDevices } from "pbHelpers/Device";
import { groupsAreEqual } from "pbHelpers/Group";
import { useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react";
import RemoveGroup from "./RemoveGroup";
@ -76,6 +77,7 @@ export default function GroupSettings(props: Props) {
groupDevices,
} = props;
const prevInitialGroup = usePrevious(initialGroup);
const [{as}] = useGlobalState();
const groupAPI = useGroupAPI();
const deviceAPI = useDeviceAPI();
const snackbar = useSnackbar()
@ -103,7 +105,7 @@ export default function GroupSettings(props: Props) {
const loadDevices = useCallback(() => {
setLoadingDevices(true);
deviceAPI
.list(1000000, 0, "asc")
.list(1000000, 0, "asc", undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,as)
.then((response: any) => {
let rDevices: Device[] = response.data.devices
? response.data.devices.map((device: any) => Device.any(device))
@ -115,7 +117,7 @@ export default function GroupSettings(props: Props) {
setDevices([]);
})
.finally(() => setLoadingDevices(false));
}, [deviceAPI]);
}, [deviceAPI, as]);
useEffect(() => {
if (prevInitialGroup !== initialGroup) {

View file

@ -4,7 +4,7 @@ import DeviceViewer from "device/DeviceViewer";
import MapMarkerSettings from "maps/MapMarkerSettings";
import { Device } from "models";
import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useSnackbar } from "providers";
import { useDeviceAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
interface Props {
@ -19,6 +19,7 @@ interface Props {
export default function DeviceDrawer(props: Props) {
const { open, onClose, selectedDevice, devices, removeMarker, updateMarker, moveMap } = props;
const [{as}] = useGlobalState();
const [device, setDevice] = useState<Device>(Device.create());
const [openMarkerSettings, setOpenMarkerSettings] = useState(false);
const deviceAPI = useDeviceAPI();
@ -96,7 +97,7 @@ export default function DeviceDrawer(props: Props) {
settings.longitude = 0;
settings.latitude = 0;
deviceAPI
.update(device.id(), settings)
.update(device.id(), settings, as)
.then(resp => {
openSnack("Marker Removed");
//then use the removeMarker prop function to update the markers in the parent map
@ -138,7 +139,7 @@ export default function DeviceDrawer(props: Props) {
let settings = device.settings;
settings.theme = newTheme;
deviceAPI
.update(device.id(), settings)
.update(device.id(), settings, as)
.then(resp => {
openSnack("marker settings updated");
updateMarker(device.id().toString(), settings);

View file

@ -503,7 +503,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
const loadDevices = useCallback(() => {
deviceAPI
.list(500, 0, "asc", "name")
.list(500, 0, "asc", "name", undefined, undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,as)
.then(resp => {
let map = new Map<string, DeviceModel>();
let newDevMarkers: Map<string, MarkerData> = new Map();
@ -557,7 +557,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
.catch(() => {
openSnack("Failed to load Devices");
});
}, [deviceAPI, openSnack]); // eslint-disable-line react-hooks/exhaustive-deps
}, [deviceAPI, openSnack, as]); // eslint-disable-line react-hooks/exhaustive-deps
const fmIcon = (type: pond.FieldMarkerType, size: number) => {
switch (type) {
@ -1042,7 +1042,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
device.settings.longitude = long;
device.settings.latitude = lat;
deviceAPI
.update(device.id(), device.settings)
.update(device.id(), device.settings, as)
.then(resp => {
openSnack("Device location Updated");
if (deviceOptions.includes(device)) {

View file

@ -194,7 +194,7 @@ export default function ConstructionMapController(props: Props) {
if (loadingDevices) return;
setLoadingDevices(true);
deviceAPI
.list(500, 0, "asc", "name", undefined, undefined, undefined, undefined, undefined, true)
.list(500, 0, "asc", "name", undefined, undefined, undefined, undefined, undefined, true, undefined, undefined,undefined,undefined,undefined,as)
.then(resp => {
let devOps: Device[] = []; //only devices that are not mapped
let deviceMap: Map<string, Device> = new Map<string, Device>(); // all devices
@ -252,7 +252,7 @@ export default function ConstructionMapController(props: Props) {
.catch(err => {
openSnack("Failed to Load Devices");
});
}, [deviceAPI, openSnack]); // eslint-disable-line react-hooks/exhaustive-deps
}, [deviceAPI, openSnack, as]); // eslint-disable-line react-hooks/exhaustive-deps
const loadGroups = useCallback(() => {
groupAPI
@ -527,7 +527,7 @@ export default function ConstructionMapController(props: Props) {
device.settings.longitude = long;
device.settings.latitude = lat;
deviceAPI
.update(device.id(), device.settings)
.update(device.id(), device.settings, as)
.then(resp => {
openSnack("Device location Updated");
if (devOps.includes(device)) {

View file

@ -90,7 +90,7 @@ export default function ObjectHeaterCharts(props: Props) {
const now = moment();
const theme = useTheme();
const [mutationData, setMutationData] = useState<MutationData[]>([]);
const [{ user }] = useGlobalState();
const [{ user, as }] = useGlobalState();
const themeType = useThemeType();
const classes = useStyles();
const [mutationsLoading, setMutationsLoading] = useState(false);
@ -165,7 +165,7 @@ export default function ObjectHeaterCharts(props: Props) {
setTempOnly(tempOnly);
setHeater(heater);
deviceAPI
.listJSONMeasurements(dev.id(), measurementsFor, start, end, 0, 0, "asc", "timestamp")
.listJSONMeasurements(dev.id(), measurementsFor, start, end, 0, 0, "asc", "timestamp", as)
.then(resp => {
resp.data.components.forEach(comp => {
if (comp.componentId === tempHum?.key()) {
@ -192,7 +192,7 @@ export default function ObjectHeaterCharts(props: Props) {
}
});
});
}, [device, deviceAPI, start, end]); //eslint-disable-line react-hooks/exhaustive-deps
}, [device, deviceAPI, start, end, as]); //eslint-disable-line react-hooks/exhaustive-deps
const updateDateRange = (newStartDate: any, newEndDate: any, live: boolean) => {
setStart(newStartDate);

View file

@ -414,7 +414,7 @@ export default function Bin(props: Props) {
const reLoadSingleComponent = (componentKey: string) => {
let dev = componentDevices.get(componentKey);
if (dev) {
componentAPI.get(dev, componentKey).then(resp => {
componentAPI.get(dev, componentKey, undefined, undefined, as).then(resp => {
let compMap = components;
let comp = Component.any(resp.data);
compMap.set(comp.key(), comp);

View file

@ -54,7 +54,7 @@ export default function DevicePage() {
// console.log("load device page data")
if (loading) return
setLoading(true)
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes()).then(resp => {
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => {
let device = Device.any(resp.data.device)
// console.log(resp.data.device)
setDevice(device)
@ -143,13 +143,13 @@ export default function DevicePage() {
useEffect(() => {
loadDevice()
}, [deviceID])
}, [deviceID, as])
const toggleNotificationPreference = () => {
let updatedPreferences = cloneDeep(preferences);
updatedPreferences.notify = !preferences.notify;
deviceAPI
.updatePreferences(deviceID, updatedPreferences, getContextKeys(), getContextTypes())
.updatePreferences(deviceID, updatedPreferences, getContextKeys(), getContextTypes(), as)
.then(() => setPreferences(updatedPreferences))
.catch(() => {
snackbar.error(

View file

@ -166,7 +166,8 @@ export default function DeviceComponent() {
false,
getContextKeys(),
getContextTypes(),
showErrors
showErrors,
as
)
.then(resp => {
if (resp.data.measurements) {
@ -179,7 +180,7 @@ export default function DeviceComponent() {
.finally(() => {
setSampling(false);
});
}, [componentAPI, deviceID, componentID, user, startDate, endDate, showErrors]);
}, [componentAPI, deviceID, componentID, user, startDate, endDate, showErrors, as]);
useEffect(() => {
if (live.current && liveSamples) {
@ -206,7 +207,7 @@ export default function DeviceComponent() {
if (user.settings.id === "") return;
setLoadingInitial(true);
deviceAPI
.get(deviceID, false, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1))
.get(deviceID, false, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1), as)
.then((response: any) => {
const rDevice = Device.any(response.data);
setDevice(rDevice);
@ -215,10 +216,11 @@ export default function DeviceComponent() {
false,
getContextKeys(),
getContextTypes(),
true
true,
as
);
let userPromise = userAPI.getUser(user.settings.id, deviceScope(deviceID.toString()));
let prefPromise = deviceAPI.listDeviceComponentPreferences(deviceID, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1));
let prefPromise = deviceAPI.listDeviceComponentPreferences(deviceID, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1), as);
Promise.all([componentsPromise, userPromise, prefPromise])
.then((responses: any) => {
const rawComponents: Array<any> = or(responses[0].data.components, []).sort(
@ -427,7 +429,8 @@ export default function DeviceComponent() {
undefined,
getContextKeys(),
getContextTypes(),
showErrors
showErrors,
as
)
.then(response => {
let total: number = response.data.total ? response.data.total : 0;
@ -458,7 +461,7 @@ export default function DeviceComponent() {
useEffect(() => {
load()
}, [page, pageSize])
}, [page, pageSize, as])
const columns = (): Column<convertedUnitMeasurement>[] => {
return [

View file

@ -10,6 +10,7 @@ import { useEffect, useState } from "react";
import { useParams } from "react-router";
import DeviceHistory from "device/DeviceHistory";
import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -24,6 +25,7 @@ interface Props {}
export default function DeviceHistoryPage(_props: Props) {
const classes = useStyles();
const { error } = useSnackbar();
const [{as}] = useGlobalState();
const deviceAPI = useDeviceAPI();
const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? "";
const [device, setDevice] = useState(Device.any({ settings: { deviceId: deviceID } }));
@ -32,7 +34,7 @@ export default function DeviceHistoryPage(_props: Props) {
useEffect(() => {
(async function load() {
await deviceAPI
.get(deviceID)
.get(deviceID, undefined, undefined, undefined, as)
.then((response: any) => {
setDevice(Device.any(response.data));
})

View file

@ -213,6 +213,7 @@ export default function Devices() {
getTypes(),
fieldContains,
search,
as
).then(resp => {
let newDevices: Device[] = []
resp.data.devices.forEach(device => {

View file

@ -4,7 +4,7 @@ import { Device, Group } from "models"
import { useEffect, useState } from "react"
import { useLocation, useNavigate, useParams } from "react-router-dom";
import PageContainer from "./PageContainer";
import { useDeviceAPI, useGroupAPI } from "providers";
import { useDeviceAPI, useGlobalState, useGroupAPI } from "providers";
import GroupActions from "group/GroupActions";
import { pond } from "protobuf-ts/pond";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
@ -66,6 +66,7 @@ export default function GroupPage() {
const groupID = parseInt(useParams<{ groupID: string }>()?.groupID ?? "0");
const { state } = useLocation();
const [{as}] = useGlobalState();
const [group, setGroup] = useState<Group>(state?.group ? Group.create(state.group) : Group.create())
const [loadingGroup, setLoadingGroup] = useState(false)
const [loadingDevices, setLoadingDevices] = useState(false)
@ -131,7 +132,10 @@ export default function GroupPage() {
undefined,
undefined,
getKeys(),
getTypes()
getTypes(),
undefined,
undefined,
as
).then(resp => {
let newDevices: Device[] = [];
resp.data.devices.forEach(device => {

View file

@ -166,7 +166,10 @@ export default function Heater() {
true,
true,
[heaterID],
["objectHeater"]
["objectHeater"],
undefined,
undefined,
as
)
.then(resp => {
setLoadingDevs(false);
@ -185,7 +188,7 @@ export default function Heater() {
});
}
}
}, [location, heaterAPI, heaterID, deviceAPI]); //eslint-disable-line react-hooks/exhaustive-deps
}, [location, heaterAPI, heaterID, deviceAPI, as]); //eslint-disable-line react-hooks/exhaustive-deps
const disconnectDisplayDevice = () => {
if (displayDevice) {

View file

@ -18,36 +18,41 @@ export interface IComponentAPIContext {
url: string,
componentKey: string
) => Promise<any>;
add: (device: number, settings: pond.ComponentSettings) => Promise<AxiosResponse<pond.AddComponentResponse>>;
add: (device: number, settings: pond.ComponentSettings, as?: string) => Promise<AxiosResponse<pond.AddComponentResponse>>;
addMultiComponents: (
device: number,
components: pond.MultiComponentSettings
components: pond.MultiComponentSettings,
as?: string
) => Promise<AxiosResponse<pond.AddMultiComponentsResponse>>;
update: (
device: number,
settings: pond.ComponentSettings,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<AxiosResponse<pond.UpdateComponentResponse>>;
remove: (device: number, component: string, keys?: string[], types?: string[]) => Promise<AxiosResponse<pond.RemoveComponentResponse>>;
get: (device: number, component: string, keys?: string[], types?: string[]) => Promise<AxiosResponse<pond.Component>>;
remove: (device: number, component: string, keys?: string[], types?: string[], as?: string) => Promise<AxiosResponse<pond.RemoveComponentResponse>>;
get: (device: number, component: string, keys?: string[], types?: string[], as?: string) => Promise<AxiosResponse<pond.Component>>;
list: (
device: number | string,
demo?: boolean,
keys?: string[],
types?: string[],
comprehensive?: boolean
comprehensive?: boolean,
as?: string
) => Promise<AxiosResponse<pond.ListComponentsResponse>>;
listForObject: (
keys: string[],
types: string[]
types: string[],
as?:string
) => Promise<AxiosResponse<pond.ListComponentsResponse>>;
listComponentCardData: (
device: number | string,
demo?: boolean,
keys?: string[],
types?: string[],
comprehensive?: boolean
comprehensive?: boolean,
as?: string
) => Promise<AxiosResponse<pond.ListComponentCardDataResponse>>;
listHistory: (
deviceId: string | number,
@ -57,30 +62,32 @@ export interface IComponentAPIContext {
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.ListComponentHistoryResponse>>;
listMeasurements: (
device: number,
component: string,
startDate: any,
endDate: any,
limit: number,
offset: number,
order: string,
orderBy: string,
demo?: boolean,
keys?: string[],
types?: string[]
) => //exportMeasurements?: boolean
Promise<AxiosResponse<pond.ListMeasurementsResponse>>;
sampleMeasurements: (
device: number | string,
component: string,
startDate: any,
endDate: any,
sampleSize: number,
demo?: boolean,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.SampleMeasurementsResponse>>;
// Old measuremnt structure functions, no longer used in codebase
// listMeasurements: (
// device: number,
// component: string,
// startDate: any,
// endDate: any,
// limit: number,
// offset: number,
// order: string,
// orderBy: string,
// demo?: boolean,
// keys?: string[],
// types?: string[]
// ) => //exportMeasurements?: boolean
// Promise<AxiosResponse<pond.ListMeasurementsResponse>>;
// sampleMeasurements: (
// device: number | string,
// component: string,
// startDate: any,
// endDate: any,
// sampleSize: number,
// demo?: boolean,
// keys?: string[],
// types?: string[],
// as?: string
// ) => Promise<AxiosResponse<pond.SampleMeasurementsResponse>>;
sampleUnitMeasurements: (
device: number | string,
component: string,
@ -90,7 +97,8 @@ export interface IComponentAPIContext {
demo?: boolean,
keys?: string[],
types?: string[],
showErrors?: boolean
showErrors?: boolean,
as?: string
) => Promise<AxiosResponse<pond.SampleUnitMeasurementsResponse>>;
// possibly a deprecated function as it is not used anywhere
updateComponentPreferences: (
@ -111,7 +119,8 @@ export interface IComponentAPIContext {
measurementType?: number | quack.MeasurementType,
keys?: string[],
types?: string[],
showErrors?: boolean
showErrors?: boolean,
as?: string
) => Promise<AxiosResponse<pond.ListUnitMeasurementsResponse>>;
}
@ -122,7 +131,7 @@ interface Props {}
export default function ComponentProvider(props: PropsWithChildren<Props>) {
const { children } = props;
const { get, post, put, del } = useHTTP();
const [{ as }] = useGlobalState();
//const [{ as }] = useGlobalState();
const newCron = (
device: number,
@ -147,7 +156,7 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
);
};
const addComponent = (device: number, settings: pond.ComponentSettings) => {
const addComponent = (device: number, settings: pond.ComponentSettings, as?: string) => {
let url = "/devices/" + device + "/components"
if (as) url = url + `?as=${as}`
return new Promise<AxiosResponse<pond.AddComponentResponse>>((resolve, reject) => {
@ -160,7 +169,7 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
})
};
const addMultiComponents = (device: number, components: pond.MultiComponentSettings) => {
const addMultiComponents = (device: number, components: pond.MultiComponentSettings, as?: string) => {
let url = "/devices/" + device + "/multiComponents"
if (as) url = url + `?as=${as}`
return new Promise<AxiosResponse<pond.AddMultiComponentsResponse>>((resolve, reject)=>{
@ -177,7 +186,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
device: number,
settings: pond.ComponentSettings,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let k: string[] = keys ? keys : [];
if (!k.includes(device.toString())) k.push(device.toString()); //if the device id is already in the keys do not add it again
@ -207,7 +217,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
device: number,
component: string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let k: string[] = keys ? keys : [];
if (!k.includes(device.toString())) k.push(device.toString()); //if the device id is already in the keys do not add it again
@ -232,7 +243,7 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
})
};
const getComponent = (device: number, component: string, keys?: string[], types?: string[]) => {
const getComponent = (device: number, component: string, keys?: string[], types?: string[], as?: string) => {
let k: string[] = keys ? keys : [];
if (!k.includes(device.toString())) k.push(device.toString()); //if the device id is already in the keys do not add it again
let t: string[] = types ? types : [];
@ -261,7 +272,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
demo: boolean = false,
keys?: string[],
types?: string[],
comprehensive?: boolean
comprehensive?: boolean,
as?: string
) => {
let k = keys ? keys : [];
let t = types ? types : [];
@ -291,7 +303,7 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
})
};
const listComponentsForObject = (keys: string[], types: string[]) => {
const listComponentsForObject = (keys: string[], types: string[], as?: string) => {
let url = pondURL("/components/forObject?keys=" + keys + "&types=" + types + (as ? "&as=" + as : ""))
return new Promise<AxiosResponse<pond.ListComponentsResponse>>((resolve, reject) => {
get<pond.ListComponentsResponse>(url).then(resp => {
@ -308,7 +320,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
demo: boolean = false,
keys?: string[],
types?: string[],
comprehensive = false
comprehensive = false,
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -368,84 +381,86 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
})
};
const listMeasurements = (
device: number,
component: string,
startDate: any,
endDate: any,
limit: number,
offset: number,
order: string,
orderBy: string,
demo: boolean = false,
keys?: string[],
types?: string[]
// exportMeasurements: boolean = false
) => {
const url = pondURL(
"/devices/" +
device +
"/components/" +
component +
"/measurements" +
//(exportMeasurements ? "/export" : "") +
dateRange(startDate, endDate) +
"&limit=" +
limit +
"&offset=" +
offset +
"&order=" +
order +
"&by=" +
orderBy +
(as ? "&as=" + as : "") +
(keys ? "&keys=" + keys : "&keys=" + [device]) +
(types ? "&types=" + types : "&types=" + ["device"]),
demo
);
return new Promise<AxiosResponse<pond.ListMeasurementsResponse>>((resolve,reject) => {
return get<pond.ListMeasurementsResponse>(url).then(resp => {
resp.data = pond.ListMeasurementsResponse.fromObject(resp.data)
return resolve(resp)
}).catch(err => {
return reject(err)
})
})
};
// const listMeasurements = (
// device: number,
// component: string,
// startDate: any,
// endDate: any,
// limit: number,
// offset: number,
// order: string,
// orderBy: string,
// demo: boolean = false,
// keys?: string[],
// types?: string[],
// as?: string
// // exportMeasurements: boolean = false
// ) => {
// const url = pondURL(
// "/devices/" +
// device +
// "/components/" +
// component +
// "/measurements" +
// //(exportMeasurements ? "/export" : "") +
// dateRange(startDate, endDate) +
// "&limit=" +
// limit +
// "&offset=" +
// offset +
// "&order=" +
// order +
// "&by=" +
// orderBy +
// (as ? "&as=" + as : "") +
// (keys ? "&keys=" + keys : "&keys=" + [device]) +
// (types ? "&types=" + types : "&types=" + ["device"]),
// demo
// );
// return new Promise<AxiosResponse<pond.ListMeasurementsResponse>>((resolve,reject) => {
// return get<pond.ListMeasurementsResponse>(url).then(resp => {
// resp.data = pond.ListMeasurementsResponse.fromObject(resp.data)
// return resolve(resp)
// }).catch(err => {
// return reject(err)
// })
// })
// };
const sampleMeasurements = (
device: number | string,
component: string,
startDate: any,
endDate: any,
sampleSize: number,
demo: boolean = false,
keys?: string[],
types?: string[]
) => {
const url = pondURL(
"/devices/" +
device +
"/components/" +
component +
"/measurements/sample" +
dateRange(startDate, endDate) +
"&size=" +
sampleSize +
(as ? "&as=" + as : "") +
(keys ? "&keys=" + keys : "&keys=" + [device]) +
(types ? "&types=" + types : "&types=" + ["device"]),
demo
);
return new Promise<AxiosResponse<pond.SampleMeasurementsResponse>>((resolve, reject) => {
get<pond.SampleMeasurementsResponse>(url).then(resp => {
resp.data = pond.SampleMeasurementsResponse.fromObject(resp.data)
return resolve(resp)
}).catch(err => {
return reject(err)
})
})
};
// const sampleMeasurements = (
// device: number | string,
// component: string,
// startDate: any,
// endDate: any,
// sampleSize: number,
// demo: boolean = false,
// keys?: string[],
// types?: string[],
// as?: string
// ) => {
// const url = pondURL(
// "/devices/" +
// device +
// "/components/" +
// component +
// "/measurements/sample" +
// dateRange(startDate, endDate) +
// "&size=" +
// sampleSize +
// (as ? "&as=" + as : "") +
// (keys ? "&keys=" + keys : "&keys=" + [device]) +
// (types ? "&types=" + types : "&types=" + ["device"]),
// demo
// );
// return new Promise<AxiosResponse<pond.SampleMeasurementsResponse>>((resolve, reject) => {
// get<pond.SampleMeasurementsResponse>(url).then(resp => {
// resp.data = pond.SampleMeasurementsResponse.fromObject(resp.data)
// return resolve(resp)
// }).catch(err => {
// return reject(err)
// })
// })
// };
const sampleUnitMeasurements = (
device: number | string,
@ -456,7 +471,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
demo: boolean = false,
keys?: string[],
types?: string[],
showErrors: boolean = true
showErrors: boolean = true,
as?: string
) => {
const url = pondURL(
"/devices/" +
@ -519,7 +535,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
measurementType?: number | quack.MeasurementType,
keys?: string[],
types?: string[],
showErrors?: boolean
showErrors?: boolean,
as?: string
) => {
const url = pondURL(
"/devices/" +
@ -563,8 +580,8 @@ export default function ComponentProvider(props: PropsWithChildren<Props>) {
listForObject: listComponentsForObject,
listComponentCardData: listComponentCardData,
listHistory,
listMeasurements: listMeasurements,
sampleMeasurements: sampleMeasurements,
//listMeasurements: listMeasurements,
//sampleMeasurements: sampleMeasurements,
sampleUnitMeasurements,
updateComponentPreferences: updateComponentPreferences,
listUnitMeasurements

View file

@ -1,6 +1,6 @@
import { useHTTP, usePermissionAPI } from "hooks";
// import { useWebsocket } from "websocket";
import { /*Component,*/ Component, deviceScope, User } from "models";
import { /*Component,*/ Component, deviceScope, Team, User } from "models";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
@ -12,18 +12,19 @@ import { dateRange } from "providers/http";
// import { reject, result } from "lodash";
export interface IDeviceAPIContext {
add: (name: string, description: string, backpack: pond.BackpackSettings) => Promise<AxiosResponse<pond.AddDeviceResponse>>;
update: (id: number, settings: pond.DeviceSettings) => Promise<AxiosResponse<pond.UpdateDeviceResponse>>;
remove: (id: number) => Promise<AxiosResponse<pond.RemoveDeviceResponse>>;
get: (id: number | string, demo?: boolean, keys?: string[], types?: string[]) => Promise<AxiosResponse<pond.Device>>
add: (name: string, description: string, backpack: pond.BackpackSettings, as?: string) => Promise<AxiosResponse<pond.AddDeviceResponse>>;
update: (id: number, settings: pond.DeviceSettings, as?: string) => Promise<AxiosResponse<pond.UpdateDeviceResponse>>;
remove: (id: number, as?: string) => Promise<AxiosResponse<pond.RemoveDeviceResponse>>;
get: (id: number | string, demo?: boolean, keys?: string[], types?: string[], as?: string) => Promise<AxiosResponse<pond.Device>>
getPageData: (
id: number | string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<AxiosResponse<pond.GetDevicePageDataResponse>>;
getMulti: (ids: number[] | string[]) => Promise<AxiosResponse<pond.GetMultiDeviceResponse>>;
getGeoJson: (id: number | string, demo?: boolean) => Promise<any>;
getMultiGeoJson: (ids: number[] | string[]) => Promise<any>;
getMulti: (ids: number[] | string[], as?: string) => Promise<AxiosResponse<pond.GetMultiDeviceResponse>>;
getGeoJson: (id: number | string, demo?: boolean, as?: string) => Promise<any>;
getMultiGeoJson: (ids: number[] | string[], as?: string) => Promise<any>;
list: (
limit: number,
offset: number,
@ -40,6 +41,7 @@ export interface IDeviceAPIContext {
types?: string[],
fieldContains?: Map<string, string>,
prefixSearch?: string,
as?: string
) => Promise<AxiosResponse<pond.ListDevicesResponse>>;
listForUser: (
asRoot: boolean,
@ -62,19 +64,22 @@ export interface IDeviceAPIContext {
id: number | string,
preferences: pond.DevicePreferences,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<any>;
updateComponentPreferences: (
id: number | string,
component: string,
preferences: pond.DeviceComponentPreferences,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<AxiosResponse<pond.UpdateDeviceComponentPreferencesResponse>>;
listDeviceComponentPreferences: (
id: number | string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<AxiosResponse<pond.ListDeviceComponentPreferencesResponse>>;
updateStatus: (
id: number | string,
@ -94,7 +99,7 @@ export interface IDeviceAPIContext {
newCap: number
) => Promise<AxiosResponse<pond.BulkChangeDataCapsResponse>>;
resume: (id: number) => Promise<any>;
getUpgradeStatus: (id: number, keys?: string[], types?: string[]) => Promise<any>;
getUpgradeStatus: (id: number, keys?: string[], types?: string[], as?: string) => Promise<any>;
loadBackpack: (id: number, backpack: number) => Promise<any>;
setTags: (id: number, tags: string[]) => Promise<any>;
setWifi: (
@ -102,7 +107,8 @@ export interface IDeviceAPIContext {
gateway: string,
password: string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => Promise<any>;
statistics: (
keys?: string[],
@ -122,7 +128,8 @@ export interface IDeviceAPIContext {
limit: number,
offset: number,
order: string,
orderBy: string
orderBy: string,
as?: string
) => Promise<AxiosResponse<pond.ListDeviceExportedMeasurementsResponse>>;
listSimpleJSON: (device: number) => Promise<any>;
resetQuackCount: (id: number) => Promise<any>;
@ -150,9 +157,9 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
const { children } = props;
const { get, post, put, del } = useHTTP();
const permissionAPI = usePermissionAPI();
const [{ as, team }] = useGlobalState();
//const [{ as, team }] = useGlobalState();
const add = (name: string, description: string, backpack: pond.BackpackSettings) => {
const add = (name: string, description: string, backpack: pond.BackpackSettings, as?: string) => {
let url = pondURL("/devices")
if (as) url = pondURL("/devices?as=" + as)
return new Promise<AxiosResponse<pond.AddDeviceResponse>>((resolve, reject) => {
@ -169,7 +176,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
id: number | string,
demo: boolean = false,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -222,7 +230,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
const getDevicePageData = (
id: number | string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
): Promise<AxiosResponse<pond.GetDevicePageDataResponse>> => {
let url = pondURL(
"/devicePageData/" +
@ -250,7 +259,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const getDeviceGeoJSON = (id: number | string, demo: boolean = false) => {
const getDeviceGeoJSON = (id: number | string, demo: boolean = false, as?: string) => {
let url = pondURL("/devices/" + id + "/geojson", demo);
if (as) url = pondURL("/devices/" + id + "/geojson?as=" + as, demo)
return new Promise<AxiosResponse>((resolve, reject) => {
@ -262,7 +271,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const getMulti = (ids: number[] | string[]) => {
const getMulti = (ids: number[] | string[], as?: string) => {
let idString = "";
ids.forEach((id: number | string, i: number) => {
if (i === 0) {
@ -282,7 +291,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const getMultiDevicesGeoJSON = (ids: number[] | string[]) => {
const getMultiDevicesGeoJSON = (ids: number[] | string[], as?: string) => {
let idString = "";
ids.forEach((id: number | string, i: number) => {
if (i === 0) {
@ -317,6 +326,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
types?: string[],
fieldContains?: Map<string, string>,
prefixSearch?: string,
as?: string
) => {
const url = pondURL(
"/devices" +
@ -375,7 +385,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const remove = (id: number) => {
const remove = (id: number, as?: string) => {
const url = pondURL("/devices/" + id + (as ? "?as=" + as : ""))
return new Promise<AxiosResponse<pond.RemoveDeviceResponse>>((resolve, reject) => {
del<pond.RemoveDeviceResponse>(url).then(resp => {
@ -387,7 +397,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const update = (id: number, settings: pond.DeviceSettings) => {
const update = (id: number, settings: pond.DeviceSettings, as?: string) => {
const url = pondURL("/devices/" + id + "/update" + (as ? "?as=" + as : ""));
return new Promise<AxiosResponse<pond.UpdateDeviceResponse>>((resolve, reject) => {
put<pond.UpdateDeviceResponse>(url, settings).then(resp => {
@ -452,7 +462,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
id: number | string,
preferences: pond.DevicePreferences,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -477,7 +488,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
component: string,
preferences: pond.DeviceComponentPreferences,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -503,7 +515,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
const listDeviceComponentPreferences = (
id: number | string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -535,7 +548,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
id: number | string,
status: pond.DeviceStatus,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let url = pondURL(
"/devices/" +
@ -626,7 +640,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
})
};
const getUpgradeStatus = (id: number, keys?: string[], types?: string[]) => {
const getUpgradeStatus = (id: number, keys?: string[], types?: string[], as?: string) => {
let a = as ? "?as=" + as : "";
if (keys && keys.length > 0 && types && types.length > 0) {
a = as ? a + "&keys=" + keys + "&types=" + types : "?keys=" + keys + "&types=" + types;
@ -666,7 +680,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
gateway: string,
password: string,
keys?: string[],
types?: string[]
types?: string[],
as?: string
) => {
let a = as ? "?as=" + as : "";
if (keys && keys.length > 0 && types && types.length > 0) {
@ -764,7 +779,8 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
limit: number,
offset: number,
order: string,
orderBy: string
orderBy: string,
as?: string
) => {
let keyString = "";
components.forEach((comp, i) => {
@ -875,10 +891,12 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
MB: number,
source?: string,
keys?: string[],
types?: string[]
types?: string[],
as?: string,
team?: Team
) => {
let url = "/devices/" + id + "/buyData?MB=" + MB;
if (as === team.key()) url = url + "&team=" + team.key();
if (as === team?.key()) url = url + "&team=" + team?.key();
if (source) url = url + "&source=" + source;
if (keys) url = url + "&keys=" + keys;
if (types) url = url + "&types=" + types;
@ -936,7 +954,7 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
getDataUsage,
buyData,
updateComponentPreferences,
listDeviceComponentPreferences
listDeviceComponentPreferences//as
}}>
{children}
</DeviceAPIContext.Provider>

View file

@ -47,7 +47,7 @@ interface Props {
const REQUEST_LIMIT = 25;
export default function AccessObject(props: Props) {
const [{ user }, dispatch] = useGlobalState();
const [{ user, as }, dispatch] = useGlobalState();
// const history = useHistory();
const navigate = useNavigate()
const isMobile = useMobile();
@ -152,7 +152,14 @@ export default function AccessObject(props: Props) {
undefined,
undefined,
undefined,
true
true,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
as
)
.then((response: any) => {
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
@ -170,7 +177,7 @@ export default function AccessObject(props: Props) {
setDevicesTotal(0);
})
.finally(() => setLoading(false));
}, [deviceAPI, groupAPI, teamAPI, kind, searchValue]);
}, [deviceAPI, groupAPI, teamAPI, kind, searchValue, as]);
const loadMoreDevices = useCallback(() => {
deviceAPI
@ -183,7 +190,14 @@ export default function AccessObject(props: Props) {
undefined,
undefined,
undefined,
true
true,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
as
)
.then((response: any) => {
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
@ -200,7 +214,7 @@ export default function AccessObject(props: Props) {
setDevicesOffset(0);
setDevicesTotal(0);
});
}, [deviceAPI, devicesOffset, searchValue, devices]);
}, [deviceAPI, devicesOffset, searchValue, devices, as]);
const loadMoreGroups = useCallback(() => {
groupAPI

View file

@ -154,7 +154,7 @@ export default function ComponentCards(props: Props) {
t.push("device");
setLoading(true);
componentAPI
.list(deviceId, false, k, t, true)
.list(deviceId, false, k, t, true, as)
.then(resp => {
if (loading) return;
if (!resp.data.components) return;
@ -342,7 +342,7 @@ export default function ComponentCards(props: Props) {
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deviceId, componentAPI, showMobile, keys, types]);
}, [deviceId, componentAPI, showMobile, keys, types, as]);
return (
<Grid container direction="row" justifyContent="flex-start" spacing={2}>

View file

@ -126,13 +126,13 @@ export default function DeviceDrawer(props: Props) {
useEffect(() => {
setLoading(true);
deviceAPI.list(limit, offset).then(resp => {
deviceAPI.list(limit, offset, undefined, undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,undefined,as).then(resp => {
setDevices(resp.data.devices);
setTotal(resp.data.total);
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [limit, offset, deviceAPI]);
}, [limit, offset, deviceAPI, as]);
const back = () => {
if (offset - limit < 0) {