Merge branch 'staging_environment' into i2c_detect
This commit is contained in:
commit
ceaff4da9e
49 changed files with 1470 additions and 412 deletions
|
|
@ -50,7 +50,8 @@ function App() {
|
|||
|
||||
const skipCallbacks = [
|
||||
"/johndeere",
|
||||
"/cnhi"
|
||||
"/cnhi",
|
||||
"/libracart"
|
||||
]
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -82,7 +82,20 @@ export default function UserWrapper(props: Props) {
|
|||
firmware: new Map()
|
||||
})
|
||||
}).catch(() => {
|
||||
setGlobal(globalDefault)
|
||||
userAPI.getUser(user_id).then(user => {
|
||||
setGlobal({
|
||||
user: user ? user : User.create(),
|
||||
team: globalDefault.team,
|
||||
as: "",
|
||||
showErrors: false,
|
||||
userTeamPermissions: [],
|
||||
backgroundTasksComplete: false,
|
||||
firmware: new Map()
|
||||
})
|
||||
}).catch(() => {
|
||||
setGlobal(globalDefault)
|
||||
})
|
||||
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
|
|
|
|||
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 { isOffline } from "utils/environment";
|
||||
import ObjectTeams from "teams/ObjectTeams";
|
||||
// import BinDuplication from "./BinDuplication";
|
||||
import BinDuplication from "./BinDuplication";
|
||||
import BinsIcon from "products/Bindapt/BinsIcon";
|
||||
import HelpIcon from "@mui/icons-material/Help";
|
||||
import BinSensors from "./BinSensors";
|
||||
|
|
@ -269,14 +269,14 @@ export default function BinActions(props: Props) {
|
|||
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||
refreshCallback={refreshCallback}
|
||||
/>
|
||||
{/* <BinDuplication
|
||||
<BinDuplication
|
||||
open={openState.duplication}
|
||||
closeDialog={() => {
|
||||
setOpenState({ ...openState, duplication: false });
|
||||
}}
|
||||
bin={bin}
|
||||
refreshCallback={refreshCallback}
|
||||
/> */}
|
||||
/>
|
||||
<RemoveSelfFromObject
|
||||
scope={binScope(key)}
|
||||
label={label}
|
||||
|
|
|
|||
|
|
@ -103,8 +103,10 @@ export default function BinCard(props: Props) {
|
|||
let filteredNodes = c.filteredNodes(true)
|
||||
|
||||
allTemps.push(...filteredNodes.temps);
|
||||
allHums.push(...filteredNodes.humids);
|
||||
allMoistures.push(...filteredNodes.moistures);
|
||||
if(avg(c.humidities) !== 0){ //if the average of the humidities is 0 that means that all the readings are 0 and it is a temp only cable
|
||||
allHums.push(...filteredNodes.humids);
|
||||
allMoistures.push(...filteredNodes.moistures);
|
||||
}
|
||||
|
||||
//set the hottest and coldest nodes
|
||||
if (coldestNode === undefined || Math.min(...filteredNodes.temps) < coldestNode.temp) {
|
||||
|
|
@ -490,7 +492,7 @@ export default function BinCard(props: Props) {
|
|||
|
||||
return (
|
||||
<Card style={{ cursor: "pointer" }}>
|
||||
{user.hasFeature("admin") && (
|
||||
{user.hasFeature("installer") && (
|
||||
<IconButton
|
||||
style={{
|
||||
height: 35,
|
||||
|
|
@ -506,9 +508,6 @@ export default function BinCard(props: Props) {
|
|||
+
|
||||
</IconButton>
|
||||
)}
|
||||
{/* <Box position="absolute" top={4} right={4}>
|
||||
<BinModeDot mode={bin.settings.mode} />
|
||||
</Box> */}
|
||||
<Box padding={1}>
|
||||
<Typography
|
||||
align="left"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
interface Props {
|
||||
interactions: Interaction[];
|
||||
interactionDevices: Map<string, number>;
|
||||
mode: pond.BinMode.BIN_MODE_DRYING | pond.BinMode.BIN_MODE_HYDRATING;
|
||||
mode: pond.BinMode;
|
||||
plenums?: Plenum[];
|
||||
ambients?: Ambient[];
|
||||
heaters?: Controller[];
|
||||
|
|
@ -94,10 +94,21 @@ export default function BinConditioningCard(props: Props) {
|
|||
return conditioningInteractions;
|
||||
};
|
||||
|
||||
const modeDisplay = () => {
|
||||
switch(mode){
|
||||
case pond.BinMode.BIN_MODE_DRYING:
|
||||
return "Drying"
|
||||
case pond.BinMode.BIN_MODE_HYDRATING:
|
||||
return "Hydrating"
|
||||
case pond.BinMode.BIN_MODE_COOLDOWN:
|
||||
return "Cooldown"
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card raised className={classes.card}>
|
||||
<Typography style={{ fontWeight: 650 }}>
|
||||
{mode === pond.BinMode.BIN_MODE_DRYING ? "Drying" : "Hydrating"} Conditions
|
||||
{modeDisplay()} Conditions
|
||||
</Typography>
|
||||
{conditionsDisplay()}
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -47,8 +47,6 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
sliderThumb: {
|
||||
height: 15,
|
||||
width: 15,
|
||||
marginTop: -6,
|
||||
marginLeft: -7.5,
|
||||
backgroundColor: "yellow"
|
||||
},
|
||||
sliderTrack: {
|
||||
|
|
@ -60,10 +58,10 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
backgroundColor: "white"
|
||||
},
|
||||
sliderValLabel: {
|
||||
left: "calc(-50%)",
|
||||
top: 22,
|
||||
left: -20,
|
||||
top: 40,
|
||||
background: "transparent",
|
||||
"& *": {
|
||||
background: "transparent",
|
||||
color: "#fff"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -476,11 +476,11 @@ export default function BinSVGV2(props: Props) {
|
|||
return cableGrainPath;
|
||||
};
|
||||
|
||||
const nodeClass = (nodeTemp: number) => {
|
||||
if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) {
|
||||
const nodeClass = (nodeTemp: number, underTop: boolean) => {
|
||||
if (hottestNodeTemp && hottestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
|
||||
return classes.hotNode;
|
||||
}
|
||||
if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2)) {
|
||||
if (coldestNodeTemp && coldestNodeTemp.toFixed(2) === nodeTemp.toFixed(2) && underTop) {
|
||||
return classes.coldNode;
|
||||
}
|
||||
return classes.cableNode;
|
||||
|
|
@ -507,12 +507,12 @@ export default function BinSVGV2(props: Props) {
|
|||
//if the cables have the same number of nodes
|
||||
if (b.temperatures.length === a.temperatures.length) {
|
||||
//sort by temp only last and any type that has humidity first
|
||||
if (
|
||||
a.settings.subtype === quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP &&
|
||||
b.settings.subtype !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP
|
||||
) {
|
||||
if ((avg(a.humidities) === 0) && (avg(b.humidities) !== 0)) {
|
||||
return 1;
|
||||
} else {
|
||||
} else if ((avg(b.humidities) === 0) && (avg(a.humidities) !== 0)) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return a.key() > b.key() ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -759,7 +759,7 @@ export default function BinSVGV2(props: Props) {
|
|||
return (
|
||||
<g key={e.key}>
|
||||
{e.nodeNumber === topNode && !e.excluded && topNodeHat(cablePos, e.y)}
|
||||
<circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp)} r="16"></circle>
|
||||
<circle cx={cablePos} cy={e.y} className={e.excluded ? classes.excludedNode : nodeClass(e.nodeTemp, e.nodeNumber <= topNode)} r="16"></circle>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ import { Bin } from "models";
|
|||
import moment from "moment";
|
||||
import { GetBinShapeDescribers } from "pbHelpers/Bin";
|
||||
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 { useHistory } from "react-router";
|
||||
import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
|
||||
|
|
@ -176,7 +176,7 @@ export default function BinSettings(props: Props) {
|
|||
const grainOptions = GrainOptions();
|
||||
const grainUseOptions = GetGrainUseOptions();
|
||||
const [inputCapacity, setInputCapacity] = useState<string>("");
|
||||
const [{ as }] = useGlobalState();
|
||||
const [{ user, as }] = useGlobalState();
|
||||
const [grainDiff, setGrainDiff] = useState(0);
|
||||
const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false);
|
||||
const [grainUpdate, setGrainUpdate] = useState(false);
|
||||
|
|
@ -197,10 +197,13 @@ export default function BinSettings(props: Props) {
|
|||
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
|
||||
pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
|
||||
);
|
||||
|
||||
const [storageType, setStorageType] = useState<pond.BinStorage>(
|
||||
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
|
||||
);
|
||||
//libracart stuff
|
||||
const libracartAPI = useLibraCartProxyAPI()
|
||||
const [lcDestination, setlcDestination] = useState<Option | null>()
|
||||
const [lcDestinationOptions, setlcDestinationOptions] = useState<Option[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
|
|
@ -365,9 +368,33 @@ export default function BinSettings(props: Props) {
|
|||
}
|
||||
}, [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(() => {
|
||||
loadBinYards();
|
||||
}, [loadBinYards]);
|
||||
loadLibraCartDestinations();
|
||||
}, [loadBinYards, loadLibraCartDestinations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bin?.settings.specs?.bushelCapacity) {
|
||||
|
|
@ -458,6 +485,7 @@ export default function BinSettings(props: Props) {
|
|||
form.inventory.inventoryControl = inventoryControl
|
||||
form.inventory.autoThreshold = autoFillThreshold
|
||||
}
|
||||
console.log(form)
|
||||
binAPI
|
||||
.updateBin(bin.key(), form, as)
|
||||
.then(response => {
|
||||
|
|
@ -713,6 +741,8 @@ export default function BinSettings(props: Props) {
|
|||
return "Auto (cable)"
|
||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
|
||||
return "Hybrid (lidar)"
|
||||
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
|
||||
return "Auto (Libra Cart)"
|
||||
default:
|
||||
return "Manual"
|
||||
}
|
||||
|
|
@ -860,7 +890,7 @@ export default function BinSettings(props: Props) {
|
|||
<FormControlLabel
|
||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC}
|
||||
control={<Radio />}
|
||||
label={"Auto"}
|
||||
label={"Auto (Cable)"}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR}
|
||||
|
|
@ -872,6 +902,13 @@ export default function BinSettings(props: Props) {
|
|||
control={<Radio />}
|
||||
label={"Hybrid (Lidar)"}
|
||||
/>
|
||||
{user.hasFeature("libra-cart") &&
|
||||
<FormControlLabel
|
||||
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
|
||||
control={<Radio />}
|
||||
label={"Auto (Libra Cart)"}
|
||||
/>
|
||||
}
|
||||
</RadioGroup>
|
||||
</AccordionDetails>
|
||||
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
|
||||
|
|
@ -912,6 +949,25 @@ export default function BinSettings(props: Props) {
|
|||
/>
|
||||
</Box>
|
||||
}
|
||||
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
|
||||
<Box width="100%" padding={2}>
|
||||
<SearchSelect
|
||||
label="Libra Cart 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 Libra Cart Destination data is synced every 6 hours
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
</Accordion>
|
||||
{empty ? (
|
||||
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center">
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { Bin, Component } from "models";
|
|||
import { GrainCable } from "models/GrainCable";
|
||||
import { UnitMeasurement } from "models/UnitMeasurement";
|
||||
import Co2Icon from "products/CommonIcons/co2Icon";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { pond, quack } from "protobuf-ts/pond";
|
||||
import { useBinAPI, useGlobalState, useSnackbar } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { avg, getTemperatureUnit } from "utils";
|
||||
|
|
@ -200,33 +200,32 @@ export default function BinStorageConditions(props: Props) {
|
|||
// }
|
||||
});
|
||||
|
||||
let nodeEMCs: number[] = [];
|
||||
|
||||
filteredNodes.moistures.forEach((emc, i) => {
|
||||
if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
|
||||
let nodeEMCs: number[] = [];
|
||||
filteredNodes.moistures.forEach((emc, i) => {
|
||||
if (bin.settings.inventory?.targetMoisture) {
|
||||
// if (i < cable.topNode) {
|
||||
nodeEMCs.push(emc);
|
||||
nodeEMCs.push(emc);
|
||||
emcCounts.total++;
|
||||
if (
|
||||
emc >
|
||||
bin.settings.inventory.targetMoisture +
|
||||
bin.settings.inventory.moistureTargetDeviation
|
||||
bin.settings.inventory.moistureTargetDeviation
|
||||
) {
|
||||
emcCounts.above++;
|
||||
} else if (
|
||||
emc <
|
||||
bin.settings.inventory.targetMoisture -
|
||||
bin.settings.inventory.moistureTargetDeviation
|
||||
bin.settings.inventory.moistureTargetDeviation
|
||||
) {
|
||||
emcCounts.below++;
|
||||
} else {
|
||||
emcCounts.onTarget++;
|
||||
}
|
||||
// }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
cableEMCAvgs.push(avg(nodeEMCs));
|
||||
}
|
||||
cableTempAvgs.push(avg(nodeTemps));
|
||||
cableEMCAvgs.push(avg(nodeEMCs));
|
||||
}
|
||||
});
|
||||
if (cableTempAvgs.length > 0) {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ export default function BinVisualizer(props: Props) {
|
|||
const [cfmHighOpen, setCFMHighOpen] = useState(false);
|
||||
const [{ user }] = useGlobalState();
|
||||
const [newPreset, setNewPreset] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
|
||||
const [newBinMode, setNewBinMode] = useState<pond.BinMode>(pond.BinMode.BIN_MODE_NONE);
|
||||
const [selectedCable, setSelectedCable] = useState<GrainCable>();
|
||||
const [openNodeDialog, setOpenNodeDialog] = useState(false);
|
||||
const [cableDevice, setCableDevice] = useState<Device | undefined>();
|
||||
|
|
@ -345,6 +346,7 @@ export default function BinVisualizer(props: Props) {
|
|||
setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
|
||||
setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2));
|
||||
setGrainSubtype(bin.subtype());
|
||||
setNewBinMode(bin.settings.mode);
|
||||
}
|
||||
if (bin.settings) {
|
||||
let t = bin.settings.outdoorTemp;
|
||||
|
|
@ -384,8 +386,11 @@ export default function BinVisualizer(props: Props) {
|
|||
//also reverse it so that the node 1 (end of the cable) is in the first position
|
||||
let filteredNodes = cable.filteredNodes(true)
|
||||
temps.push(...filteredNodes.temps)
|
||||
humids.push(...filteredNodes.humids)
|
||||
emcs.push(...filteredNodes.moistures)
|
||||
//only push to the humidity values if the cable reads humidities
|
||||
if(cable.subType() !== quack.GrainCableSubtype.GRAIN_CABLE_SUBTYPE_OPI_TEMP){
|
||||
humids.push(...filteredNodes.humids)
|
||||
emcs.push(...filteredNodes.moistures)
|
||||
}
|
||||
// let tempClone = cloneDeep(cable.temperatures).reverse();
|
||||
// let humClone = cloneDeep(cable.humidities).reverse();
|
||||
// let emcClone = cloneDeep(cable.grainMoistures).reverse();
|
||||
|
|
@ -409,14 +414,15 @@ export default function BinVisualizer(props: Props) {
|
|||
|
||||
//if the lowest temp for this cable is less than the temp in the low node conditions or the low node conditions are not set
|
||||
//use this cables lowest node and trend data in the condition
|
||||
if (!lowNodeConditions || Math.min(...temps) < lowNodeConditions.tempC) {
|
||||
if (!lowNodeConditions || Math.min(...filteredNodes.temps) < lowNodeConditions.tempC) {
|
||||
//determine which node is the coldest so that the data displayed is for the same node
|
||||
let lowTempIndex =
|
||||
temps.indexOf(Math.min(...temps)) === -1 ? 0 : temps.indexOf(Math.min(...temps));
|
||||
filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.min(...filteredNodes.temps));
|
||||
console.log(lowTempIndex)
|
||||
lowNodeConditions = {
|
||||
tempC: temps[lowTempIndex],
|
||||
humidity: humids[lowTempIndex],
|
||||
emc: emcs[lowTempIndex],
|
||||
tempC: filteredNodes.temps[lowTempIndex],
|
||||
humidity: filteredNodes.humids[lowTempIndex],
|
||||
emc: filteredNodes.moistures[lowTempIndex],
|
||||
tempCTrend: cableTrendData.coldNodeTrend?.tempTrend ?? 0,
|
||||
humidityTrend: cableTrendData.coldNodeTrend?.humidityTrend ?? 0,
|
||||
emcTrend: cableTrendData.coldNodeTrend?.emcTrend ?? 0
|
||||
|
|
@ -424,13 +430,13 @@ export default function BinVisualizer(props: Props) {
|
|||
}
|
||||
|
||||
//do the same for the high node
|
||||
if (!highNodeConditions || cable.maxTemp() > highNodeConditions.tempC) {
|
||||
if (!highNodeConditions || Math.max(...filteredNodes.temps) > highNodeConditions.tempC) {
|
||||
let highTempIndex =
|
||||
temps.indexOf(Math.max(...temps)) === -1 ? 0 : temps.indexOf(Math.max(...temps));
|
||||
filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps)) === -1 ? 0 : filteredNodes.temps.indexOf(Math.max(...filteredNodes.temps));
|
||||
highNodeConditions = {
|
||||
tempC: temps[highTempIndex],
|
||||
humidity: humids[highTempIndex],
|
||||
emc: emcs[highTempIndex],
|
||||
tempC: filteredNodes.temps[highTempIndex],
|
||||
humidity: filteredNodes.humids[highTempIndex],
|
||||
emc: filteredNodes.moistures[highTempIndex],
|
||||
tempCTrend: cableTrendData.hotNodeTrend?.tempTrend ?? 0,
|
||||
humidityTrend: cableTrendData.hotNodeTrend?.humidityTrend ?? 0,
|
||||
emcTrend: cableTrendData.hotNodeTrend?.emcTrend ?? 0
|
||||
|
|
@ -1967,6 +1973,7 @@ export default function BinVisualizer(props: Props) {
|
|||
|
||||
const setModeStorage = () => {
|
||||
setNewPreset(pond.BinMode.BIN_MODE_STORAGE);
|
||||
setNewBinMode(pond.BinMode.BIN_MODE_STORAGE);
|
||||
};
|
||||
|
||||
const setModeCooldown = () => {
|
||||
|
|
@ -1974,6 +1981,7 @@ export default function BinVisualizer(props: Props) {
|
|||
return;
|
||||
}
|
||||
setNewPreset(pond.BinMode.BIN_MODE_COOLDOWN);
|
||||
setNewBinMode(pond.BinMode.BIN_MODE_COOLDOWN);
|
||||
};
|
||||
|
||||
const setModeDrying = () => {
|
||||
|
|
@ -1982,13 +1990,16 @@ export default function BinVisualizer(props: Props) {
|
|||
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture
|
||||
) {
|
||||
setNewPreset(pond.BinMode.BIN_MODE_HYDRATING);
|
||||
setNewBinMode(pond.BinMode.BIN_MODE_HYDRATING);
|
||||
} else {
|
||||
setNewPreset(pond.BinMode.BIN_MODE_DRYING);
|
||||
setNewBinMode(pond.BinMode.BIN_MODE_DRYING);
|
||||
}
|
||||
};
|
||||
|
||||
const closeMoistureDialog = () => {
|
||||
setNewPreset(0);
|
||||
setNewBinMode(bin.settings.mode);
|
||||
setShowInputMoisture(false);
|
||||
};
|
||||
|
||||
|
|
@ -2049,6 +2060,7 @@ export default function BinVisualizer(props: Props) {
|
|||
<Button
|
||||
onClick={() => {
|
||||
setNewPreset(0);
|
||||
setNewBinMode(bin.settings.mode);
|
||||
setShowInputMoisture(false);
|
||||
}}
|
||||
color="primary">
|
||||
|
|
@ -2090,7 +2102,13 @@ export default function BinVisualizer(props: Props) {
|
|||
}
|
||||
if (b.settings.outdoorHumidity !== undefined)
|
||||
b.settings.outdoorHumidity = Number(outdoorHumidityInput);
|
||||
b.settings.mode = newPreset;
|
||||
|
||||
if (b.settings.mode != newBinMode){
|
||||
if(b.settings.inventory){
|
||||
b.settings.inventory.initialTimestamp = moment().format();
|
||||
}
|
||||
b.settings.mode = newBinMode;
|
||||
}
|
||||
|
||||
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
|
||||
refresh();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ const useStyles = makeStyles((_theme) => {
|
|||
position: "relative",
|
||||
minHeight: "233px",
|
||||
height: "auto !important",
|
||||
width: "184px",
|
||||
padding: 2
|
||||
},
|
||||
hidden: {
|
||||
|
|
@ -113,7 +112,7 @@ export default function BinsList(props: Props) {
|
|||
return (
|
||||
<Grid container direction="row">
|
||||
{bins.map((b, i) =>
|
||||
isMobile ? (
|
||||
(
|
||||
<Grid
|
||||
size={{
|
||||
xs: 6,
|
||||
|
|
@ -133,21 +132,6 @@ export default function BinsList(props: Props) {
|
|||
valDisplay={valDisplay}
|
||||
/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export default function BinComponentGraph(props: Props) {
|
|||
<UnitMeasurementSummary
|
||||
component={component}
|
||||
reading={UnitMeasurement.convertLastMeasurement(
|
||||
component.status.lastGoodMeasurement.map(m => UnitMeasurement.create(m, user))
|
||||
lastMeasurement.map(m => UnitMeasurement.create(m, user))
|
||||
)}
|
||||
/>
|
||||
}
|
||||
|
|
|
|||
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 {
|
||||
Box,
|
||||
Card,
|
||||
CircularProgress,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
Switch,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { blue, grey, red, yellow, orange } from "@mui/material/colors";
|
||||
import BarGraph, { BarData, RefArea } from "charts/BarGraph";
|
||||
import BarGraph, { BarData } from "charts/BarGraph";
|
||||
import { Bin } from "models";
|
||||
import moment, { Moment } from "moment";
|
||||
import { pond, quack } from "protobuf-ts/pond";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useBinAPI, useGlobalState } from "providers";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Legend } from "recharts";
|
||||
import { getGrainUnit } from "utils";
|
||||
import BinLevelAreaGraph from "./BinLevelAreaGraph";
|
||||
|
||||
interface Props {
|
||||
binLoading: boolean;
|
||||
|
|
@ -27,12 +24,16 @@ interface Props {
|
|||
customHeight?: number | string;
|
||||
}
|
||||
|
||||
interface InventoryAt {
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default function BinLevelOverTime(props: Props) {
|
||||
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
|
||||
const binAPI = useBinAPI();
|
||||
const [{as}] = useGlobalState();
|
||||
const [data, setData] = useState<BarData[]>([]);
|
||||
const [modeAreas, setModeAreas] = useState<RefArea[]>([]);
|
||||
const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]);
|
||||
const [dataLoading, setDataLoading] = useState(false);
|
||||
const [capacity, setCapacity] = useState<number | undefined>();
|
||||
|
||||
|
|
@ -76,33 +77,15 @@ export default function BinLevelOverTime(props: Props) {
|
|||
.listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString())
|
||||
.then(resp => {
|
||||
let data: BarData[] = [];
|
||||
let modeAreas: RefArea[] = [];
|
||||
let lastBushels = -1;
|
||||
|
||||
//values for ref areas
|
||||
let x1 = 0;
|
||||
let x2 = 0;
|
||||
let y1 = -50;
|
||||
let y2 = -200;
|
||||
|
||||
let currentMode: pond.BinMode;
|
||||
|
||||
let currentMode: pond.BinMode | undefined = undefined
|
||||
let modeData: BarData[] = []
|
||||
resp.data.history.forEach(hist => {
|
||||
//build the data for the inventory bar graph
|
||||
if (hist.settings?.inventory) {
|
||||
let settings = pond.BinSettings.fromObject(hist.settings);
|
||||
let bushels = hist.settings.inventory.grainBushels ?? 0;
|
||||
if (bushels !== lastBushels || currentMode !== settings.mode) {
|
||||
x1 = moment(hist.timestamp).valueOf();
|
||||
x2 = moment(hist.timestamp).valueOf();
|
||||
modeAreas.push({
|
||||
x1: x1,
|
||||
x2: x2,
|
||||
y1: cap ? cap * -0.1 : y1,
|
||||
y2: cap ? cap * -0.2 : y2,
|
||||
fill: getFill(settings.mode)
|
||||
});
|
||||
currentMode = settings.mode;
|
||||
let newData: BarData = {
|
||||
if (bushels !== lastBushels) {
|
||||
let newData: InventoryAt = {
|
||||
timestamp: moment(hist.timestamp).valueOf(),
|
||||
value: fertilizerBin
|
||||
? Math.round(bushels * 35.239)
|
||||
|
|
@ -114,10 +97,21 @@ export default function BinLevelOverTime(props: Props) {
|
|||
lastBushels = bushels;
|
||||
}
|
||||
}
|
||||
//build the data for the mode change bar graph
|
||||
let histBin = Bin.create()
|
||||
histBin.settings = hist.settings ?? pond.BinSettings.create()
|
||||
if(hist.settings && currentMode !== hist.settings.mode){
|
||||
currentMode = hist.settings.mode
|
||||
modeData.push({
|
||||
timestamp: moment(hist.timestamp).valueOf(),
|
||||
value: 1,
|
||||
fill: getFill(currentMode)
|
||||
})
|
||||
}
|
||||
});
|
||||
let currentTime = moment().valueOf();
|
||||
if (data.length === 0) {
|
||||
let bushels = bin.settings.inventory?.grainBushels ?? 0;
|
||||
let currentTime = moment().valueOf();
|
||||
let bushels = bin.bushels();
|
||||
data.push({
|
||||
value: fertilizerBin
|
||||
? Math.round(bushels * 35.239)
|
||||
|
|
@ -126,17 +120,16 @@ export default function BinLevelOverTime(props: Props) {
|
|||
: bushels,
|
||||
timestamp: currentTime
|
||||
});
|
||||
|
||||
modeAreas.push({
|
||||
x1: currentTime,
|
||||
x2: currentTime,
|
||||
y1: cap ? cap * -0.1 : y1,
|
||||
y2: cap ? cap * -0.2 : y2,
|
||||
fill: getFill(bin.settings.mode)
|
||||
});
|
||||
}
|
||||
setData(data);
|
||||
setModeAreas(modeAreas);
|
||||
if(modeData.length === 0){
|
||||
modeData.push({
|
||||
timestamp: currentTime,
|
||||
value: 1,
|
||||
fill: getFill(bin.settings.mode)
|
||||
})
|
||||
}
|
||||
setInventoryData(data);
|
||||
setModeData(modeData)
|
||||
})
|
||||
.finally(() => {
|
||||
setDataLoading(false);
|
||||
|
|
@ -180,7 +173,7 @@ export default function BinLevelOverTime(props: Props) {
|
|||
});
|
||||
});
|
||||
if (autoBarData.length === 0) {
|
||||
let bushels = bin.settings.inventory?.grainBushels ?? 0;
|
||||
let bushels = bin.bushels();
|
||||
let currentTime = moment().valueOf();
|
||||
autoBarData.push({
|
||||
value: fertilizerBin
|
||||
|
|
@ -191,7 +184,7 @@ export default function BinLevelOverTime(props: Props) {
|
|||
timestamp: currentTime
|
||||
});
|
||||
}
|
||||
setData(autoBarData);
|
||||
setInventoryData(autoBarData);
|
||||
})
|
||||
.catch(err => {})
|
||||
.finally(() => {
|
||||
|
|
@ -220,6 +213,13 @@ export default function BinLevelOverTime(props: Props) {
|
|||
})
|
||||
}
|
||||
})
|
||||
if(modeData.length === 0){
|
||||
modeData.push({
|
||||
timestamp: moment().valueOf(),
|
||||
value: 1,
|
||||
fill: getFill(bin.settings.mode)
|
||||
})
|
||||
}
|
||||
setModeData(modeData)
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
@ -238,7 +238,8 @@ export default function BinLevelOverTime(props: Props) {
|
|||
let control = bin.inventoryControl()
|
||||
//for automatic lidar and cables get the data from measurements as an object measurement
|
||||
if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC
|
||||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
|
||||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART
|
||||
){
|
||||
//load the measurement data for the bar graph
|
||||
loadMeasurementData()
|
||||
|
|
@ -270,23 +271,30 @@ export default function BinLevelOverTime(props: Props) {
|
|||
};
|
||||
|
||||
const inventoryChart = () => {
|
||||
return (
|
||||
<BarGraph
|
||||
if(inventoryData.length > 10){
|
||||
return (
|
||||
<BinLevelAreaGraph
|
||||
data={inventoryData}
|
||||
fill={colour}
|
||||
/>
|
||||
)
|
||||
}else{
|
||||
return (
|
||||
<BarGraph
|
||||
customHeight={customHeight}
|
||||
data={data}
|
||||
refAreas={modeAreas}
|
||||
data={inventoryData}
|
||||
barColour={colour}
|
||||
yMax={capacity}
|
||||
yMin={0}
|
||||
graphLegend={modeAreas.length > 0 ? legend() : undefined}
|
||||
labelColour="white"
|
||||
labels
|
||||
useGradient
|
||||
/>
|
||||
);
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const autoBinModeChart = () => {
|
||||
const binModeChart = () => {
|
||||
return (
|
||||
<BarGraph
|
||||
data={modeData}
|
||||
|
|
@ -305,9 +313,7 @@ export default function BinLevelOverTime(props: Props) {
|
|||
Grain Levels Over Time
|
||||
</Typography>
|
||||
{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 */}
|
||||
{(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
||||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) && autoBinModeChart()}
|
||||
{binModeChart()}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -59,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
},
|
||||
stickyHeader: {
|
||||
position: "sticky",
|
||||
// backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
|
||||
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
|
||||
top: 0,
|
||||
zIndex: 300 //giving a really high z-index to make sure nothing is in front of it
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ export default function PeriodSelect(props: Props) {
|
|||
}
|
||||
}, [unit, prevUnit, onChange, value]);
|
||||
|
||||
useEffect(()=>{
|
||||
let initialUnit: TimeUnit = bestUnit(initialMs);
|
||||
let initialValue = milliToX(initialMs, initialUnit).toString();
|
||||
setValue(initialValue)
|
||||
setUnit(initialUnit)
|
||||
},[initialMs])
|
||||
|
||||
const changeValue = (event: any) => {
|
||||
let value = event.target.value;
|
||||
setValue(value);
|
||||
|
|
|
|||
|
|
@ -699,6 +699,86 @@ export default function ComponentForm(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const componentMode = () => {
|
||||
const { component, coefficient, offset } = form;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid size={{ xs: 4, sm: 3 }}>
|
||||
<Tooltip
|
||||
title="Switch to be able to customize the mode of the component"
|
||||
placement="bottom">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={component.settings.calibrate}
|
||||
onChange={toggleCalibrate}
|
||||
name="mode"
|
||||
aria-label="mode"
|
||||
color="secondary"
|
||||
/>
|
||||
}
|
||||
label="Mode"
|
||||
labelPlacement="top"
|
||||
className={classes.switchControl}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 10, sm: 4 }}>
|
||||
{compMode.mode.entryA && (
|
||||
<TextField
|
||||
id={compMode.mode.labelA}
|
||||
label={compMode.mode.labelA}
|
||||
disabled={!component.settings.calibrate}
|
||||
error={!isCoefficientValid()}
|
||||
value={coefficient}
|
||||
onChange={changeCoefficient}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select={compMode.mode.entryA === "select"}
|
||||
type={compMode.mode.entryA}
|
||||
variant="outlined">
|
||||
{compMode.mode.entryA === "select" &&
|
||||
compMode.mode.dictionaryA.map((elem: any) => {
|
||||
if (!elem.restricted || (elem.restricted && user.hasAdmin())) {
|
||||
return (
|
||||
<MenuItem key={elem.key} value={elem.value}>
|
||||
{elem.key}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined
|
||||
})}
|
||||
</TextField>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 10, sm: 5 }}>
|
||||
{compMode.mode.entryB && (
|
||||
<TextField
|
||||
id={compMode.mode.labelB}
|
||||
label={compMode.mode.labelB}
|
||||
disabled={!component.settings.calibrate}
|
||||
error={!isOffsetValid(compMode.mode.minB, compMode.mode.maxB)}
|
||||
value={offset}
|
||||
onChange={changeOffset}
|
||||
onFocus={selectText}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
type={compMode.mode.entryB}
|
||||
variant="outlined">
|
||||
{compMode.mode.entryB === "select" &&
|
||||
compMode.mode.dictionaryB.map((elem: any) => (
|
||||
<MenuItem key={elem.key} value={elem.value}>
|
||||
{elem.key}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => {
|
||||
const { component } = form;
|
||||
return describeMeasurement(measurementType, component.type(), component.subType());
|
||||
|
|
@ -1046,87 +1126,17 @@ export default function ComponentForm(props: Props) {
|
|||
</AccordionDetails>
|
||||
</Accordion>
|
||||
}
|
||||
{!compMode ? (
|
||||
device.featureSupported("individualCalibrations") ? (
|
||||
individualCalibration()
|
||||
) : (
|
||||
blanketCalibration()
|
||||
)
|
||||
) : (
|
||||
{device.featureSupported("individualCalibrations") ? (
|
||||
<React.Fragment>
|
||||
<Grid size={{ xs: 4, sm: 3 }}>
|
||||
<Tooltip
|
||||
title="Switch to be able to customize the mode of the component"
|
||||
placement="bottom">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={component.settings.calibrate}
|
||||
onChange={toggleCalibrate}
|
||||
name="mode"
|
||||
aria-label="mode"
|
||||
color="secondary"
|
||||
/>
|
||||
}
|
||||
label="Mode"
|
||||
labelPlacement="top"
|
||||
className={classes.switchControl}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 10, sm: 4 }}>
|
||||
{compMode.mode.entryA && (
|
||||
<TextField
|
||||
id={compMode.mode.labelA}
|
||||
label={compMode.mode.labelA}
|
||||
disabled={!component.settings.calibrate}
|
||||
error={!isCoefficientValid()}
|
||||
value={coefficient}
|
||||
onChange={changeCoefficient}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select={compMode.mode.entryA === "select"}
|
||||
type={compMode.mode.entryA}
|
||||
variant="outlined">
|
||||
{compMode.mode.entryA === "select" &&
|
||||
compMode.mode.dictionaryA.map((elem: any) => {
|
||||
if (!elem.restricted || (elem.restricted && user.hasAdmin())) {
|
||||
return (
|
||||
<MenuItem key={elem.key} value={elem.value}>
|
||||
{elem.key}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined
|
||||
})}
|
||||
</TextField>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 10, sm: 5 }}>
|
||||
{compMode.mode.entryB && (
|
||||
<TextField
|
||||
id={compMode.mode.labelB}
|
||||
label={compMode.mode.labelB}
|
||||
disabled={!component.settings.calibrate}
|
||||
error={!isOffsetValid(compMode.mode.minB, compMode.mode.maxB)}
|
||||
value={offset}
|
||||
onChange={changeOffset}
|
||||
onFocus={selectText}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
type={compMode.mode.entryB}
|
||||
variant="outlined">
|
||||
{compMode.mode.entryB === "select" &&
|
||||
compMode.mode.dictionaryB.map((elem: any) => (
|
||||
<MenuItem key={elem.key} value={elem.value}>
|
||||
{elem.key}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
</Grid>
|
||||
{compMode && componentMode()}
|
||||
{individualCalibration()}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
!compMode ? (
|
||||
blanketCalibration()
|
||||
):(
|
||||
componentMode()
|
||||
)
|
||||
)}
|
||||
<TextField
|
||||
id="dataAveraging"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,45 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 12,
|
||||
"subtype": 1,
|
||||
"mode": {
|
||||
"labelA": "Power Mode",
|
||||
"entryA": "select",
|
||||
"dictionaryA": [
|
||||
{
|
||||
"key": "High Power",
|
||||
"value": 1,
|
||||
"restricted": false
|
||||
},
|
||||
{
|
||||
"key": "Low Power",
|
||||
"value": 2,
|
||||
"restricted": false
|
||||
},
|
||||
{
|
||||
"key": "Calibration",
|
||||
"value": 3,
|
||||
"restricted": true
|
||||
}
|
||||
],
|
||||
"labelB": "Number of Averages",
|
||||
"entryB": "number",
|
||||
"dictionaryB": [
|
||||
{
|
||||
"key": "min",
|
||||
"value": 1,
|
||||
"restricted": false
|
||||
},
|
||||
{
|
||||
"key": "max",
|
||||
"value": 8,
|
||||
"restricted": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 28,
|
||||
"subtype": 0,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
Slider,
|
||||
|
|
@ -19,7 +19,7 @@ import { DevicePreset } from "models/DevicePreset";
|
|||
import { ObjectTypeString } from "objects/ObjectDescriber";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useSnackbar } from "providers";
|
||||
// import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
|
||||
import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getTemperatureUnit } from "utils";
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
removeCallback,
|
||||
updateCallback
|
||||
} = props;
|
||||
// const devicePresetAPI = useDevicePresetAPI();
|
||||
const devicePresetAPI = useDevicePresetAPI();
|
||||
const [presetName, setPresetName] = useState("");
|
||||
const [presetType, setPresetType] = useState<pond.PresetType>(1);
|
||||
const [controllerType, setControllerType] = useState<pond.ControllerType>(1);
|
||||
|
|
@ -81,22 +81,22 @@ export default function DevicePresetCard(props: Props) {
|
|||
p.settings.temperature = tempC;
|
||||
p.settings.humidity = hum;
|
||||
|
||||
// devicePresetAPI
|
||||
// .addDevicePreset(p.name, p.settings, objectKey, typestring)
|
||||
// .then(resp => {
|
||||
// //get the temp key from the preset
|
||||
// let temp = p.key;
|
||||
// //update the key in the preset with the key that came back from the add
|
||||
// p.key = resp.data.key;
|
||||
// //use the callback function passing in the preset and the temp key
|
||||
// if (addCallback) {
|
||||
// addCallback(p, temp);
|
||||
// }
|
||||
// openSnack("New preset saved");
|
||||
// })
|
||||
// .catch(err => {
|
||||
// openSnack("Failed to save new preset");
|
||||
// });
|
||||
devicePresetAPI
|
||||
.addDevicePreset(p.name, p.settings, objectKey, typestring)
|
||||
.then(resp => {
|
||||
//get the temp key from the preset
|
||||
let temp = p.key;
|
||||
//update the key in the preset with the key that came back from the add
|
||||
p.key = resp.data.key;
|
||||
//use the callback function passing in the preset and the temp key
|
||||
if (addCallback) {
|
||||
addCallback(p, temp);
|
||||
}
|
||||
openSnack("New preset saved");
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to save new preset");
|
||||
});
|
||||
};
|
||||
|
||||
const updatePreset = () => {
|
||||
|
|
@ -108,32 +108,32 @@ export default function DevicePresetCard(props: Props) {
|
|||
newPreset.settings.temperature = tempC;
|
||||
newPreset.settings.humidity = hum;
|
||||
|
||||
// devicePresetAPI
|
||||
// .updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings)
|
||||
// .then(resp => {
|
||||
// if (updateCallback) {
|
||||
// updateCallback(oldPreset, newPreset);
|
||||
// }
|
||||
// openSnack("Preset Updated");
|
||||
// })
|
||||
// .catch(err => {
|
||||
// openSnack("There was a problem updating the preset");
|
||||
// });
|
||||
devicePresetAPI
|
||||
.updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings)
|
||||
.then(resp => {
|
||||
if (updateCallback) {
|
||||
updateCallback(oldPreset, newPreset);
|
||||
}
|
||||
openSnack("Preset Updated");
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("There was a problem updating the preset");
|
||||
});
|
||||
};
|
||||
|
||||
const removePreset = () => {
|
||||
// devicePresetAPI
|
||||
// .removeDevicePreset(preset.key)
|
||||
// .then(resp => {
|
||||
// openSnack("Preset has been deleted");
|
||||
// setOpenDeleteConfirmation(false);
|
||||
// if (removeCallback) {
|
||||
// removeCallback(preset);
|
||||
// }
|
||||
// })
|
||||
// .catch(err => {
|
||||
// openSnack("There was a problem deleting this preset");
|
||||
// });
|
||||
devicePresetAPI
|
||||
.removeDevicePreset(preset.key)
|
||||
.then(resp => {
|
||||
openSnack("Preset has been deleted");
|
||||
setOpenDeleteConfirmation(false);
|
||||
if (removeCallback) {
|
||||
removeCallback(preset);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("There was a problem deleting this preset");
|
||||
});
|
||||
};
|
||||
|
||||
const deleteConfirmation = () => {
|
||||
|
|
@ -192,7 +192,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
<Card raised style={{ padding: 10, width: "100%" }}>
|
||||
{deleteConfirmation()}
|
||||
<Grid container direction="row" alignContent="center" alignItems="center">
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Grid size={{xs:12, sm:6}}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
|
|
@ -203,7 +203,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Grid size={{xs:12, sm:6}}>
|
||||
{presetType !== pond.PresetType.PRESET_TYPE_UNKNOWN &&
|
||||
controllerType !== pond.ControllerType.CONTROLLER_TYPE_UNKNOWN && (
|
||||
<Box paddingLeft={2}>
|
||||
|
|
@ -216,7 +216,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid size={6}>
|
||||
{/* type */}
|
||||
<TextField
|
||||
style={{ width: "100%", marginRight: 5 }}
|
||||
|
|
@ -250,7 +250,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Grid size={6}>
|
||||
{/* controller type */}
|
||||
<TextField
|
||||
style={{ width: "100%", marginLeft: 5 }}
|
||||
|
|
@ -281,7 +281,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
</Grid>
|
||||
{/* temp slider */}
|
||||
{isMobile ? (
|
||||
<Grid item xs={12}>
|
||||
<Grid size={12}>
|
||||
<Box
|
||||
display="flex"
|
||||
alignContent="center"
|
||||
|
|
@ -338,10 +338,10 @@ export default function DevicePresetCard(props: Props) {
|
|||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={2}>
|
||||
<Grid size={2}>
|
||||
<Typography>Temperature</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Grid size={7}>
|
||||
<Box paddingRight={2}>
|
||||
<Slider
|
||||
//orientation="vertical"
|
||||
|
|
@ -362,7 +362,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid size={3}>
|
||||
<TextField
|
||||
style={{ width: "100%" }}
|
||||
margin="dense"
|
||||
|
|
@ -395,7 +395,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
)}
|
||||
{/* humidity slider */}
|
||||
{isMobile ? (
|
||||
<Grid item xs={12}>
|
||||
<Grid size={12}>
|
||||
<Box
|
||||
display="flex"
|
||||
alignContent="center"
|
||||
|
|
@ -437,10 +437,10 @@ export default function DevicePresetCard(props: Props) {
|
|||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={2}>
|
||||
<Grid size={2}>
|
||||
<Typography>Humidity</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={7}>
|
||||
<Grid size={7}>
|
||||
<Box paddingRight={2}>
|
||||
<Slider
|
||||
//orientation="vertical"
|
||||
|
|
@ -456,7 +456,7 @@ export default function DevicePresetCard(props: Props) {
|
|||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Grid size={3}>
|
||||
<TextField
|
||||
style={{ width: "100%" }}
|
||||
margin="dense"
|
||||
|
|
|
|||
|
|
@ -338,14 +338,12 @@ export default function GateDevice(props: Props) {
|
|||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
direction={(isMobile) ? "column" : "row"}
|
||||
width="100%"
|
||||
//alignItems="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}>
|
||||
<Box
|
||||
paddingLeft={1}
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
|
|
@ -364,20 +362,6 @@ export default function GateDevice(props: Props) {
|
|||
}
|
||||
]}
|
||||
/>
|
||||
{/* <ToggleButtonGroup value={detail} exclusive size="small" aria-label="detail">
|
||||
<ToggleButton
|
||||
onClick={() => setDetail("analytics")}
|
||||
value={"analytics"}
|
||||
aria-label="Analysis Graphs">
|
||||
Analysis
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
onClick={() => setDetail("sensors")}
|
||||
value={"sensors"}
|
||||
aria-label="Sensor Graphs">
|
||||
Sensors
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup> */}
|
||||
</Box>
|
||||
<Card raised>
|
||||
<GateSVG
|
||||
|
|
|
|||
|
|
@ -258,9 +258,9 @@ export default function GateFlowGraph(props: Props) {
|
|||
<React.Fragment>
|
||||
{flowEvents.length > 0 ? (
|
||||
<Grid container direction="row" spacing={2} className={classes.eventGrid}>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.eventCard}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Total Events:</Typography>
|
||||
|
|
@ -271,7 +271,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Total Time:</Typography>
|
||||
<Typography>Time:</Typography>
|
||||
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||
{moment.duration(totalTimeS, "s").humanize()}
|
||||
</Typography>
|
||||
|
|
@ -280,9 +280,9 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.eventCard}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Events Inside:</Typography>
|
||||
|
|
@ -293,7 +293,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Event Time:</Typography>
|
||||
<Typography>Time:</Typography>
|
||||
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||
{moment.duration(timeSInside, "s").humanize()}
|
||||
</Typography>
|
||||
|
|
@ -302,9 +302,9 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.eventCard}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Events Outside:</Typography>
|
||||
|
|
@ -315,7 +315,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
<Grid size={9}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Typography>Event Time:</Typography>
|
||||
<Typography>Time:</Typography>
|
||||
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||
{moment.duration(timeSOutside, "s").humanize()}
|
||||
</Typography>
|
||||
|
|
@ -324,15 +324,15 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.eventCard}>
|
||||
<Grid container direction="column" alignItems="center">
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid>
|
||||
<Typography>Performance:</Typography>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||
{(eventsInside / totalEvents) * 100}%
|
||||
{((eventsInside / totalEvents) * 100).toFixed(2)}%
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
@ -367,7 +367,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
<React.Fragment>
|
||||
{runtime && (
|
||||
<Grid container direction="row" spacing={2} className={classes.runtimeGrid}>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.calcCard}>
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid>Approximate Runtime:</Grid>
|
||||
|
|
@ -379,7 +379,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.calcCard}>
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid>Cost to run PCA:</Grid>
|
||||
|
|
@ -394,7 +394,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.calcCard}>
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid>Cost to run APU:</Grid>
|
||||
|
|
@ -409,7 +409,7 @@ export default function GateFlowGraph(props: Props) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Grid size={{xs: 6, md: 3}}>
|
||||
<Card raised className={classes.calcCard}>
|
||||
<Grid container width="100%" direction="column" alignItems="center">
|
||||
<Grid>Total Cost:</Grid>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
[
|
||||
pond.Grain.GRAIN_OATS,
|
||||
{
|
||||
name: "Oats",
|
||||
name: "Oats a",
|
||||
group: "Oats",
|
||||
equation: Equation.henderson,
|
||||
a: 0.000085511,
|
||||
|
|
@ -203,6 +203,40 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
}
|
||||
],
|
||||
[
|
||||
pond.Grain.GRAIN_OATS_B,
|
||||
{
|
||||
name: "Oats b",
|
||||
group: "Oats",
|
||||
equation: Equation.chungPfost,
|
||||
a: 442.85,
|
||||
b: 0.21228,
|
||||
c: 35.803,
|
||||
setTempC: 32.0,
|
||||
targetMC: 13.6,
|
||||
img: OatImg,
|
||||
colour: "#79955a",
|
||||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
}
|
||||
],
|
||||
[
|
||||
pond.Grain.GRAIN_OATS_C,
|
||||
{
|
||||
name: "Oats c",
|
||||
group: "Oats",
|
||||
equation: Equation.oswin,
|
||||
a: 12.412,
|
||||
b: -0.060707,
|
||||
c: 2.9397,
|
||||
setTempC: 32.0,
|
||||
targetMC: 13.6,
|
||||
img: OatImg,
|
||||
colour: "#79955a",
|
||||
weightConversionKg: 15.4222988297197,
|
||||
bushelsPerTonne: 64.842
|
||||
}
|
||||
],
|
||||
[
|
||||
pond.Grain.GRAIN_PEANUTS,
|
||||
|
|
@ -506,6 +540,66 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
|||
weightConversionKg: 27.2155,
|
||||
bushelsPerTonne: 36.744
|
||||
}
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_A,
|
||||
{
|
||||
name: "Rye a",
|
||||
group: "Rye",
|
||||
equation: Equation.henderson,
|
||||
a: 0.00006343,
|
||||
b: 2.2060,
|
||||
c: 13.1810,
|
||||
setTempC: defaultSetTemp,
|
||||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
}
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_B,
|
||||
{
|
||||
name: "Rye b",
|
||||
group: "Rye",
|
||||
equation: Equation.chungPfost,
|
||||
a: 461.0230,
|
||||
b: 0.1840,
|
||||
c: 36.7410,
|
||||
setTempC: defaultSetTemp,
|
||||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
},
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_C,
|
||||
{
|
||||
name: "Rye c",
|
||||
group: "Rye",
|
||||
equation: Equation.halsey,
|
||||
a: 4.2970,
|
||||
b: 0.380,
|
||||
c: 2.2710,
|
||||
setTempC: defaultSetTemp,
|
||||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
},
|
||||
],[
|
||||
pond.Grain.GRAIN_RYE_D,
|
||||
{
|
||||
name: "Rye d",
|
||||
group: "Rye",
|
||||
equation: Equation.oswin,
|
||||
a: 11.8870,
|
||||
b: 0.0210,
|
||||
c: 3.2620,
|
||||
setTempC: defaultSetTemp,
|
||||
targetMC: 15.0,
|
||||
colour: "grey",
|
||||
weightConversionKg: 25.4,
|
||||
bushelsPerTonne: 39.368
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
|
|
|
|||
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://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 Libra Cart 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>
|
||||
Libra Cart 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ export class Bin {
|
|||
}
|
||||
|
||||
public empty(): boolean {
|
||||
return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.settings.inventory.grainBushels < 5);
|
||||
return this.settings.inventory?.empty || (this.settings.inventory !== undefined && this.settings.inventory !== null && this.bushels() < 5);
|
||||
}
|
||||
|
||||
public objectType(): pond.ObjectType {
|
||||
|
|
@ -126,8 +126,10 @@ export class Bin {
|
|||
}
|
||||
|
||||
public bushels(): number {
|
||||
let control = this.settings.inventory?.inventoryControl;
|
||||
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
|
||||
}
|
||||
return bushels
|
||||
|
|
@ -137,7 +139,8 @@ export class Bin {
|
|||
let fill = 0;
|
||||
if (this.settings.inventory && this.settings.specs) {
|
||||
let bushels = this.settings.inventory.grainBushels
|
||||
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){
|
||||
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
|
||||
}
|
||||
fill = Math.round(
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export class Component {
|
|||
return 0;
|
||||
}
|
||||
|
||||
public addressDescription = (deviceProduct?: pond.DeviceProduct) => {
|
||||
public addressDescription(deviceProduct?: pond.DeviceProduct): string {
|
||||
if (
|
||||
this.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY ||
|
||||
(this.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 &&
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ export class GrainCable {
|
|||
let filtered: number[] = []
|
||||
values.forEach((val, index) => {
|
||||
//if the node is not excluded AND is either under the top node OR top node is not set
|
||||
if(!this.excludedNodes.includes(index) && (index <= this.topNode || this.topNode === 0)){
|
||||
if(!this.excludedNodes.includes(index) && (index < this.topNode || this.topNode === 0)){
|
||||
filtered.push(val)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const Transactions = lazy(() => import("pages/Transactions"));
|
|||
const BinCableEstimator = lazy(() => import("pages/BinCableEstimator"));
|
||||
const APIDocs = lazy(() => import("pages/APIDocs"));
|
||||
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 Firmware = lazy(() => import("pages/Firmware"));
|
||||
const Nfc = lazy(() => import("pages/Nfc"));
|
||||
|
|
@ -46,6 +46,7 @@ const Contracts = lazy(() => import("pages/Contracts"));
|
|||
const Contract = lazy(() => import("pages/Contract"));
|
||||
const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
||||
const CNHi = lazy(() => import("pages/CNHi"));
|
||||
const LibraCart = lazy(() => import("pages/LibraCart"));
|
||||
|
||||
export const appendToUrl = (appendage: number | string) => {
|
||||
const basePath = location.pathname.replace(/\/$/, "");
|
||||
|
|
@ -340,6 +341,9 @@ export default function Router() {
|
|||
{user.hasFeature("cnhi") &&
|
||||
<Route path="cnhi" element={<CNHi />} />
|
||||
}
|
||||
{user.hasFeature("libra-cart") &&
|
||||
<Route path="libracart" element={<LibraCart />} />
|
||||
}
|
||||
{/* Map routes */}
|
||||
<Route path="visualFarm" element={<FieldMap />} />
|
||||
<Route path="aviationMap" element={<AviationMap />} />
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import DataDuckIcon from "products/Bindapt/DataDuckIcon";
|
|||
import ContractsIcon from "products/CommonIcons/contractIcon";
|
||||
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
||||
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
||||
import LibraCartIcon from "products/CommonIcons/libracartIcon";
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
|
|
@ -69,19 +70,26 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
})
|
||||
},
|
||||
sideMenuOnClosed: {
|
||||
transition: theme.transitions.create(["width", "z-index", "opacity"], {
|
||||
transition: theme.transitions.create(["width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
width: theme.spacing(7),
|
||||
zIndex: theme.zIndex.drawer,
|
||||
opacity: 0,
|
||||
// overflowX: "hidden",
|
||||
width: theme.spacing(0),
|
||||
// zIndex: theme.zIndex.drawer,
|
||||
// opacity: 0,
|
||||
[theme.breakpoints.up("md")]: {
|
||||
width: theme.spacing(9),
|
||||
width: theme.spacing(9.25),
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
sideMenuOnClosedMobile: {
|
||||
transition: theme.transitions.create(["width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}),
|
||||
width: theme.spacing(0),
|
||||
},
|
||||
list: {
|
||||
paddingTop: 0
|
||||
},
|
||||
|
|
@ -436,7 +444,7 @@ export default function SideNavigator(props: Props) {
|
|||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{(isAg || isStreamline) &&
|
||||
{(isAg || isStreamline || user.hasFeature("admin")) &&
|
||||
<Tooltip title="Marketplace" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-marketplace"
|
||||
|
|
@ -477,6 +485,20 @@ export default function SideNavigator(props: Props) {
|
|||
{open && <ListItemText primary="Case New Holland" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{user.hasFeature("libra-cart") &&
|
||||
<Tooltip title="Libra Cart" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-libraCart"
|
||||
onClick={() => goTo("/libracart")}
|
||||
classes={getClasses("/libracart")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<LibraCartIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Libra Cart" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</List>
|
||||
)
|
||||
|
|
@ -489,7 +511,7 @@ export default function SideNavigator(props: Props) {
|
|||
classes={{
|
||||
paper: classNames(
|
||||
classes.sideMenu,
|
||||
open ? classes.sideMenuOpened : classes.sideMenuOnClosed
|
||||
open ? classes.sideMenuOpened : (isMobile ? classes.sideMenuOnClosedMobile : classes.sideMenuOnClosed)
|
||||
)
|
||||
}}
|
||||
anchor="left"
|
||||
|
|
|
|||
|
|
@ -810,7 +810,8 @@ export default function Bin(props: Props) {
|
|||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 2.6 }}>
|
||||
{(bin.settings.mode === pond.BinMode.BIN_MODE_DRYING ||
|
||||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && (
|
||||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING ||
|
||||
bin.settings.mode === pond.BinMode.BIN_MODE_COOLDOWN) && (
|
||||
<BinConditioningCard
|
||||
mode={bin.settings.mode}
|
||||
interactions={interactions}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</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
|
||||
className={classes.icon}
|
||||
onClick={event => {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import PageContainer from "./PageContainer";
|
|||
export default function CNHi() {
|
||||
const [currentOrg, setCurrentOrg] = useState("");
|
||||
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 [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
|
||||
const [{ as }] = useGlobalState();
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ export default function Devices() {
|
|||
]
|
||||
if (hasPlenums) {
|
||||
columns.push({
|
||||
title: "Plenum",
|
||||
title: "Temp/Humidity",
|
||||
// sortKey: "hi",
|
||||
// disableSort: true,
|
||||
render: (device: Device) => {
|
||||
|
|
|
|||
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="Libra Cart 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",
|
||||
"marketplace",
|
||||
"installer",
|
||||
"cnhi"
|
||||
"cnhi",
|
||||
"libra-cart"
|
||||
].sort();
|
||||
|
||||
const [rows, setRows] = useState<User[]>([])
|
||||
|
|
|
|||
|
|
@ -71,16 +71,19 @@ export function describePower(power?: pond.DevicePower | null): PowerDescriber {
|
|||
}
|
||||
} else {
|
||||
let thresholds = notChargingThresholds;
|
||||
let suffix = "not charging";
|
||||
if (power.inputVoltage >= 4) {
|
||||
thresholds = chargingThresholds;
|
||||
if (power.chargePercent === 100) {
|
||||
suffix = "charged";
|
||||
} else {
|
||||
suffix = "charging";
|
||||
}
|
||||
}
|
||||
result.description = power.chargePercent.toString() + "%, " + suffix;
|
||||
// we would not actually know if it is charging here since it is just looking at the voltage now and not comparing to previous voltage
|
||||
// let suffix = "not charging";
|
||||
// if (power.inputVoltage >= 4) {
|
||||
// thresholds = chargingThresholds;
|
||||
// if (power.chargePercent === 100) {
|
||||
// suffix = "charged";
|
||||
// } else {
|
||||
// suffix = "charging";
|
||||
// }
|
||||
// }
|
||||
// result.description = power.chargePercent.toString() + "%, " + suffix;
|
||||
// TODO: there is a plan in place to add a boolean value to power in the backend to have it check and set the boolean, we can then use that to know if it is charging or not
|
||||
result.description = power.chargePercent.toString() + "%"
|
||||
for (let i = 0; i < thresholds.length; i++) {
|
||||
if (power.chargePercent >= thresholds[i].threshold) {
|
||||
result.icon = thresholds[i].icon;
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map<
|
|||
// [quack.ComponentType.COMPONENT_TYPE_VAPOUR_PRESSURE_DEFICIT, [0x40]], //component type deprecated?
|
||||
[quack.ComponentType.COMPONENT_TYPE_LIDAR, [0x62]],
|
||||
[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]],
|
||||
|
|
|
|||
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()} />;
|
||||
}
|
||||
|
|
@ -45,7 +45,8 @@ export {
|
|||
useFileControllerAPI,
|
||||
useContractAPI, //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";
|
||||
// export { SecurityContext, useSecurity } from "./security";
|
||||
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||
|
|
|
|||
150
src/providers/pond/devicePresetAPI.tsx
Normal file
150
src/providers/pond/devicePresetAPI.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { useHTTP } from "hooks";
|
||||
import { createContext, PropsWithChildren, useContext } from "react";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { pondURL } from "./pond";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useGlobalState } from "providers";
|
||||
|
||||
export interface IDevicePresetInterface {
|
||||
//add
|
||||
addDevicePreset: (
|
||||
name: string,
|
||||
settings: pond.DevicePresetSettings,
|
||||
parentKey?: string,
|
||||
parentTypes?: string
|
||||
) => Promise<AxiosResponse<pond.AddDevicePresetResponse>>;
|
||||
//list
|
||||
listDevicePresets: (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order?: "asc" | "desc",
|
||||
orderBy?: string,
|
||||
search?: string,
|
||||
keys?: string[],
|
||||
types?: string[]
|
||||
) => Promise<AxiosResponse<pond.ListDevicePresetResponse>>;
|
||||
//update
|
||||
updateDevicePreset: (
|
||||
key: string,
|
||||
name: string,
|
||||
settings: pond.DevicePresetSettings
|
||||
) => Promise<AxiosResponse<pond.UpdateDevicePresetResponse>>;
|
||||
|
||||
//remove
|
||||
removeDevicePreset: (key: string) => Promise<AxiosResponse<pond.DeleteDevicePresetResponse>>;
|
||||
|
||||
//get
|
||||
getDevicePreset: (key: string) => Promise<AxiosResponse<pond.GetDevicePresetResponse>>;
|
||||
}
|
||||
|
||||
export const DevicePresetAPIcontext = createContext<IDevicePresetInterface>(
|
||||
{} as IDevicePresetInterface
|
||||
);
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function DevicePresetProvider(props: PropsWithChildren<Props>) {
|
||||
const { children } = props;
|
||||
const { get, del, post, put } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
//add
|
||||
const addDevicePreset = (
|
||||
name: string,
|
||||
settings: pond.DevicePresetSettings,
|
||||
parentKey?: string,
|
||||
parentType?: string
|
||||
) => {
|
||||
let key = "";
|
||||
let type = "";
|
||||
if (parentKey && parentType) {
|
||||
key = "&parentKey=" + parentKey;
|
||||
type = "&parentType=" + parentType;
|
||||
}
|
||||
if (as) {
|
||||
return post<pond.AddDevicePresetResponse>(
|
||||
pondURL("/devicePreset?name=" + name + "&as=" + as + key + type),
|
||||
settings
|
||||
);
|
||||
}
|
||||
return post<pond.AddDevicePresetResponse>(
|
||||
pondURL("/devicePreset?name=" + name + key + type),
|
||||
settings
|
||||
);
|
||||
};
|
||||
//list
|
||||
const listDevicePresets = (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order?: "asc" | "desc",
|
||||
orderBy?: string,
|
||||
search?: string,
|
||||
keys?: string[],
|
||||
types?: string[]
|
||||
) => {
|
||||
let asText = "";
|
||||
if (as) asText = "&as=" + as;
|
||||
return get<pond.ListDevicePresetResponse>(
|
||||
pondURL(
|
||||
"/devicePresets?limit=" +
|
||||
limit +
|
||||
"&offset=" +
|
||||
offset +
|
||||
("&order=" + (order ? order : "asc")) +
|
||||
("&by=" + (orderBy ? orderBy : "key")) +
|
||||
(search ? "&search=" + search : "") +
|
||||
(keys ? "&keys=" + keys.toString() : "") +
|
||||
(types ? "&types=" + types.toString() : "") +
|
||||
asText
|
||||
)
|
||||
);
|
||||
};
|
||||
//update
|
||||
const updateDevicePreset = (key: string, name: string, settings: pond.DevicePresetSettings) => {
|
||||
if (as) {
|
||||
return put<pond.UpdateDevicePresetResponse>(
|
||||
pondURL("/devicePreset/" + key + "?as=" + as + "&name=" + name),
|
||||
settings
|
||||
);
|
||||
}
|
||||
return put<pond.UpdateDevicePresetResponse>(
|
||||
pondURL("/devicePreset/" + key + "?name=" + name),
|
||||
settings
|
||||
);
|
||||
};
|
||||
|
||||
//remove
|
||||
const removeDevicePreset = (key: string) => {
|
||||
if (as) {
|
||||
return del<pond.DeleteDevicePresetResponse>(pondURL("/devicePreset/" + key + "?as=" + as));
|
||||
}
|
||||
return del<pond.DeleteDevicePresetResponse>(pondURL("/devicePreset/" + key + "?as=" + as));
|
||||
};
|
||||
|
||||
//get
|
||||
const getDevicePreset = (key: string) => {
|
||||
if (as) {
|
||||
return del<pond.GetDevicePresetResponse>(pondURL("/devicePreset/" + key + "?as=" + as));
|
||||
}
|
||||
return del<pond.GetDevicePresetResponse>(pondURL("/devicePreset/" + key + "?as=" + as));
|
||||
};
|
||||
return (
|
||||
<DevicePresetAPIcontext.Provider
|
||||
value={{
|
||||
//add
|
||||
addDevicePreset,
|
||||
//list
|
||||
listDevicePresets,
|
||||
//update
|
||||
updateDevicePreset,
|
||||
//remove
|
||||
removeDevicePreset,
|
||||
//get
|
||||
getDevicePreset
|
||||
}}>
|
||||
{children}
|
||||
</DevicePresetAPIcontext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useDevicePresetAPI = () => useContext(DevicePresetAPIcontext);
|
||||
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,7 +35,9 @@ import ContractProvider, { useContractAPI } from "./contractAPI";
|
|||
import UsageProvider, { useUsageAPI } from "./usageAPI";
|
||||
import KeyManagerProvider, { useKeyManagerAPI } from "./keyManagerAPI";
|
||||
import JohnDeereProvider, { useJohnDeereProxyAPI } from "./johnDeereProxyAPI";
|
||||
import LibraCartProvider, { useLibraCartProxyAPI } from "./libracartProxyAPI";
|
||||
import CNHiProvider, { useCNHiProxyAPI } from "./cnhiProxyAPI";
|
||||
import DevicePresetProvider, { useDevicePresetAPI } from "./devicePresetAPI";
|
||||
// import NoteProvider from "providers/noteAPI";
|
||||
|
||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||
|
|
@ -86,11 +88,15 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
|||
<ContractProvider>
|
||||
<JohnDeereProvider>
|
||||
<CNHiProvider>
|
||||
<UsageProvider>
|
||||
<KeyManagerProvider>
|
||||
{children}
|
||||
</KeyManagerProvider>
|
||||
</UsageProvider>
|
||||
<LibraCartProvider>
|
||||
<UsageProvider>
|
||||
<KeyManagerProvider>
|
||||
<DevicePresetProvider>
|
||||
{children}
|
||||
</DevicePresetProvider>
|
||||
</KeyManagerProvider>
|
||||
</UsageProvider>
|
||||
</LibraCartProvider>
|
||||
</CNHiProvider>
|
||||
</JohnDeereProvider>
|
||||
</ContractProvider>
|
||||
|
|
@ -163,5 +169,7 @@ export {
|
|||
useUsageAPI,
|
||||
useKeyManagerAPI,
|
||||
useJohnDeereProxyAPI,
|
||||
useCNHiProxyAPI
|
||||
useCNHiProxyAPI,
|
||||
useLibraCartProxyAPI,
|
||||
useDevicePresetAPI
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
Textsms as TextIcon,
|
||||
Contrast,
|
||||
} from "@mui/icons-material";
|
||||
import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
|
||||
import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, Slider, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import UserAvatar from "./UserAvatar";
|
||||
import { useGlobalState, useSnackbar, useUserAPI } from "providers";
|
||||
|
|
@ -31,6 +31,7 @@ import UnitsIcon from "@mui/icons-material/Category";
|
|||
import { setThemeType } from "theme";
|
||||
import { IsAdaptiveAgriculture } from "services/whiteLabel";
|
||||
import { setDistanceUnit, setGrainUnit, setPressureUnit, setTemperatureUnit } from "utils";
|
||||
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
|
|
@ -82,7 +83,9 @@ export default function UserSettings(props: Props) {
|
|||
const [avatarExpanded, setAvatarExpanded] = useState<boolean>(false);
|
||||
const [notificationsExpanded, setNotificationsExpanded] = useState<boolean>(false);
|
||||
const [unitsExpanded, setUnitsExpanded] = useState<boolean>(false);
|
||||
const [mapsExpanded, setMapsExpanded] = useState<boolean>(false);
|
||||
const [avatarUrl, setAvatarUrl] = useState<string>("");
|
||||
const [sliderVal, setSliderVal] = useState(globalState.user.settings.mapZoom);
|
||||
// const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -151,6 +154,30 @@ export default function UserSettings(props: Props) {
|
|||
setUser(updatedUser);
|
||||
};
|
||||
|
||||
const changeDefaultZoom = (value: number) => {
|
||||
setSliderVal(value);
|
||||
let updatedUser = User.clone(user);
|
||||
updatedUser.settings.mapZoom = value;
|
||||
setUser(updatedUser)
|
||||
};
|
||||
|
||||
const mapSettings = () => {
|
||||
return (
|
||||
<Box display="flex">
|
||||
<Typography>Map Zoom Level</Typography>
|
||||
<Slider
|
||||
valueLabelDisplay="auto"
|
||||
value={sliderVal}
|
||||
min={1}
|
||||
max={16}
|
||||
onChange={(_, val) => {
|
||||
changeDefaultZoom(val as number);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const generalSettings = () => {
|
||||
const { name, email, phoneNumber, timezone } = user.settings;
|
||||
|
||||
|
|
@ -631,15 +658,15 @@ export default function UserSettings(props: Props) {
|
|||
</List>
|
||||
</Collapse>
|
||||
|
||||
{/* <Divider />
|
||||
<Divider />
|
||||
|
||||
<ListItem button onClick={() => setMapsExpanded(!mapsExpanded)}>
|
||||
<ListItemButton onClick={() => setMapsExpanded(!mapsExpanded)}>
|
||||
<ListItemIcon>
|
||||
<FieldsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={"Map Settings"} />
|
||||
{mapsExpanded ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
</ListItemButton>
|
||||
<Collapse in={mapsExpanded} timeout="auto" unmountOnExit>
|
||||
<List component="div">
|
||||
<ListItem>
|
||||
|
|
@ -648,7 +675,7 @@ export default function UserSettings(props: Props) {
|
|||
</ListItemText>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Collapse> */}
|
||||
</Collapse>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useSnackbar, useUserAPI } from "hooks";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
|
||||
import CNHiIcon from "products/CommonIcons/cnhiIcon";
|
||||
import LibraCartIcon from "products/CommonIcons/libracartIcon";
|
||||
//import AgLogo from "assets/whitelabels/AdaptiveAgriculture/AGLogoSquare.png";
|
||||
import {
|
||||
IsAdaptiveAgriculture
|
||||
|
|
@ -60,7 +61,15 @@ const agFeatureList: ProductDetails[] = [
|
|||
"Integrate with the Case New Holland Industrial Center to bring your data into our platform"
|
||||
// bulletPoints: ["bullet one", "bullet two"],
|
||||
// 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"
|
||||
// }
|
||||
];
|
||||
|
||||
// const constructionFeatureList: ProductDetails[] = []
|
||||
|
|
@ -80,7 +89,7 @@ export default function Marketplace() {
|
|||
|
||||
useEffect(() => {
|
||||
let list: ProductDetails[] = [];
|
||||
if (IsAdaptiveAgriculture()) {
|
||||
if (IsAdaptiveAgriculture() || user.hasFeature("admin")) {
|
||||
list = list.concat(agFeatureList);
|
||||
}
|
||||
// if(IsAdCon()){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue