Merge branch 'staging_environment' into field_dashboard
This commit is contained in:
commit
1028f0639b
38 changed files with 992 additions and 231 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -42,7 +42,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#field_dashboard",
|
||||||
"query-string": "^9.2.1",
|
"query-string": "^9.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
@ -10911,7 +10911,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#0cdf4d5e3038ef13318da0841886a7c9f0926f8d",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#37e19bde4218b369e028606c009729112668ccc6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ function App() {
|
||||||
|
|
||||||
const skipCallbacks = [
|
const skipCallbacks = [
|
||||||
"/johndeere",
|
"/johndeere",
|
||||||
"/cnhi"
|
"/cnhi",
|
||||||
|
"/libracart"
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
BIN
src/assets/marketplaceImages/LibraCartGrey.png
Normal file
BIN
src/assets/marketplaceImages/LibraCartGrey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -24,7 +24,7 @@ import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
import ShareObject from "user/ShareObject";
|
import ShareObject from "user/ShareObject";
|
||||||
import { isOffline } from "utils/environment";
|
import { isOffline } from "utils/environment";
|
||||||
import ObjectTeams from "teams/ObjectTeams";
|
import ObjectTeams from "teams/ObjectTeams";
|
||||||
// import BinDuplication from "./BinDuplication";
|
import BinDuplication from "./BinDuplication";
|
||||||
import BinsIcon from "products/Bindapt/BinsIcon";
|
import BinsIcon from "products/Bindapt/BinsIcon";
|
||||||
import HelpIcon from "@mui/icons-material/Help";
|
import HelpIcon from "@mui/icons-material/Help";
|
||||||
import BinSensors from "./BinSensors";
|
import BinSensors from "./BinSensors";
|
||||||
|
|
@ -269,14 +269,14 @@ export default function BinActions(props: Props) {
|
||||||
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
refreshCallback={refreshCallback}
|
refreshCallback={refreshCallback}
|
||||||
/>
|
/>
|
||||||
{/* <BinDuplication
|
<BinDuplication
|
||||||
open={openState.duplication}
|
open={openState.duplication}
|
||||||
closeDialog={() => {
|
closeDialog={() => {
|
||||||
setOpenState({ ...openState, duplication: false });
|
setOpenState({ ...openState, duplication: false });
|
||||||
}}
|
}}
|
||||||
bin={bin}
|
bin={bin}
|
||||||
refreshCallback={refreshCallback}
|
refreshCallback={refreshCallback}
|
||||||
/> */}
|
/>
|
||||||
<RemoveSelfFromObject
|
<RemoveSelfFromObject
|
||||||
scope={binScope(key)}
|
scope={binScope(key)}
|
||||||
label={label}
|
label={label}
|
||||||
|
|
|
||||||
|
|
@ -506,9 +506,6 @@ export default function BinCard(props: Props) {
|
||||||
+
|
+
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
{/* <Box position="absolute" top={4} right={4}>
|
|
||||||
<BinModeDot mode={bin.settings.mode} />
|
|
||||||
</Box> */}
|
|
||||||
<Box padding={1}>
|
<Box padding={1}>
|
||||||
<Typography
|
<Typography
|
||||||
align="left"
|
align="left"
|
||||||
|
|
|
||||||
72
src/bin/BinDuplication.tsx
Normal file
72
src/bin/BinDuplication.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
TextField,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { Bin } from "models";
|
||||||
|
import { useBinAPI, useSnackbar } from "providers";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
bin: Bin;
|
||||||
|
closeDialog: () => void;
|
||||||
|
refreshCallback: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BinDuplication(props: Props) {
|
||||||
|
const { open, bin, closeDialog, refreshCallback } = props;
|
||||||
|
const [binDupName, setBinDupName] = useState("(copy of) " + bin.name());
|
||||||
|
const binAPI = useBinAPI();
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setBinDupName("(copy of) " + bin.name());
|
||||||
|
}, [bin]);
|
||||||
|
|
||||||
|
const duplicate = () => {
|
||||||
|
let settings = bin.settings;
|
||||||
|
settings.name = binDupName;
|
||||||
|
binAPI
|
||||||
|
.addBin(settings)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Successfully duplicated bin");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("Failed to duplicate bin");
|
||||||
|
});
|
||||||
|
refreshCallback();
|
||||||
|
closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={open} onClose={closeDialog}>
|
||||||
|
<DialogTitle>Create Duplicate Bin</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>
|
||||||
|
This will create a new bin with the same settings as {bin.name()}.
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
id={"duplicateName"}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
label="Name of Copy"
|
||||||
|
value={binDupName}
|
||||||
|
onChange={e => setBinDupName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={closeDialog} style={{ color: "red" }}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={duplicate} color="primary">
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ import { Bin } from "models";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { GetBinShapeDescribers } from "pbHelpers/Bin";
|
import { GetBinShapeDescribers } from "pbHelpers/Bin";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useBinAPI, useBinYardAPI, useGlobalState } from "providers";
|
import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
// import { useHistory } from "react-router";
|
// import { useHistory } from "react-router";
|
||||||
import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
|
import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
|
||||||
|
|
@ -176,7 +176,7 @@ export default function BinSettings(props: Props) {
|
||||||
const grainOptions = GrainOptions();
|
const grainOptions = GrainOptions();
|
||||||
const grainUseOptions = GetGrainUseOptions();
|
const grainUseOptions = GetGrainUseOptions();
|
||||||
const [inputCapacity, setInputCapacity] = useState<string>("");
|
const [inputCapacity, setInputCapacity] = useState<string>("");
|
||||||
const [{ as }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
const [grainDiff, setGrainDiff] = useState(0);
|
const [grainDiff, setGrainDiff] = useState(0);
|
||||||
const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false);
|
const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false);
|
||||||
const [grainUpdate, setGrainUpdate] = useState(false);
|
const [grainUpdate, setGrainUpdate] = useState(false);
|
||||||
|
|
@ -197,10 +197,13 @@ export default function BinSettings(props: Props) {
|
||||||
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
|
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
|
||||||
pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
|
pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
|
||||||
);
|
);
|
||||||
|
|
||||||
const [storageType, setStorageType] = useState<pond.BinStorage>(
|
const [storageType, setStorageType] = useState<pond.BinStorage>(
|
||||||
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
|
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
|
||||||
);
|
);
|
||||||
|
//libracart stuff
|
||||||
|
const libracartAPI = useLibraCartProxyAPI()
|
||||||
|
const [lcDestination, setlcDestination] = useState<Option | null>()
|
||||||
|
const [lcDestinationOptions, setlcDestinationOptions] = useState<Option[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
|
@ -365,9 +368,33 @@ export default function BinSettings(props: Props) {
|
||||||
}
|
}
|
||||||
}, [binYardAPI, form.yardKey, userID, as, props.binYards]);
|
}, [binYardAPI, form.yardKey, userID, as, props.binYards]);
|
||||||
|
|
||||||
|
const loadLibraCartDestinations = useCallback(()=>{
|
||||||
|
if(inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
|
||||||
|
//load the destinations for the user/team they are viewing as
|
||||||
|
let options: Option[] = []
|
||||||
|
libracartAPI.listDestinations(0,0,undefined, as)
|
||||||
|
.then(resp=>{
|
||||||
|
//set the options for the search select
|
||||||
|
resp.data.destinations.forEach(d => {
|
||||||
|
let newOp: Option = {
|
||||||
|
label: d.name,
|
||||||
|
value: d.id
|
||||||
|
}
|
||||||
|
options.push(newOp)
|
||||||
|
//set the current option based on the key in the form
|
||||||
|
if (d.id === form.libracartDestinationKey){
|
||||||
|
setlcDestination(newOp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setlcDestinationOptions(options)
|
||||||
|
}).catch(()=>{})
|
||||||
|
}
|
||||||
|
},[libracartAPI, inventoryControl, as, form.libracartDestinationKey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadBinYards();
|
loadBinYards();
|
||||||
}, [loadBinYards]);
|
loadLibraCartDestinations();
|
||||||
|
}, [loadBinYards, loadLibraCartDestinations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (bin?.settings.specs?.bushelCapacity) {
|
if (bin?.settings.specs?.bushelCapacity) {
|
||||||
|
|
@ -713,6 +740,8 @@ export default function BinSettings(props: Props) {
|
||||||
return "Auto (cable)"
|
return "Auto (cable)"
|
||||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
|
||||||
return "Hybrid (lidar)"
|
return "Hybrid (lidar)"
|
||||||
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
|
||||||
|
return "Auto (LibraCart)"
|
||||||
default:
|
default:
|
||||||
return "Manual"
|
return "Manual"
|
||||||
}
|
}
|
||||||
|
|
@ -860,7 +889,7 @@ export default function BinSettings(props: Props) {
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC}
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC}
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
label={"Auto"}
|
label={"Auto (Cable)"}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR}
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR}
|
||||||
|
|
@ -872,6 +901,13 @@ export default function BinSettings(props: Props) {
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
label={"Hybrid (Lidar)"}
|
label={"Hybrid (Lidar)"}
|
||||||
/>
|
/>
|
||||||
|
{user.hasFeature("libra-cart") &&
|
||||||
|
<FormControlLabel
|
||||||
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
|
||||||
|
control={<Radio />}
|
||||||
|
label={"Auto (LibraCart)"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
|
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
|
||||||
|
|
@ -912,6 +948,25 @@ export default function BinSettings(props: Props) {
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
|
||||||
|
<Box width="100%" padding={2}>
|
||||||
|
<SearchSelect
|
||||||
|
label="LibraCart Destination"
|
||||||
|
selected={lcDestination}
|
||||||
|
changeSelection={option => {
|
||||||
|
let newForm = form;
|
||||||
|
newForm.libracartDestinationKey = option?.value;
|
||||||
|
setForm(newForm);
|
||||||
|
setlcDestination(option ? option : null);
|
||||||
|
}}
|
||||||
|
disabled={!canEdit}
|
||||||
|
options={lcDestinationOptions}
|
||||||
|
/>
|
||||||
|
<Typography variant="caption">
|
||||||
|
The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
{empty ? (
|
{empty ? (
|
||||||
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center">
|
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center">
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ const useStyles = makeStyles((_theme) => {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
minHeight: "233px",
|
minHeight: "233px",
|
||||||
height: "auto !important",
|
height: "auto !important",
|
||||||
width: "184px",
|
|
||||||
padding: 2
|
padding: 2
|
||||||
},
|
},
|
||||||
hidden: {
|
hidden: {
|
||||||
|
|
@ -113,7 +112,7 @@ export default function BinsList(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Grid container direction="row">
|
<Grid container direction="row">
|
||||||
{bins.map((b, i) =>
|
{bins.map((b, i) =>
|
||||||
isMobile ? (
|
(
|
||||||
<Grid
|
<Grid
|
||||||
size={{
|
size={{
|
||||||
xs: 6,
|
xs: 6,
|
||||||
|
|
@ -133,21 +132,6 @@ export default function BinsList(props: Props) {
|
||||||
valDisplay={valDisplay}
|
valDisplay={valDisplay}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
style={{ width: "184px" }}
|
|
||||||
className={classes.gridListTile}
|
|
||||||
onClick={() => {
|
|
||||||
!duplicate && goToBin(i)
|
|
||||||
}}>
|
|
||||||
<BinCardV2
|
|
||||||
bin={b}
|
|
||||||
duplicateBin={duplicateBin}
|
|
||||||
dupHovered={setDuplicate}
|
|
||||||
valDisplay={valDisplay}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
67
src/bin/graphs/BinLevelAreaGraph.tsx
Normal file
67
src/bin/graphs/BinLevelAreaGraph.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { useTheme } from "@mui/material";
|
||||||
|
import MaterialChartTooltip from "charts/MaterialChartTooltip";
|
||||||
|
import moment from "moment";
|
||||||
|
import { Area, AreaChart, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
|
export interface LevelAreaData {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: LevelAreaData[]
|
||||||
|
fill: string
|
||||||
|
customHeight?: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BinLevelAreaGraph(props: Props) {
|
||||||
|
const { data, customHeight, fill } = props
|
||||||
|
const theme = useTheme();
|
||||||
|
const now = moment();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveContainer width={"100%"} height={customHeight ?? 350}>
|
||||||
|
<AreaChart data={data}>
|
||||||
|
<XAxis
|
||||||
|
allowDataOverflow
|
||||||
|
dataKey="timestamp"
|
||||||
|
domain={["dataMin", "dataMax"]}
|
||||||
|
name="Time"
|
||||||
|
tickFormatter={timestamp => {
|
||||||
|
let t = moment(timestamp);
|
||||||
|
return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
|
||||||
|
}}
|
||||||
|
scale="time"
|
||||||
|
type="number"
|
||||||
|
tick={{ fill: theme.palette.text.primary }}
|
||||||
|
stroke={theme.palette.divider}
|
||||||
|
interval="preserveStartEnd"
|
||||||
|
/>
|
||||||
|
<YAxis />
|
||||||
|
<Area
|
||||||
|
dataKey={"value"}
|
||||||
|
fill={fill}
|
||||||
|
stroke={fill}
|
||||||
|
strokeWidth={3}
|
||||||
|
type="monotone"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
animationEasing="ease-out"
|
||||||
|
cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
|
||||||
|
labelFormatter={timestamp => moment(timestamp).format("lll")}
|
||||||
|
content={(props: TooltipProps<any, any>) => {
|
||||||
|
return (
|
||||||
|
<MaterialChartTooltip
|
||||||
|
{...props}
|
||||||
|
valueFormatter={value => {
|
||||||
|
return value + " Bushels";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Card,
|
Card,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
FormControlLabel,
|
|
||||||
Grid,
|
|
||||||
Switch,
|
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { blue, grey, red, yellow, orange } from "@mui/material/colors";
|
import { blue, grey, red, yellow, orange } from "@mui/material/colors";
|
||||||
import BarGraph, { BarData, RefArea } from "charts/BarGraph";
|
import BarGraph, { BarData } from "charts/BarGraph";
|
||||||
import { Bin } from "models";
|
import { Bin } from "models";
|
||||||
import moment, { Moment } from "moment";
|
import moment, { Moment } from "moment";
|
||||||
import { pond, quack } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useBinAPI, useGlobalState } from "providers";
|
import { useBinAPI, useGlobalState } from "providers";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Legend } from "recharts";
|
import { Legend } from "recharts";
|
||||||
import { getGrainUnit } from "utils";
|
import { getGrainUnit } from "utils";
|
||||||
|
import BinLevelAreaGraph from "./BinLevelAreaGraph";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
binLoading: boolean;
|
binLoading: boolean;
|
||||||
|
|
@ -27,12 +24,16 @@ interface Props {
|
||||||
customHeight?: number | string;
|
customHeight?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InventoryAt {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default function BinLevelOverTime(props: Props) {
|
export default function BinLevelOverTime(props: Props) {
|
||||||
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
|
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
|
||||||
const binAPI = useBinAPI();
|
const binAPI = useBinAPI();
|
||||||
const [{as}] = useGlobalState();
|
const [{as}] = useGlobalState();
|
||||||
const [data, setData] = useState<BarData[]>([]);
|
const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]);
|
||||||
const [modeAreas, setModeAreas] = useState<RefArea[]>([]);
|
|
||||||
const [dataLoading, setDataLoading] = useState(false);
|
const [dataLoading, setDataLoading] = useState(false);
|
||||||
const [capacity, setCapacity] = useState<number | undefined>();
|
const [capacity, setCapacity] = useState<number | undefined>();
|
||||||
|
|
||||||
|
|
@ -76,33 +77,15 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
let data: BarData[] = [];
|
let data: BarData[] = [];
|
||||||
let modeAreas: RefArea[] = [];
|
|
||||||
let lastBushels = -1;
|
let lastBushels = -1;
|
||||||
|
let currentMode: pond.BinMode | undefined = undefined
|
||||||
//values for ref areas
|
let modeData: BarData[] = []
|
||||||
let x1 = 0;
|
|
||||||
let x2 = 0;
|
|
||||||
let y1 = -50;
|
|
||||||
let y2 = -200;
|
|
||||||
|
|
||||||
let currentMode: pond.BinMode;
|
|
||||||
|
|
||||||
resp.data.history.forEach(hist => {
|
resp.data.history.forEach(hist => {
|
||||||
|
//build the data for the inventory bar graph
|
||||||
if (hist.settings?.inventory) {
|
if (hist.settings?.inventory) {
|
||||||
let settings = pond.BinSettings.fromObject(hist.settings);
|
|
||||||
let bushels = hist.settings.inventory.grainBushels ?? 0;
|
let bushels = hist.settings.inventory.grainBushels ?? 0;
|
||||||
if (bushels !== lastBushels || currentMode !== settings.mode) {
|
if (bushels !== lastBushels) {
|
||||||
x1 = moment(hist.timestamp).valueOf();
|
let newData: InventoryAt = {
|
||||||
x2 = moment(hist.timestamp).valueOf();
|
|
||||||
modeAreas.push({
|
|
||||||
x1: x1,
|
|
||||||
x2: x2,
|
|
||||||
y1: cap ? cap * -0.1 : y1,
|
|
||||||
y2: cap ? cap * -0.2 : y2,
|
|
||||||
fill: getFill(settings.mode)
|
|
||||||
});
|
|
||||||
currentMode = settings.mode;
|
|
||||||
let newData: BarData = {
|
|
||||||
timestamp: moment(hist.timestamp).valueOf(),
|
timestamp: moment(hist.timestamp).valueOf(),
|
||||||
value: fertilizerBin
|
value: fertilizerBin
|
||||||
? Math.round(bushels * 35.239)
|
? Math.round(bushels * 35.239)
|
||||||
|
|
@ -114,10 +97,21 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
lastBushels = bushels;
|
lastBushels = bushels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//build the data for the mode change bar graph
|
||||||
|
let histBin = Bin.create()
|
||||||
|
histBin.settings = hist.settings ?? pond.BinSettings.create()
|
||||||
|
if(hist.settings && currentMode !== hist.settings.mode){
|
||||||
|
currentMode = hist.settings.mode
|
||||||
|
modeData.push({
|
||||||
|
timestamp: moment(hist.timestamp).valueOf(),
|
||||||
|
value: 1,
|
||||||
|
fill: getFill(currentMode)
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
let currentTime = moment().valueOf();
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
let bushels = bin.settings.inventory?.grainBushels ?? 0;
|
let bushels = bin.bushels();
|
||||||
let currentTime = moment().valueOf();
|
|
||||||
data.push({
|
data.push({
|
||||||
value: fertilizerBin
|
value: fertilizerBin
|
||||||
? Math.round(bushels * 35.239)
|
? Math.round(bushels * 35.239)
|
||||||
|
|
@ -126,17 +120,16 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
: bushels,
|
: bushels,
|
||||||
timestamp: currentTime
|
timestamp: currentTime
|
||||||
});
|
});
|
||||||
|
|
||||||
modeAreas.push({
|
|
||||||
x1: currentTime,
|
|
||||||
x2: currentTime,
|
|
||||||
y1: cap ? cap * -0.1 : y1,
|
|
||||||
y2: cap ? cap * -0.2 : y2,
|
|
||||||
fill: getFill(bin.settings.mode)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setData(data);
|
if(modeData.length === 0){
|
||||||
setModeAreas(modeAreas);
|
modeData.push({
|
||||||
|
timestamp: currentTime,
|
||||||
|
value: 1,
|
||||||
|
fill: getFill(bin.settings.mode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setInventoryData(data);
|
||||||
|
setModeData(modeData)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setDataLoading(false);
|
setDataLoading(false);
|
||||||
|
|
@ -180,7 +173,7 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (autoBarData.length === 0) {
|
if (autoBarData.length === 0) {
|
||||||
let bushels = bin.settings.inventory?.grainBushels ?? 0;
|
let bushels = bin.bushels();
|
||||||
let currentTime = moment().valueOf();
|
let currentTime = moment().valueOf();
|
||||||
autoBarData.push({
|
autoBarData.push({
|
||||||
value: fertilizerBin
|
value: fertilizerBin
|
||||||
|
|
@ -191,7 +184,7 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
timestamp: currentTime
|
timestamp: currentTime
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setData(autoBarData);
|
setInventoryData(autoBarData);
|
||||||
})
|
})
|
||||||
.catch(err => {})
|
.catch(err => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|
@ -220,6 +213,13 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if(modeData.length === 0){
|
||||||
|
modeData.push({
|
||||||
|
timestamp: moment().valueOf(),
|
||||||
|
value: 1,
|
||||||
|
fill: getFill(bin.settings.mode)
|
||||||
|
})
|
||||||
|
}
|
||||||
setModeData(modeData)
|
setModeData(modeData)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
@ -238,7 +238,8 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
let control = bin.inventoryControl()
|
let control = bin.inventoryControl()
|
||||||
//for automatic lidar and cables get the data from measurements as an object measurement
|
//for automatic lidar and cables get the data from measurements as an object measurement
|
||||||
if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
|
||||||
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART
|
||||||
){
|
){
|
||||||
//load the measurement data for the bar graph
|
//load the measurement data for the bar graph
|
||||||
loadMeasurementData()
|
loadMeasurementData()
|
||||||
|
|
@ -270,23 +271,30 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const inventoryChart = () => {
|
const inventoryChart = () => {
|
||||||
return (
|
if(inventoryData.length > 10){
|
||||||
<BarGraph
|
return (
|
||||||
|
<BinLevelAreaGraph
|
||||||
|
data={inventoryData}
|
||||||
|
fill={colour}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
return (
|
||||||
|
<BarGraph
|
||||||
customHeight={customHeight}
|
customHeight={customHeight}
|
||||||
data={data}
|
data={inventoryData}
|
||||||
refAreas={modeAreas}
|
|
||||||
barColour={colour}
|
barColour={colour}
|
||||||
yMax={capacity}
|
yMax={capacity}
|
||||||
yMin={0}
|
yMin={0}
|
||||||
graphLegend={modeAreas.length > 0 ? legend() : undefined}
|
|
||||||
labelColour="white"
|
labelColour="white"
|
||||||
labels
|
labels
|
||||||
useGradient
|
useGradient
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoBinModeChart = () => {
|
const binModeChart = () => {
|
||||||
return (
|
return (
|
||||||
<BarGraph
|
<BarGraph
|
||||||
data={modeData}
|
data={modeData}
|
||||||
|
|
@ -305,9 +313,7 @@ export default function BinLevelOverTime(props: Props) {
|
||||||
Grain Levels Over Time
|
Grain Levels Over Time
|
||||||
</Typography>
|
</Typography>
|
||||||
{dataLoading ? <CircularProgress /> : inventoryChart()}
|
{dataLoading ? <CircularProgress /> : inventoryChart()}
|
||||||
{/* when using auto will need to make a new chart just for the bin modes since the auto inventory comes from another place */}
|
{binModeChart()}
|
||||||
{(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
|
||||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) && autoBinModeChart()}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,13 @@ export default function PeriodSelect(props: Props) {
|
||||||
}
|
}
|
||||||
}, [unit, prevUnit, onChange, value]);
|
}, [unit, prevUnit, onChange, value]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let initialUnit: TimeUnit = bestUnit(initialMs);
|
||||||
|
let initialValue = milliToX(initialMs, initialUnit).toString();
|
||||||
|
setValue(initialValue)
|
||||||
|
setUnit(initialUnit)
|
||||||
|
},[initialMs])
|
||||||
|
|
||||||
const changeValue = (event: any) => {
|
const changeValue = (event: any) => {
|
||||||
let value = event.target.value;
|
let value = event.target.value;
|
||||||
setValue(value);
|
setValue(value);
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ export default function ComponentSettings(props: Props) {
|
||||||
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
|
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
|
||||||
const [formComponent, setFormComponent] = useState<Component>(new Component());
|
const [formComponent, setFormComponent] = useState<Component>(new Component());
|
||||||
const [cableID, setCableID] = useState(0);
|
const [cableID, setCableID] = useState(0);
|
||||||
|
const [expansionLine, setExpansionLine] = useState(0);
|
||||||
const [overlayIndex, setOverlayIndex] = useState(0);
|
const [overlayIndex, setOverlayIndex] = useState(0);
|
||||||
const [overlayColourDialog, setOverlayCoulourDialog] = useState(false);
|
const [overlayColourDialog, setOverlayCoulourDialog] = useState(false);
|
||||||
const [tabVal, setTabVal] = useState(0);
|
const [tabVal, setTabVal] = useState(0);
|
||||||
|
|
@ -223,6 +224,9 @@ export default function ComponentSettings(props: Props) {
|
||||||
const isFormValid = () => {
|
const isFormValid = () => {
|
||||||
//let { component } = form;
|
//let { component } = form;
|
||||||
let type = or(formComponent.settings.type, "");
|
let type = or(formComponent.settings.type, "");
|
||||||
|
if(supportsExpansion() && expansionLine === 0){
|
||||||
|
return false
|
||||||
|
}
|
||||||
return isComponentTypeValid(type) && isAddressValid(formComponent);
|
return isComponentTypeValid(type) && isAddressValid(formComponent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -306,6 +310,7 @@ export default function ComponentSettings(props: Props) {
|
||||||
const component = formComponent;
|
const component = formComponent;
|
||||||
//component.settings.calibrationCoefficient = Number(form.coefficient);
|
//component.settings.calibrationCoefficient = Number(form.coefficient);
|
||||||
if (cableID > 0) component.settings.addressType = cableID + 8;
|
if (cableID > 0) component.settings.addressType = cableID + 8;
|
||||||
|
component.settings.expansionLine = expansionLine
|
||||||
componentAPI
|
componentAPI
|
||||||
.add(device.id(), component.settings, as)
|
.add(device.id(), component.settings, as)
|
||||||
.then((_response: any) => {
|
.then((_response: any) => {
|
||||||
|
|
@ -373,10 +378,10 @@ export default function ComponentSettings(props: Props) {
|
||||||
let ext = extension(type, subtype);
|
let ext = extension(type, subtype);
|
||||||
formComponent.settings.type = type;
|
formComponent.settings.type = type;
|
||||||
formComponent.settings.subtype = subtype;
|
formComponent.settings.subtype = subtype;
|
||||||
formComponent.settings.addressType = getAddressTypes(
|
formComponent.settings.addressType = or(addressTypeRestriction, getAddressTypes(
|
||||||
formComponent.settings.type,
|
formComponent.settings.type,
|
||||||
formComponent.settings.subtype
|
formComponent.settings.subtype
|
||||||
)[0];
|
)[0]);
|
||||||
formComponent.settings.address = or(
|
formComponent.settings.address = or(
|
||||||
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0],
|
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0],
|
||||||
Component.create().settings.address
|
Component.create().settings.address
|
||||||
|
|
@ -399,11 +404,19 @@ export default function ComponentSettings(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePositionChanged = (event: any) => {
|
const handlePositionChanged = (event: any) => {
|
||||||
|
setCableID(0)
|
||||||
|
setExpansionLine(0)
|
||||||
let f = cloneDeep(formComponent);
|
let f = cloneDeep(formComponent);
|
||||||
f.settings.address = event.target.value
|
//split the string
|
||||||
.toString()
|
let split: string[] = event.target.value.toString().split(":")
|
||||||
.split(":")
|
// pop the addres off of the end of the array
|
||||||
.pop();
|
f.settings.address = parseInt(split.pop() ?? "0")
|
||||||
|
// pop the address type out of the array next
|
||||||
|
f.settings.addressType = parseInt(split.pop() ?? "0")
|
||||||
|
// f.settings.address = event.target.value
|
||||||
|
// .toString()
|
||||||
|
// .split(":")
|
||||||
|
// .pop();
|
||||||
setFormComponent(f);
|
setFormComponent(f);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -490,6 +503,21 @@ export default function ComponentSettings(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCableComponent = () => {
|
||||||
|
return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE ||
|
||||||
|
formComponent.settings.type ===
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE ||
|
||||||
|
formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE ||
|
||||||
|
(formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE &&
|
||||||
|
formComponent.subType() ===
|
||||||
|
quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG))
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsExpansion = () => {
|
||||||
|
return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE &&
|
||||||
|
formComponent.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C)
|
||||||
|
}
|
||||||
|
|
||||||
const addForm = () => {
|
const addForm = () => {
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|
@ -543,14 +571,8 @@ export default function ComponentSettings(props: Props) {
|
||||||
formComponent.settings.subtype
|
formComponent.settings.subtype
|
||||||
)}
|
)}
|
||||||
</TextField>
|
</TextField>
|
||||||
{(formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE ||
|
{supportsExpansion() && setExpLine()}
|
||||||
formComponent.settings.type ===
|
{(isCableComponent() && !supportsExpansion())&& setComponentAddrType()}
|
||||||
quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE ||
|
|
||||||
formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE ||
|
|
||||||
(formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE &&
|
|
||||||
formComponent.subType() ===
|
|
||||||
quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG)) &&
|
|
||||||
setComponentAddrType()}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : activeStep === 1 ? (
|
) : activeStep === 1 ? (
|
||||||
<ComponentForm
|
<ComponentForm
|
||||||
|
|
@ -582,6 +604,28 @@ export default function ComponentSettings(props: Props) {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setExpLine = () => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
label="Expansion Line"
|
||||||
|
type="number"
|
||||||
|
margin="normal"
|
||||||
|
error={expansionLine < 1 || expansionLine > 12}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={expansionLine}
|
||||||
|
helperText="Enter line the cable is connected to on your expander"
|
||||||
|
onChange={e => {
|
||||||
|
let number = parseInt(e.target.value);
|
||||||
|
if (number < 0 || isNaN(number)) number = 0;
|
||||||
|
if (number > 12) number = 12;
|
||||||
|
setExpansionLine(number);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const setComponentAddrType = () => {
|
const setComponentAddrType = () => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
||||||
|
|
@ -198,16 +198,23 @@ export default function DeviceWizard(props: Props) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function that restricts the pin availabilty in the availability map to the pin of the port that was selected
|
* function that restricts the pin availabilty in the availability map to the pin of the port that was selected
|
||||||
* does nothing for I2C ports as the restrictions for that are handled by the component type
|
|
||||||
* @param port port that was selected
|
* @param port port that was selected
|
||||||
* @param availability availability map of the device
|
* @param availability availability map of the device
|
||||||
* @returns modified availability map
|
* @returns modified availability map
|
||||||
*/
|
*/
|
||||||
const adjustAvailablePositions = (port: PortInformation, availability: DeviceAvailabilityMap) => {
|
const adjustAvailablePositions = (port: PortInformation, availability: DeviceAvailabilityMap) => {
|
||||||
let currentAvailability = availability;
|
let currentAvailability = availability;
|
||||||
|
console.log(port)
|
||||||
switch (port.addressType) {
|
switch (port.addressType) {
|
||||||
case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY:
|
case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY:
|
||||||
currentAvailability.set(port.addressType, [{ address: port.address, label: port.label }]);
|
currentAvailability.set(port.addressType, [{ address: port.address, label: port.label }]);
|
||||||
|
//since we now have a component type that can be pins or i2c need to remove i2c options if they selected a pin port
|
||||||
|
currentAvailability.set(quack.AddressType.ADDRESS_TYPE_I2C, new Map());
|
||||||
|
break;
|
||||||
|
case quack.AddressType.ADDRESS_TYPE_I2C:
|
||||||
|
//since we now have a component type that can be pins or i2c need to remove pin options if they selected the i2c port
|
||||||
|
currentAvailability.set(quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, []);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return currentAvailability;
|
return currentAvailability;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,20 @@ interface Props {
|
||||||
availableOffsets: OffsetAvailabilityMap
|
availableOffsets: OffsetAvailabilityMap
|
||||||
refreshCallback: () => void
|
refreshCallback: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompStep {
|
interface CompStep {
|
||||||
label: string;
|
label: string;
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let i2cBlacklistAddresses: number[] = [0x70]
|
||||||
|
|
||||||
export default function DeviceScannedComponents(props: Props){
|
export default function DeviceScannedComponents(props: Props){
|
||||||
const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props
|
const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props
|
||||||
const compAPI = useComponentAPI();
|
const compAPI = useComponentAPI();
|
||||||
const deviceAPI = useDeviceAPI()
|
const deviceAPI = useDeviceAPI()
|
||||||
const [scannedI2C, setScannedI2C] = useState<pond.DetectI2C>()
|
const [scannedI2C, setScannedI2C] = useState<pond.DetectI2C>() //the unmodified scan of addresses
|
||||||
|
const [validCompAddresses, setValidComponentAddresses] = useState<quack.AddressData[]>([]) //the filtered array of address data
|
||||||
const [components, setComponents] = useState<Component[]>([])
|
const [components, setComponents] = useState<Component[]>([])
|
||||||
const [steps, setSteps] = useState<CompStep[]>([]);
|
const [steps, setSteps] = useState<CompStep[]>([]);
|
||||||
const { error, success } = useSnackbar();
|
const { error, success } = useSnackbar();
|
||||||
|
|
@ -59,6 +62,19 @@ export default function DeviceScannedComponents(props: Props){
|
||||||
setSteps(steps)
|
setSteps(steps)
|
||||||
},[components])
|
},[components])
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let valid: quack.AddressData[] = []
|
||||||
|
//filter the address data
|
||||||
|
scannedI2C?.settings?.foundAddresses.forEach(addrData => {
|
||||||
|
if(!i2cBlacklistAddresses.includes(addrData.address)){
|
||||||
|
if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){
|
||||||
|
valid.push(addrData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setValidComponentAddresses(valid)
|
||||||
|
},[scannedI2C])
|
||||||
|
|
||||||
const stepper = () => {
|
const stepper = () => {
|
||||||
return (
|
return (
|
||||||
<Stepper nonLinear activeStep={currentStep} style={{ width: "100%" }}>
|
<Stepper nonLinear activeStep={currentStep} style={{ width: "100%" }}>
|
||||||
|
|
@ -205,9 +221,9 @@ export default function DeviceScannedComponents(props: Props){
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key)}}>Clear Scan</Button>
|
<Button variant="contained" color="primary" onClick={()=>{removeScan(scannedI2C.key)}}>Clear Scan</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{scannedI2C.settings?.foundAddresses && scannedI2C.settings.foundAddresses.length > 0 ? scannedI2C?.settings?.foundAddresses.map((addr, index) => {
|
{validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => {
|
||||||
return (
|
return (
|
||||||
<ScannedI2C key={index} sensorNum={index+1} decimalAddress={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? []}/>
|
<ScannedI2C key={index} sensorNum={index+1} addressData={addr} deviceProduct={device.settings.product} componentSelectionCallback={componentSelection} availablePositions={availablePositions.get(quack.AddressType.ADDRESS_TYPE_I2C) ?? []}/>
|
||||||
)
|
)
|
||||||
}) :
|
}) :
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { quack } from "protobuf-ts/quack";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
decimalAddress: number
|
addressData: quack.AddressData
|
||||||
deviceProduct: pond.DeviceProduct
|
deviceProduct: pond.DeviceProduct
|
||||||
componentSelectionCallback: (component: Component[], checked: boolean) => void
|
componentSelectionCallback: (component: Component[], checked: boolean) => void
|
||||||
availablePositions: DevicePositions
|
availablePositions: DevicePositions
|
||||||
|
|
@ -16,7 +16,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScannedI2C(props: Props){
|
export default function ScannedI2C(props: Props){
|
||||||
const {decimalAddress, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props
|
const {addressData, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props
|
||||||
const [types, setTypes] = useState<quack.ComponentType[]>([])
|
const [types, setTypes] = useState<quack.ComponentType[]>([])
|
||||||
const [subtypeOptions, setSubtypeOptions] = useState<Array<Subtype>>([])
|
const [subtypeOptions, setSubtypeOptions] = useState<Array<Subtype>>([])
|
||||||
const [selectedPrimaryType, setSelectedPrimaryType] = useState<quack.ComponentType>(quack.ComponentType.COMPONENT_TYPE_INVALID)
|
const [selectedPrimaryType, setSelectedPrimaryType] = useState<quack.ComponentType>(quack.ComponentType.COMPONENT_TYPE_INVALID)
|
||||||
|
|
@ -34,7 +34,7 @@ export default function ScannedI2C(props: Props){
|
||||||
if(i2cMap){
|
if(i2cMap){
|
||||||
i2cMap.forEach((val, key) => {
|
i2cMap.forEach((val, key) => {
|
||||||
let addresses = val as number[] ?? []
|
let addresses = val as number[] ?? []
|
||||||
if(addresses.length > 0 && addresses.includes(decimalAddress)){
|
if(addresses.length > 0 && addresses.includes(addressData.address)){
|
||||||
types.push(key)
|
types.push(key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -47,7 +47,7 @@ export default function ScannedI2C(props: Props){
|
||||||
checkAddressAvailable(types[0])
|
checkAddressAvailable(types[0])
|
||||||
}
|
}
|
||||||
setTypes(types)
|
setTypes(types)
|
||||||
},[decimalAddress, deviceProduct])
|
},[addressData, deviceProduct])
|
||||||
|
|
||||||
const buildSubtypeOptions = (compType: quack.ComponentType) => {
|
const buildSubtypeOptions = (compType: quack.ComponentType) => {
|
||||||
let subtypes = getSubtypes(compType)
|
let subtypes = getSubtypes(compType)
|
||||||
|
|
@ -69,7 +69,7 @@ export default function ScannedI2C(props: Props){
|
||||||
const checkAddressAvailable = (compType: quack.ComponentType) => {
|
const checkAddressAvailable = (compType: quack.ComponentType) => {
|
||||||
let i2cMap = availablePositions as ComponentAvailabilityMap
|
let i2cMap = availablePositions as ComponentAvailabilityMap
|
||||||
let compAddresses = i2cMap.get(compType) ?? []
|
let compAddresses = i2cMap.get(compType) ?? []
|
||||||
if (!compAddresses.includes(decimalAddress)){
|
if (!compAddresses.includes(addressData.address)){
|
||||||
setAddressInUse(true)
|
setAddressInUse(true)
|
||||||
}else{
|
}else{
|
||||||
setAddressInUse(false)
|
setAddressInUse(false)
|
||||||
|
|
@ -86,7 +86,10 @@ export default function ScannedI2C(props: Props){
|
||||||
primaryComponent.settings.type = selectedPrimaryType
|
primaryComponent.settings.type = selectedPrimaryType
|
||||||
primaryComponent.settings.subtype = selectedPrimarySubtype
|
primaryComponent.settings.subtype = selectedPrimarySubtype
|
||||||
primaryComponent.settings.addressType = quack.AddressType.ADDRESS_TYPE_I2C
|
primaryComponent.settings.addressType = quack.AddressType.ADDRESS_TYPE_I2C
|
||||||
primaryComponent.settings.address = decimalAddress
|
primaryComponent.settings.address = addressData.address
|
||||||
|
primaryComponent.settings.expansionLine = addressData.expansionLine
|
||||||
|
primaryComponent.settings.muxLine = addressData.muxLine
|
||||||
|
|
||||||
primaryComponent.settings.name = getFriendlyName(selectedPrimaryType, selectedPrimarySubtype)
|
primaryComponent.settings.name = getFriendlyName(selectedPrimaryType, selectedPrimarySubtype)
|
||||||
toAdd.push(primaryComponent)
|
toAdd.push(primaryComponent)
|
||||||
|
|
||||||
|
|
@ -161,9 +164,6 @@ export default function ScannedI2C(props: Props){
|
||||||
|
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
<Typography>Sensor Not Supported By Product</Typography>
|
<Typography>Sensor Not Supported By Product</Typography>
|
||||||
|
|
|
||||||
220
src/integrations/LibraCart/LibraCartAccess.tsx
Normal file
220
src/integrations/LibraCart/LibraCartAccess.tsx
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
import { Help } from "@mui/icons-material";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Grid2 as Grid,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
Tooltip,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { teamScope, User } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useSnackbar, useUserAPI } from "providers";
|
||||||
|
import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import TeamSearch from "teams/TeamSearch";
|
||||||
|
|
||||||
|
export default function LibraCartAccess() {
|
||||||
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
|
const [teamKey, setTeamKey] = useState("");
|
||||||
|
const [teamUsers, setTeamUsers] = useState<User[]>([]);
|
||||||
|
const [primaryUser, setPrimaryUser] = useState("");
|
||||||
|
const [libracartUsername, setLibraCartUserName] = useState("");
|
||||||
|
const [libracartCode, setLibraCartCode] = useState<string | null>("");
|
||||||
|
const [libracartAuth, setLibraCartAuth] = useState<string | null>("");
|
||||||
|
//const [activeStep, setActiveStep] = useState<number>(0);
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const libracartAPI = useLibraCartProxyAPI();
|
||||||
|
//const [dataOps, setDataOps] = useState<pond.DataOption[]>([]);
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
|
||||||
|
const submitNewOrganization = () => {
|
||||||
|
if (libracartCode && libracartAuth) {
|
||||||
|
libracartAPI
|
||||||
|
.addAccount(teamKey, primaryUser, libracartCode, libracartAuth, libracartUsername)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Added New Libra Cart Account Link");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("Failed to Add New Libra Cart Account Link");
|
||||||
|
});
|
||||||
|
setOpenDialog(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (teamKey !== "") {
|
||||||
|
userAPI.listObjectUsers(teamScope(teamKey)).then(resp => {
|
||||||
|
setTeamUsers(resp.data.users.map((u: pond.User) => User.any(u)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [teamKey, userAPI]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// let code = localStorage.getItem("state");
|
||||||
|
// if (code) {
|
||||||
|
// setLibraCartCode(code);
|
||||||
|
// setOpenDialog(true);
|
||||||
|
// }
|
||||||
|
// }, [searchParams, libracartCode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let code = new URLSearchParams(window.location.search).get("state"); // this is the account_code
|
||||||
|
let auth = new URLSearchParams(window.location.search).get("authorization_token"); // this is the authorization used to verify where it came from
|
||||||
|
|
||||||
|
if (code && auth) {
|
||||||
|
setLibraCartCode(code);
|
||||||
|
setLibraCartAuth(auth);
|
||||||
|
setOpenDialog(true);
|
||||||
|
}
|
||||||
|
}, [window.location]);
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
let invalid = false;
|
||||||
|
if (libracartUsername === "" || teamKey === "" || primaryUser === "" || libracartCode === "") {
|
||||||
|
invalid = true;
|
||||||
|
}
|
||||||
|
return invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const general = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
label="Libra Cart Email"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={libracartUsername}
|
||||||
|
onChange={e => setLibraCartUserName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TeamSearch label="Team" setTeamCallback={setTeamKey} />
|
||||||
|
<TextField
|
||||||
|
disabled={teamKey === ""}
|
||||||
|
margin="dense"
|
||||||
|
label="Primary User"
|
||||||
|
select
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={primaryUser}
|
||||||
|
onChange={e => setPrimaryUser(e.target.value)}>
|
||||||
|
{teamUsers.map(u => (
|
||||||
|
<MenuItem key={u.id()} value={u.id()}>
|
||||||
|
<Grid container direction="row" alignItems="center">
|
||||||
|
<Grid>
|
||||||
|
<Avatar
|
||||||
|
alt={u.name()}
|
||||||
|
src={
|
||||||
|
u.settings.avatar && u.settings.avatar !== "" ? u.settings.avatar : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Box marginLeft={2}>{u.name()}</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
label="code"
|
||||||
|
disabled
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={libracartCode}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
label="authorization token"
|
||||||
|
disabled
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={libracartAuth}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const newOrgDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={openDialog}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenDialog(false);
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Enter New Integration Link</DialogTitle>
|
||||||
|
<DialogContent>{general()}</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpenDialog(false);
|
||||||
|
}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={validate()}
|
||||||
|
onClick={submitNewOrganization}
|
||||||
|
variant="contained"
|
||||||
|
color="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ padding: 10 }}>
|
||||||
|
<Grid container direction="row" alignContent="center" alignItems="center" wrap="nowrap" width="100%" justifyContent="space-between">
|
||||||
|
<Grid container direction="row" alignContent="center" alignItems="center" wrap="nowrap">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.open(`https://staging.cloud.agrimatics.com/grain/integrations`, "_blank");
|
||||||
|
}}>
|
||||||
|
Link Libra Cart Account
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Tooltip
|
||||||
|
title='The integration can be found by selecting my account in the corner,
|
||||||
|
selecting the "Manage Account" option and then selecting integrations in the left side menu'>
|
||||||
|
<Help />
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Tooltip title={"This will Sync the data for all LibraCart accounts linked to " + (as ? "this team" : "your account")}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={e => {
|
||||||
|
libracartAPI.syncData().then(resp => {}).catch(err => {})
|
||||||
|
}}>
|
||||||
|
Sync
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Box paddingTop={2}>
|
||||||
|
<Typography>
|
||||||
|
LibraCart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{newOrgDialog()}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -126,8 +126,10 @@ export class Bin {
|
||||||
}
|
}
|
||||||
|
|
||||||
public bushels(): number {
|
public bushels(): number {
|
||||||
|
let control = this.settings.inventory?.inventoryControl;
|
||||||
let bushels = this.settings.inventory?.grainBushels || 0
|
let bushels = this.settings.inventory?.grainBushels || 0
|
||||||
if (this.settings.inventory?.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
|
if (control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||||
|
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
|
||||||
bushels = this.status.grainBushels
|
bushels = this.status.grainBushels
|
||||||
}
|
}
|
||||||
return bushels
|
return bushels
|
||||||
|
|
@ -137,7 +139,8 @@ export class Bin {
|
||||||
let fill = 0;
|
let fill = 0;
|
||||||
if (this.settings.inventory && this.settings.specs) {
|
if (this.settings.inventory && this.settings.specs) {
|
||||||
let bushels = this.settings.inventory.grainBushels
|
let bushels = this.settings.inventory.grainBushels
|
||||||
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
|
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||||
|
this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
|
||||||
bushels = this.status.grainBushels
|
bushels = this.status.grainBushels
|
||||||
}
|
}
|
||||||
fill = Math.round(
|
fill = Math.round(
|
||||||
|
|
|
||||||
|
|
@ -67,18 +67,25 @@ export class Component {
|
||||||
return quack.ComponentID.fromObject({
|
return quack.ComponentID.fromObject({
|
||||||
type: this.settings.type,
|
type: this.settings.type,
|
||||||
addressType: this.settings.addressType,
|
addressType: this.settings.addressType,
|
||||||
address: this.settings.address
|
address: this.settings.address,
|
||||||
|
expansionLine: this.settings.expansionLine,
|
||||||
|
muxLine: this.settings.muxLine
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public locationString(): string {
|
public locationString(): string {
|
||||||
return (
|
let compositeLocation = or(this.settings.type, 0).toString() +
|
||||||
or(this.settings.type, 0).toString() +
|
|
||||||
"-" +
|
"-" +
|
||||||
or(this.settings.addressType, 0).toString() +
|
or(this.settings.addressType, 0).toString() +
|
||||||
"-" +
|
"-" +
|
||||||
or(this.settings.address, 0).toString()
|
or(this.settings.address, 0).toString()
|
||||||
);
|
if(this.settings.expansionLine){
|
||||||
|
compositeLocation = compositeLocation + "-" + this.settings.expansionLine
|
||||||
|
}
|
||||||
|
if(this.settings.muxLine){
|
||||||
|
compositeLocation = compositeLocation + ":" + this.settings.muxLine
|
||||||
|
}
|
||||||
|
return compositeLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public type(): quack.ComponentType {
|
public type(): quack.ComponentType {
|
||||||
|
|
@ -107,7 +114,7 @@ export class Component {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addressDescription = (deviceProduct?: pond.DeviceProduct) => {
|
public addressDescription(deviceProduct?: pond.DeviceProduct): string {
|
||||||
if (
|
if (
|
||||||
this.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY ||
|
this.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY ||
|
||||||
(this.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 &&
|
(this.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 &&
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
|
||||||
v2Cell: "N/A",
|
v2Cell: "N/A",
|
||||||
v2WifiS3: "N/A",
|
v2WifiS3: "N/A",
|
||||||
v2CellS3: "N/A",
|
v2CellS3: "N/A",
|
||||||
v2CellBlack: "N/A",
|
v2CellBlack: "2.1.6",
|
||||||
v2CellGreen: "N/A",
|
v2CellGreen: "N/A",
|
||||||
v2WifiBlue: "N/A",
|
v2WifiBlue: "2.1.6",
|
||||||
v2CellBlue: "N/A",
|
v2CellBlue: "2.1.6",
|
||||||
v2EthBlue: "N/A"
|
v2EthBlue: "2.1.6"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ const Transactions = lazy(() => import("pages/Transactions"));
|
||||||
const BinCableEstimator = lazy(() => import("pages/BinCableEstimator"));
|
const BinCableEstimator = lazy(() => import("pages/BinCableEstimator"));
|
||||||
const APIDocs = lazy(() => import("pages/APIDocs"));
|
const APIDocs = lazy(() => import("pages/APIDocs"));
|
||||||
const Fields = lazy(()=> import("pages/Fields"));
|
const Fields = lazy(()=> import("pages/Fields"));
|
||||||
const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
const Marketplace = lazy(() => import("pages/Marketplace"))
|
||||||
const Logs = lazy(() => import("pages/Logs"))
|
const Logs = lazy(() => import("pages/Logs"))
|
||||||
const Firmware = lazy(() => import("pages/Firmware"));
|
const Firmware = lazy(() => import("pages/Firmware"));
|
||||||
const Nfc = lazy(() => import("pages/Nfc"));
|
const Nfc = lazy(() => import("pages/Nfc"));
|
||||||
|
|
@ -46,6 +46,7 @@ const Contracts = lazy(() => import("pages/Contracts"));
|
||||||
const Contract = lazy(() => import("pages/Contract"));
|
const Contract = lazy(() => import("pages/Contract"));
|
||||||
const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
||||||
const CNHi = lazy(() => import("pages/CNHi"));
|
const CNHi = lazy(() => import("pages/CNHi"));
|
||||||
|
const LibraCart = lazy(() => import("pages/LibraCart"));
|
||||||
|
|
||||||
export const appendToUrl = (appendage: number | string) => {
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
|
@ -340,6 +341,9 @@ export default function Router() {
|
||||||
{user.hasFeature("cnhi") &&
|
{user.hasFeature("cnhi") &&
|
||||||
<Route path="cnhi" element={<CNHi />} />
|
<Route path="cnhi" element={<CNHi />} />
|
||||||
}
|
}
|
||||||
|
{user.hasFeature("libra-cart") &&
|
||||||
|
<Route path="libracart" element={<LibraCart />} />
|
||||||
|
}
|
||||||
{/* Map routes */}
|
{/* Map routes */}
|
||||||
<Route path="visualFarm" element={<FieldMap />} />
|
<Route path="visualFarm" element={<FieldMap />} />
|
||||||
<Route path="aviationMap" element={<AviationMap />} />
|
<Route path="aviationMap" element={<AviationMap />} />
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import DataDuckIcon from "products/Bindapt/DataDuckIcon";
|
||||||
import ContractsIcon from "products/CommonIcons/contractIcon";
|
import ContractsIcon from "products/CommonIcons/contractIcon";
|
||||||
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
||||||
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
||||||
|
import LibraCartIcon from "products/CommonIcons/libracartIcon";
|
||||||
|
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
|
@ -436,7 +437,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
{(isAg || isStreamline) &&
|
{(isAg || isStreamline || user.hasFeature("admin")) &&
|
||||||
<Tooltip title="Marketplace" placement="right">
|
<Tooltip title="Marketplace" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-marketplace"
|
id="tour-marketplace"
|
||||||
|
|
@ -477,6 +478,20 @@ export default function SideNavigator(props: Props) {
|
||||||
{open && <ListItemText primary="Case New Holland" />}
|
{open && <ListItemText primary="Case New Holland" />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{user.hasFeature("libra-cart") &&
|
||||||
|
<Tooltip title="LibraCart" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-libraCart"
|
||||||
|
onClick={() => goTo("/libracart")}
|
||||||
|
classes={getClasses("/libracart")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<LibraCartIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="LibraCart" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1214,37 +1214,6 @@ export default function Bins(props: Props) {
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{/* <StyledToggleButtonGroup
|
|
||||||
id="cardValueDisplay"
|
|
||||||
value={cardValDisplay}
|
|
||||||
exclusive
|
|
||||||
size="small"
|
|
||||||
aria-label="card detail">
|
|
||||||
<StyledToggle
|
|
||||||
value={"low"}
|
|
||||||
aria-label="low"
|
|
||||||
onClick={() => {
|
|
||||||
setCardValDisplay("low");
|
|
||||||
}}>
|
|
||||||
Low
|
|
||||||
</StyledToggle>
|
|
||||||
<StyledToggle
|
|
||||||
value={"average"}
|
|
||||||
aria-label="average"
|
|
||||||
onClick={() => {
|
|
||||||
setCardValDisplay("average");
|
|
||||||
}}>
|
|
||||||
Average
|
|
||||||
</StyledToggle>
|
|
||||||
<StyledToggle
|
|
||||||
value={"high"}
|
|
||||||
aria-label="high"
|
|
||||||
onClick={() => {
|
|
||||||
setCardValDisplay("high");
|
|
||||||
}}>
|
|
||||||
High
|
|
||||||
</StyledToggle>
|
|
||||||
</StyledToggleButtonGroup> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid >
|
<Grid >
|
||||||
|
|
@ -1270,41 +1239,6 @@ export default function Bins(props: Props) {
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{/* <StyledToggleButtonGroup
|
|
||||||
id="tour-graph-tabs"
|
|
||||||
value={binView}
|
|
||||||
exclusive
|
|
||||||
size="small"
|
|
||||||
aria-label="detail">
|
|
||||||
<StyledToggle
|
|
||||||
value={"grid"}
|
|
||||||
aria-label="grid view"
|
|
||||||
onClick={() => {
|
|
||||||
setBinView("grid");
|
|
||||||
sessionStorage.setItem("binsView", "grid");
|
|
||||||
}}>
|
|
||||||
<ViewComfy />
|
|
||||||
</StyledToggle> */}
|
|
||||||
{/* hidden at dustins request so that grid view and list are the only two */}
|
|
||||||
{/* <StyledToggle
|
|
||||||
value={"scroll"}
|
|
||||||
aria-label="scroll view"
|
|
||||||
onClick={() => {
|
|
||||||
setBinView("scroll");
|
|
||||||
sessionStorage.setItem("binsView", "scroll");
|
|
||||||
}}>
|
|
||||||
<ViewColumn />
|
|
||||||
</StyledToggle> */}
|
|
||||||
{/* <StyledToggle
|
|
||||||
value={"list"}
|
|
||||||
aria-label="list view"
|
|
||||||
onClick={() => {
|
|
||||||
setBinView("list");
|
|
||||||
sessionStorage.setItem("binsView", "list");
|
|
||||||
}}>
|
|
||||||
<ViewList />
|
|
||||||
</StyledToggle>
|
|
||||||
</StyledToggleButtonGroup> */}
|
|
||||||
<MoreVert
|
<MoreVert
|
||||||
className={classes.icon}
|
className={classes.icon}
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import PageContainer from "./PageContainer";
|
||||||
export default function CNHi() {
|
export default function CNHi() {
|
||||||
const [currentOrg, setCurrentOrg] = useState("");
|
const [currentOrg, setCurrentOrg] = useState("");
|
||||||
const cnhiAPI = useCNHiProxyAPI();
|
const cnhiAPI = useCNHiProxyAPI();
|
||||||
const [organizations, setOrganizations] = useState<Map<string, pond.JDAccount>>(new Map());
|
const [organizations, setOrganizations] = useState<Map<string, pond.CNHiAccount>>(new Map());
|
||||||
const [fieldAccordion, setFieldAccordion] = useState(false);
|
const [fieldAccordion, setFieldAccordion] = useState(false);
|
||||||
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
|
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
|
|
|
||||||
134
src/pages/LibraCart.tsx
Normal file
134
src/pages/LibraCart.tsx
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
Grid2 as Grid,
|
||||||
|
MenuItem,
|
||||||
|
Select
|
||||||
|
} from "@mui/material";
|
||||||
|
import LibraCartAccess from "integrations/LibraCart/LibraCartAccess";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
|
||||||
|
export default function LibraCart() {
|
||||||
|
const [currentOrg, setCurrentOrg] = useState("");
|
||||||
|
const libracartAPI = useLibraCartProxyAPI();
|
||||||
|
const [organizations, setOrganizations] = useState<Map<string, pond.LibraCartAccount>>(new Map());
|
||||||
|
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
//load organizations for the user
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentOrg("");
|
||||||
|
libracartAPI
|
||||||
|
.listAccounts(0, 0, as)
|
||||||
|
.then(resp => {
|
||||||
|
let tempOrgs: Map<string, pond.LibraCartAccount> = new Map();
|
||||||
|
resp.data.accounts.forEach(org => {
|
||||||
|
let organization = pond.LibraCartAccount.fromObject(org);
|
||||||
|
tempOrgs.set(organization.key, organization);
|
||||||
|
});
|
||||||
|
setOrganizations(tempOrgs);
|
||||||
|
})
|
||||||
|
.catch(err => {});
|
||||||
|
}, [libracartAPI, as]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let organization = organizations.get(currentOrg);
|
||||||
|
if (organization) {
|
||||||
|
let currentOptions: pond.DataOption[] = organization.options ?? [];
|
||||||
|
setDataOptions(currentOptions);
|
||||||
|
}
|
||||||
|
}, [currentOrg, organizations]);
|
||||||
|
|
||||||
|
const updateOrgData = (checked: boolean, option: pond.DataOption) => {
|
||||||
|
let currentOps: pond.DataOption[] = dataOptions;
|
||||||
|
if (checked && !currentOps.includes(option)) {
|
||||||
|
currentOps.push(option);
|
||||||
|
} else if (!checked && currentOps.includes(option)) {
|
||||||
|
currentOps.splice(currentOps.indexOf(option), 1);
|
||||||
|
}
|
||||||
|
setDataOptions([...currentOps]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
libracartAPI.updateAccount(currentOrg, dataOptions, as ?? undefined).then(resp => {
|
||||||
|
//update the organization in the map to have the correct dataOptions
|
||||||
|
let org = organizations.get(currentOrg);
|
||||||
|
if (org) {
|
||||||
|
org.options = dataOptions;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fieldOptions = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid container direction="row" spacing={2} alignItems="center" alignContent="center">
|
||||||
|
<Grid size={6}>
|
||||||
|
<FormControlLabel
|
||||||
|
label="Destinations"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dataOptions.includes(pond.DataOption.DATA_OPTION_DESTINATIONS)}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
//setFields(!fields);
|
||||||
|
updateOrgData(checked, pond.DataOption.DATA_OPTION_DESTINATIONS);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={6}>
|
||||||
|
Import your destinations for the option to link it to a bin
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<LibraCartAccess />
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
style={{ padding: 10 }}>
|
||||||
|
<Grid>
|
||||||
|
<Select
|
||||||
|
//style={{ maxWidth: 110 }}
|
||||||
|
title="LibraCart Account"
|
||||||
|
displayEmpty
|
||||||
|
disableUnderline={true}
|
||||||
|
value={currentOrg}
|
||||||
|
onChange={(event: any) => {
|
||||||
|
setCurrentOrg(event.target.value);
|
||||||
|
}}>
|
||||||
|
<MenuItem key={""} value={""}>
|
||||||
|
Select account to adjust accessable data
|
||||||
|
</MenuItem>
|
||||||
|
{Array.from(organizations.values()).map(org => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={org.key} value={org.key}>
|
||||||
|
{org.username}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Button onClick={submit} variant="contained" color="primary">
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Box style={{ padding: 10 }}>{fieldOptions()}</Box>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -128,7 +128,8 @@ export default function Users() {
|
||||||
"developer",
|
"developer",
|
||||||
"marketplace",
|
"marketplace",
|
||||||
"installer",
|
"installer",
|
||||||
"cnhi"
|
"cnhi",
|
||||||
|
"libracart"
|
||||||
].sort();
|
].sort();
|
||||||
|
|
||||||
const [rows, setRows] = useState<User[]>([])
|
const [rows, setRows] = useState<User[]>([])
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ export const I2C: AddressTypeExtension = {
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, 0x4f],
|
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, 0x4f],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, 0x4f],
|
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, 0x4f],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, 0x68],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, 0x68],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b]
|
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, 0x70],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, 0x6c]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const offset = offsets.get(componentType);
|
const offset = offsets.get(componentType);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export const GetBinShapeDescribers = (): BinShapeDescriber[] => {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DEPRECATED -- the bon now knows its components from the relative object table
|
||||||
export const HasGrainCable = (bin?: pond.BinSettings): boolean => {
|
export const HasGrainCable = (bin?: pond.BinSettings): boolean => {
|
||||||
if (bin && bin.deviceComponents) {
|
if (bin && bin.deviceComponents) {
|
||||||
bin.deviceComponents.forEach(dc => {
|
bin.deviceComponents.forEach(dc => {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ export function getComponentIDString(component?: Component): string {
|
||||||
? componentIDToString({
|
? componentIDToString({
|
||||||
type: component.settings.type,
|
type: component.settings.type,
|
||||||
addressType: component.settings.addressType,
|
addressType: component.settings.addressType,
|
||||||
address: component.settings.address
|
address: component.settings.address,
|
||||||
|
expansionLine: component.settings.expansionLine,
|
||||||
|
muxLine: component.settings.muxLine
|
||||||
})
|
})
|
||||||
: "0-0-0";
|
: "0-0-0";
|
||||||
}
|
}
|
||||||
|
|
@ -40,13 +42,18 @@ export function componentIDToString(componentID?: quack.IComponentID | null): st
|
||||||
if (!componentID) {
|
if (!componentID) {
|
||||||
return "0-0-0";
|
return "0-0-0";
|
||||||
}
|
}
|
||||||
return (
|
let compositeLocation = or(componentID.type, 0).toString() +
|
||||||
or(componentID.type, 0).toString() +
|
|
||||||
"-" +
|
"-" +
|
||||||
or(componentID.addressType, 0).toString() +
|
or(componentID.addressType, 0).toString() +
|
||||||
"-" +
|
"-" +
|
||||||
or(componentID.address, 0).toString()
|
or(componentID.address, 0).toString()
|
||||||
);
|
if(componentID.expansionLine){
|
||||||
|
compositeLocation = compositeLocation + "-" + componentID.expansionLine
|
||||||
|
}
|
||||||
|
if(componentID.muxLine){
|
||||||
|
compositeLocation = compositeLocation + ":" + componentID.muxLine
|
||||||
|
}
|
||||||
|
return compositeLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringToComponentId(componentIDString: string): quack.ComponentID {
|
export function stringToComponentId(componentIDString: string): quack.ComponentID {
|
||||||
|
|
|
||||||
|
|
@ -48,16 +48,16 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension {
|
||||||
return {
|
return {
|
||||||
type: quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE,
|
type: quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE,
|
||||||
subtypes: [
|
subtypes: [
|
||||||
{
|
// {
|
||||||
key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_NONE,
|
// key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_NONE,
|
||||||
value: "DRAGER_GAS_DONGLE_SUBTYPE_NONE",
|
// value: "DRAGER_GAS_DONGLE_SUBTYPE_NONE",
|
||||||
friendlyName: "Drager Gas Chain"
|
// friendlyName: "Drager Gas Chain"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_DEBUG_VOLTAGE,
|
// key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_DEBUG_VOLTAGE,
|
||||||
value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED",
|
// value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED",
|
||||||
friendlyName: "Drager Gas Chain - Voltage Debug"
|
// friendlyName: "Drager Gas Chain - Voltage Debug"
|
||||||
},
|
// },
|
||||||
// {
|
// {
|
||||||
// key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_CO_CO2_NO2_O2,
|
// key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_CO_CO2_NO2_O2,
|
||||||
// value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED",
|
// value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED",
|
||||||
|
|
@ -100,7 +100,7 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension {
|
||||||
isSource: true,
|
isSource: true,
|
||||||
isArray: true,
|
isArray: true,
|
||||||
isCalibratable: false,
|
isCalibratable: false,
|
||||||
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY],
|
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C],
|
||||||
interactionResultTypes: [],
|
interactionResultTypes: [],
|
||||||
states: [],
|
states: [],
|
||||||
//this is apparently used by the interactions to determine what the possible options for measurement type are
|
//this is apparently used by the interactions to determine what the possible options for measurement type are
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ export function GrainCable(subtype: number = 0): ComponentTypeExtension {
|
||||||
isSource: true,
|
isSource: true,
|
||||||
isArray: true,
|
isArray: true,
|
||||||
isCalibratable: false,
|
isCalibratable: false,
|
||||||
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY],
|
addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C],
|
||||||
interactionResultTypes: [],
|
interactionResultTypes: [],
|
||||||
states: [],
|
states: [],
|
||||||
measurements: [
|
measurements: [
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,9 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map<quack.AddressType, De
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, [0x50]],
|
[quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, [0x50]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, [0x50]],
|
[quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, [0x50]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]]
|
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]], // the address cables will use when they are plugged into the expander with V2 device only
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]],
|
[quack.AddressType.ADDRESS_TYPE_DAC, [0, 1]],
|
||||||
|
|
@ -103,7 +105,8 @@ export class DeviceAvailability {
|
||||||
ClaimAddress(
|
ClaimAddress(
|
||||||
addressType: quack.AddressType,
|
addressType: quack.AddressType,
|
||||||
componentType: quack.ComponentType,
|
componentType: quack.ComponentType,
|
||||||
address: number
|
address: number,
|
||||||
|
expansionLine?: number
|
||||||
) {
|
) {
|
||||||
let offset = addressType - 8;
|
let offset = addressType - 8;
|
||||||
if (offset > 0) addressType = addressType - (7 + offset);
|
if (offset > 0) addressType = addressType - (7 + offset);
|
||||||
|
|
@ -127,6 +130,9 @@ export class DeviceAvailability {
|
||||||
break;
|
break;
|
||||||
case quack.AddressType.ADDRESS_TYPE_I2C:
|
case quack.AddressType.ADDRESS_TYPE_I2C:
|
||||||
case quack.AddressType.ADDRESS_TYPE_SPI:
|
case quack.AddressType.ADDRESS_TYPE_SPI:
|
||||||
|
if(expansionLine){
|
||||||
|
break;
|
||||||
|
}
|
||||||
let addressTypePositions = cloneDeep(
|
let addressTypePositions = cloneDeep(
|
||||||
this.availability.get(addressType) as ComponentAvailabilityMap
|
this.availability.get(addressType) as ComponentAvailabilityMap
|
||||||
);
|
);
|
||||||
|
|
@ -171,7 +177,8 @@ export function FindAvailablePositions(
|
||||||
available.ClaimAddress(
|
available.ClaimAddress(
|
||||||
component.settings.addressType,
|
component.settings.addressType,
|
||||||
component.settings.type,
|
component.settings.type,
|
||||||
or(component.settings.address, 0)
|
or(component.settings.address, 0),
|
||||||
|
component.settings.expansionLine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -246,12 +246,13 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
|
||||||
[
|
[
|
||||||
quack.AddressType.ADDRESS_TYPE_I2C,
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
new Map<quack.ComponentType, number[]>([
|
new Map<quack.ComponentType, number[]>([
|
||||||
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
|
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
|
||||||
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
|
@ -278,12 +279,13 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map<
|
||||||
[
|
[
|
||||||
quack.AddressType.ADDRESS_TYPE_I2C,
|
quack.AddressType.ADDRESS_TYPE_I2C,
|
||||||
new Map<quack.ComponentType, number[]>([
|
new Map<quack.ComponentType, number[]>([
|
||||||
//[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
|
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
|
||||||
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
[quack.ComponentType.COMPONENT_TYPE_CO2, [0x61]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]]
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, [0x71]]
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
|
|
||||||
24
src/products/CommonIcons/libracartIcon.tsx
Normal file
24
src/products/CommonIcons/libracartIcon.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// update logo images when they share official black and white
|
||||||
|
import LibraCartLogoWhite from "assets/marketplaceImages/LibraCartGrey.png";
|
||||||
|
import LibraCartLogoBlack from "assets/marketplaceImages/LibraCartGrey.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LibraCartIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? LibraCartLogoWhite : LibraCartLogoBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? LibraCartLogoBlack : LibraCartLogoWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="libra-cart" src={src()} />;
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,9 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map<
|
||||||
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
|
||||||
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]]
|
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]],
|
||||||
|
[quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]]
|
||||||
|
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ export {
|
||||||
useFileControllerAPI,
|
useFileControllerAPI,
|
||||||
useContractAPI, //TODO: update api with resolve, reject
|
useContractAPI, //TODO: update api with resolve, reject
|
||||||
useJohnDeereProxyAPI, //TODO: update api with resolve, reject
|
useJohnDeereProxyAPI, //TODO: update api with resolve, reject
|
||||||
useCNHiProxyAPI //TODO: update api with resolve, reject
|
useCNHiProxyAPI, //TODO: update api with resolve, reject
|
||||||
|
useLibraCartProxyAPI //TODO: update api with resolve, reject
|
||||||
} from "./pond/pond";
|
} from "./pond/pond";
|
||||||
// export { SecurityContext, useSecurity } from "./security";
|
// export { SecurityContext, useSecurity } from "./security";
|
||||||
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||||
|
|
|
||||||
128
src/providers/pond/libracartProxyAPI.tsx
Normal file
128
src/providers/pond/libracartProxyAPI.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
//import { or } from "utils";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { useGlobalState } from "providers/StateContainer";
|
||||||
|
|
||||||
|
export interface ILibraCartProxyAPIContext {
|
||||||
|
//add new organization
|
||||||
|
addAccount: (
|
||||||
|
teamKey: string,
|
||||||
|
userID: string,
|
||||||
|
libracartCode: string,
|
||||||
|
libracartAuth: string,
|
||||||
|
libracartUsername: string
|
||||||
|
) => Promise<AxiosResponse<pond.AddLibraCartAccountResponse>>;
|
||||||
|
//list organizations
|
||||||
|
listAccounts: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
as?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => Promise<AxiosResponse<pond.ListLibraCartAccountsResponse>>;
|
||||||
|
updateAccount: (
|
||||||
|
key: string,
|
||||||
|
options: pond.DataOption[],
|
||||||
|
as?: string
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateLibraCartAccountResponse>>;
|
||||||
|
listDestinations: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
libracartKey?: string,
|
||||||
|
as?: string
|
||||||
|
) => Promise<AxiosResponse<pond.ListLibraCartDestinationsResponse>>;
|
||||||
|
syncData: () => Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LibraCartProxyAPIContext = createContext<ILibraCartProxyAPIContext>(
|
||||||
|
{} as ILibraCartProxyAPIContext
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function LibraCartProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const [{as}] = useGlobalState();
|
||||||
|
const { post, get, put } = useHTTP();
|
||||||
|
|
||||||
|
const addAccount = (
|
||||||
|
teamKey: string,
|
||||||
|
userID: string,
|
||||||
|
libracartCode: string,
|
||||||
|
libracartAuth: string,
|
||||||
|
libracartUsername: string
|
||||||
|
) => {
|
||||||
|
return post<pond.AddLibraCartAccountResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/libracartAccounts?team=" +
|
||||||
|
teamKey +
|
||||||
|
"&user=" +
|
||||||
|
userID +
|
||||||
|
"&code=" +
|
||||||
|
libracartCode +
|
||||||
|
"&auth=" +
|
||||||
|
libracartAuth +
|
||||||
|
"&libracartUsername=" +
|
||||||
|
libracartUsername
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listAccounts = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
as?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[]
|
||||||
|
) => {
|
||||||
|
return get<pond.ListLibraCartAccountsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/libracartAccounts?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
(as ? "&as=" + as : "") +
|
||||||
|
(keys ? "&keys=" + keys.join(",") : "") +
|
||||||
|
(types ? "&types=" + types.join(",") : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAccount = (key: string, options: pond.DataOption[], as?: string) => {
|
||||||
|
return put<pond.UpdateLibraCartAccountResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/libracartAccounts/" + key + "?options=" + options.toString() + (as ? "&as=" + as : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listDestinations = (limit: number, offset: number, libracartKey?: string, as?: string) => {
|
||||||
|
return get<pond.ListLibraCartDestinationsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/libracartDestinations?limit" + limit + "&offset=" + offset + (libracartKey ? "&libracartKey=" + libracartKey : "") + (as ? "&as=" + as : "")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncData = () => {
|
||||||
|
return get(pondURL("/libracartImport" + (as ? "?as=" + as : "")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LibraCartProxyAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
addAccount,
|
||||||
|
listAccounts,
|
||||||
|
updateAccount,
|
||||||
|
listDestinations,
|
||||||
|
syncData
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</LibraCartProxyAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLibraCartProxyAPI = () => useContext(LibraCartProxyAPIContext);
|
||||||
|
|
@ -35,6 +35,7 @@ import ContractProvider, { useContractAPI } from "./contractAPI";
|
||||||
import UsageProvider, { useUsageAPI } from "./usageAPI";
|
import UsageProvider, { useUsageAPI } from "./usageAPI";
|
||||||
import KeyManagerProvider, { useKeyManagerAPI } from "./keyManagerAPI";
|
import KeyManagerProvider, { useKeyManagerAPI } from "./keyManagerAPI";
|
||||||
import JohnDeereProvider, { useJohnDeereProxyAPI } from "./johnDeereProxyAPI";
|
import JohnDeereProvider, { useJohnDeereProxyAPI } from "./johnDeereProxyAPI";
|
||||||
|
import LibraCartProvider, { useLibraCartProxyAPI } from "./libracartProxyAPI";
|
||||||
import CNHiProvider, { useCNHiProxyAPI } from "./cnhiProxyAPI";
|
import CNHiProvider, { useCNHiProxyAPI } from "./cnhiProxyAPI";
|
||||||
import DevicePresetProvider, { useDevicePresetAPI } from "./devicePresetAPI";
|
import DevicePresetProvider, { useDevicePresetAPI } from "./devicePresetAPI";
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
@ -87,13 +88,15 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<ContractProvider>
|
<ContractProvider>
|
||||||
<JohnDeereProvider>
|
<JohnDeereProvider>
|
||||||
<CNHiProvider>
|
<CNHiProvider>
|
||||||
<UsageProvider>
|
<LibraCartProvider>
|
||||||
<KeyManagerProvider>
|
<UsageProvider>
|
||||||
<DevicePresetProvider>
|
<KeyManagerProvider>
|
||||||
{children}
|
<DevicePresetProvider>
|
||||||
</DevicePresetProvider>
|
{children}
|
||||||
</KeyManagerProvider>
|
</DevicePresetProvider>
|
||||||
</UsageProvider>
|
</KeyManagerProvider>
|
||||||
|
</UsageProvider>
|
||||||
|
</LibraCartProvider>
|
||||||
</CNHiProvider>
|
</CNHiProvider>
|
||||||
</JohnDeereProvider>
|
</JohnDeereProvider>
|
||||||
</ContractProvider>
|
</ContractProvider>
|
||||||
|
|
@ -167,5 +170,6 @@ export {
|
||||||
useKeyManagerAPI,
|
useKeyManagerAPI,
|
||||||
useJohnDeereProxyAPI,
|
useJohnDeereProxyAPI,
|
||||||
useCNHiProxyAPI,
|
useCNHiProxyAPI,
|
||||||
|
useLibraCartProxyAPI,
|
||||||
useDevicePresetAPI
|
useDevicePresetAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { useSnackbar, useUserAPI } from "hooks";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
||||||
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
||||||
|
import LibraCartIcon from "products/CommonIcons/libracartIcon";
|
||||||
//import AgLogo from "assets/whitelabels/AdaptiveAgriculture/AGLogoSquare.png";
|
//import AgLogo from "assets/whitelabels/AdaptiveAgriculture/AGLogoSquare.png";
|
||||||
import {
|
import {
|
||||||
IsAdaptiveAgriculture
|
IsAdaptiveAgriculture
|
||||||
|
|
@ -60,6 +61,14 @@ const agFeatureList: ProductDetails[] = [
|
||||||
"Integrate with the Case New Holland Industrial Center to bring your data into our platform"
|
"Integrate with the Case New Holland Industrial Center to bring your data into our platform"
|
||||||
// bulletPoints: ["bullet one", "bullet two"],
|
// bulletPoints: ["bullet one", "bullet two"],
|
||||||
// questions: [],
|
// questions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
featureName: "libra-cart",
|
||||||
|
featureLogo: <LibraCartIcon />,
|
||||||
|
featureCost: 0,
|
||||||
|
cardImage: FeatureImageTest,
|
||||||
|
featureTitle: "Libra Cart",
|
||||||
|
longDescription: "Integrate with Libra Cart to bring your data into our platform"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -80,7 +89,7 @@ export default function Marketplace() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let list: ProductDetails[] = [];
|
let list: ProductDetails[] = [];
|
||||||
if (IsAdaptiveAgriculture()) {
|
if (IsAdaptiveAgriculture() || user.hasFeature("admin")) {
|
||||||
list = list.concat(agFeatureList);
|
list = list.concat(agFeatureList);
|
||||||
}
|
}
|
||||||
// if(IsAdCon()){
|
// if(IsAdCon()){
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue