diff --git a/package-lock.json b/package-lock.json index fe947bc..ecde9fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "mui-tel-input": "^7.0.0", "notistack": "^3.0.1", "openweathermap-ts": "^1.2.10", - "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#field_dashboard", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10911,7 +10911,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#0cdf4d5e3038ef13318da0841886a7c9f0926f8d", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#37e19bde4218b369e028606c009729112668ccc6", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/app/App.tsx b/src/app/App.tsx index 16bb781..2c6b94b 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -50,7 +50,8 @@ function App() { const skipCallbacks = [ "/johndeere", - "/cnhi" + "/cnhi", + "/libracart" ] return ( diff --git a/src/assets/marketplaceImages/LibraCartGrey.png b/src/assets/marketplaceImages/LibraCartGrey.png new file mode 100644 index 0000000..331ac29 Binary files /dev/null and b/src/assets/marketplaceImages/LibraCartGrey.png differ diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index 5f4d93a..a425ea6 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -24,7 +24,7 @@ import RemoveSelfFromObject from "user/RemoveSelfFromObject"; import ShareObject from "user/ShareObject"; import { isOffline } from "utils/environment"; import ObjectTeams from "teams/ObjectTeams"; -// import BinDuplication from "./BinDuplication"; +import BinDuplication from "./BinDuplication"; import BinsIcon from "products/Bindapt/BinsIcon"; import HelpIcon from "@mui/icons-material/Help"; import BinSensors from "./BinSensors"; @@ -269,14 +269,14 @@ export default function BinActions(props: Props) { closeDialogCallback={() => setOpenState({ ...openState, users: false })} refreshCallback={refreshCallback} /> - {/* { setOpenState({ ...openState, duplication: false }); }} bin={bin} refreshCallback={refreshCallback} - /> */} + /> )} - {/* - - */} void; + refreshCallback: () => void; +} + +export default function BinDuplication(props: Props) { + const { open, bin, closeDialog, refreshCallback } = props; + const [binDupName, setBinDupName] = useState("(copy of) " + bin.name()); + const binAPI = useBinAPI(); + const { openSnack } = useSnackbar(); + + useEffect(() => { + setBinDupName("(copy of) " + bin.name()); + }, [bin]); + + const duplicate = () => { + let settings = bin.settings; + settings.name = binDupName; + binAPI + .addBin(settings) + .then(resp => { + openSnack("Successfully duplicated bin"); + }) + .catch(err => { + openSnack("Failed to duplicate bin"); + }); + refreshCallback(); + closeDialog(); + }; + + return ( + + Create Duplicate Bin + + + This will create a new bin with the same settings as {bin.name()}. + + setBinDupName(e.target.value)} + /> + + + + + + + ); +} diff --git a/src/bin/BinSettings.tsx b/src/bin/BinSettings.tsx index 5558230..f970b2b 100644 --- a/src/bin/BinSettings.tsx +++ b/src/bin/BinSettings.tsx @@ -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(""); - const [{ as }] = useGlobalState(); + const [{ user, as }] = useGlobalState(); const [grainDiff, setGrainDiff] = useState(0); const [isCustomInventory, setIsCustomInventory] = useState(false); const [grainUpdate, setGrainUpdate] = useState(false); @@ -197,10 +197,13 @@ export default function BinSettings(props: Props) { const [inventoryControl, setInventoryControl] = useState( pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN ); - const [storageType, setStorageType] = useState( pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN ); + //libracart stuff + const libracartAPI = useLibraCartProxyAPI() + const [lcDestination, setlcDestination] = useState } + {inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART && + + { + let newForm = form; + newForm.libracartDestinationKey = option?.value; + setForm(newForm); + setlcDestination(option ? option : null); + }} + disabled={!canEdit} + options={lcDestinationOptions} + /> + + The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours + + + } {empty ? ( diff --git a/src/bin/BinsList.tsx b/src/bin/BinsList.tsx index a9e517a..3ae7428 100644 --- a/src/bin/BinsList.tsx +++ b/src/bin/BinsList.tsx @@ -30,7 +30,6 @@ const useStyles = makeStyles((_theme) => { position: "relative", minHeight: "233px", height: "auto !important", - width: "184px", padding: 2 }, hidden: { @@ -113,7 +112,7 @@ export default function BinsList(props: Props) { return ( {bins.map((b, i) => - isMobile ? ( + ( - ) : ( - { - !duplicate && goToBin(i) - }}> - - ) )} diff --git a/src/bin/graphs/BinLevelAreaGraph.tsx b/src/bin/graphs/BinLevelAreaGraph.tsx new file mode 100644 index 0000000..fc13182 --- /dev/null +++ b/src/bin/graphs/BinLevelAreaGraph.tsx @@ -0,0 +1,67 @@ +import { useTheme } from "@mui/material"; +import MaterialChartTooltip from "charts/MaterialChartTooltip"; +import moment from "moment"; +import { Area, AreaChart, ResponsiveContainer, Tooltip, TooltipProps, XAxis, YAxis } from "recharts"; + +export interface LevelAreaData { + timestamp: number; + value: number; +} + +interface Props { + data: LevelAreaData[] + fill: string + customHeight?: string | number +} + +export default function BinLevelAreaGraph(props: Props) { + const { data, customHeight, fill } = props + const theme = useTheme(); + const now = moment(); + + + return ( + + + { + let t = moment(timestamp); + return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD"); + }} + scale="time" + type="number" + tick={{ fill: theme.palette.text.primary }} + stroke={theme.palette.divider} + interval="preserveStartEnd" + /> + + + moment(timestamp).format("lll")} + content={(props: TooltipProps) => { + return ( + { + return value + " Bushels"; + }} + /> + ); + }} + /> + + + ) +} \ No newline at end of file diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 8aabf93..d9c98cf 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -1,21 +1,18 @@ import { - Box, Card, CircularProgress, - FormControlLabel, - Grid, - Switch, Typography } from "@mui/material"; import { blue, grey, red, yellow, orange } from "@mui/material/colors"; -import BarGraph, { BarData, RefArea } from "charts/BarGraph"; +import BarGraph, { BarData } from "charts/BarGraph"; import { Bin } from "models"; import moment, { Moment } from "moment"; -import { pond, quack } from "protobuf-ts/pond"; +import { pond } from "protobuf-ts/pond"; import { useBinAPI, useGlobalState } from "providers"; import { useCallback, useEffect, useState } from "react"; import { Legend } from "recharts"; import { getGrainUnit } from "utils"; +import BinLevelAreaGraph from "./BinLevelAreaGraph"; interface Props { binLoading: boolean; @@ -27,12 +24,16 @@ interface Props { customHeight?: number | string; } +interface InventoryAt { + timestamp: number; + value: number; +} + export default function BinLevelOverTime(props: Props) { const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props; const binAPI = useBinAPI(); const [{as}] = useGlobalState(); - const [data, setData] = useState([]); - const [modeAreas, setModeAreas] = useState([]); + const [inventoryData, setInventoryData] = useState([]); const [dataLoading, setDataLoading] = useState(false); const [capacity, setCapacity] = useState(); @@ -76,33 +77,15 @@ export default function BinLevelOverTime(props: Props) { .listHistoryBetween(bin.key(), 500, startDate.toISOString(), endDate.toISOString()) .then(resp => { let data: BarData[] = []; - let modeAreas: RefArea[] = []; let lastBushels = -1; - - //values for ref areas - let x1 = 0; - let x2 = 0; - let y1 = -50; - let y2 = -200; - - let currentMode: pond.BinMode; - + let currentMode: pond.BinMode | undefined = undefined + let modeData: BarData[] = [] resp.data.history.forEach(hist => { + //build the data for the inventory bar graph if (hist.settings?.inventory) { - let settings = pond.BinSettings.fromObject(hist.settings); let bushels = hist.settings.inventory.grainBushels ?? 0; - if (bushels !== lastBushels || currentMode !== settings.mode) { - x1 = moment(hist.timestamp).valueOf(); - x2 = moment(hist.timestamp).valueOf(); - modeAreas.push({ - x1: x1, - x2: x2, - y1: cap ? cap * -0.1 : y1, - y2: cap ? cap * -0.2 : y2, - fill: getFill(settings.mode) - }); - currentMode = settings.mode; - let newData: BarData = { + if (bushels !== lastBushels) { + let newData: InventoryAt = { timestamp: moment(hist.timestamp).valueOf(), value: fertilizerBin ? Math.round(bushels * 35.239) @@ -114,10 +97,21 @@ export default function BinLevelOverTime(props: Props) { lastBushels = bushels; } } + //build the data for the mode change bar graph + let histBin = Bin.create() + histBin.settings = hist.settings ?? pond.BinSettings.create() + if(hist.settings && currentMode !== hist.settings.mode){ + currentMode = hist.settings.mode + modeData.push({ + timestamp: moment(hist.timestamp).valueOf(), + value: 1, + fill: getFill(currentMode) + }) + } }); + let currentTime = moment().valueOf(); if (data.length === 0) { - let bushels = bin.settings.inventory?.grainBushels ?? 0; - let currentTime = moment().valueOf(); + let bushels = bin.bushels(); data.push({ value: fertilizerBin ? Math.round(bushels * 35.239) @@ -126,17 +120,16 @@ export default function BinLevelOverTime(props: Props) { : bushels, timestamp: currentTime }); - - modeAreas.push({ - x1: currentTime, - x2: currentTime, - y1: cap ? cap * -0.1 : y1, - y2: cap ? cap * -0.2 : y2, - fill: getFill(bin.settings.mode) - }); } - setData(data); - setModeAreas(modeAreas); + if(modeData.length === 0){ + modeData.push({ + timestamp: currentTime, + value: 1, + fill: getFill(bin.settings.mode) + }) + } + setInventoryData(data); + setModeData(modeData) }) .finally(() => { setDataLoading(false); @@ -180,7 +173,7 @@ export default function BinLevelOverTime(props: Props) { }); }); if (autoBarData.length === 0) { - let bushels = bin.settings.inventory?.grainBushels ?? 0; + let bushels = bin.bushels(); let currentTime = moment().valueOf(); autoBarData.push({ value: fertilizerBin @@ -191,7 +184,7 @@ export default function BinLevelOverTime(props: Props) { timestamp: currentTime }); } - setData(autoBarData); + setInventoryData(autoBarData); }) .catch(err => {}) .finally(() => { @@ -220,6 +213,13 @@ export default function BinLevelOverTime(props: Props) { }) } }) + if(modeData.length === 0){ + modeData.push({ + timestamp: moment().valueOf(), + value: 1, + fill: getFill(bin.settings.mode) + }) + } setModeData(modeData) }) .catch(err => { @@ -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 ( - 10){ + return ( + + ) + }else{ + return ( + 0 ? legend() : undefined} labelColour="white" labels useGradient - /> - ); + /> + ); + } }; - const autoBinModeChart = () => { + const binModeChart = () => { return ( {dataLoading ? : inventoryChart()} - {/* when using auto will need to make a new chart just for the bin modes since the auto inventory comes from another place */} - {(bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR || - bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) && autoBinModeChart()} + {binModeChart()} ); } diff --git a/src/common/time/PeriodSelect.tsx b/src/common/time/PeriodSelect.tsx index 66e4f9c..d510ebc 100644 --- a/src/common/time/PeriodSelect.tsx +++ b/src/common/time/PeriodSelect.tsx @@ -28,6 +28,13 @@ export default function PeriodSelect(props: Props) { } }, [unit, prevUnit, onChange, value]); + useEffect(()=>{ + let initialUnit: TimeUnit = bestUnit(initialMs); + let initialValue = milliToX(initialMs, initialUnit).toString(); + setValue(initialValue) + setUnit(initialUnit) + },[initialMs]) + const changeValue = (event: any) => { let value = event.target.value; setValue(value); diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index 250d713..a7b8877 100644 --- a/src/component/ComponentSettings.tsx +++ b/src/component/ComponentSettings.tsx @@ -169,6 +169,7 @@ export default function ComponentSettings(props: Props) { const [removeDialogOpen, setRemoveDialogOpen] = useState(false); const [formComponent, setFormComponent] = useState(new Component()); const [cableID, setCableID] = useState(0); + const [expansionLine, setExpansionLine] = useState(0); const [overlayIndex, setOverlayIndex] = useState(0); const [overlayColourDialog, setOverlayCoulourDialog] = useState(false); const [tabVal, setTabVal] = useState(0); @@ -223,6 +224,9 @@ export default function ComponentSettings(props: Props) { const isFormValid = () => { //let { component } = form; let type = or(formComponent.settings.type, ""); + if(supportsExpansion() && expansionLine === 0){ + return false + } return isComponentTypeValid(type) && isAddressValid(formComponent); }; @@ -306,6 +310,7 @@ export default function ComponentSettings(props: Props) { const component = formComponent; //component.settings.calibrationCoefficient = Number(form.coefficient); if (cableID > 0) component.settings.addressType = cableID + 8; + component.settings.expansionLine = expansionLine componentAPI .add(device.id(), component.settings, as) .then((_response: any) => { @@ -373,10 +378,10 @@ export default function ComponentSettings(props: Props) { let ext = extension(type, subtype); formComponent.settings.type = type; formComponent.settings.subtype = subtype; - formComponent.settings.addressType = getAddressTypes( + formComponent.settings.addressType = or(addressTypeRestriction, getAddressTypes( formComponent.settings.type, formComponent.settings.subtype - )[0]; + )[0]); formComponent.settings.address = or( getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0], Component.create().settings.address @@ -399,11 +404,19 @@ export default function ComponentSettings(props: Props) { }; const handlePositionChanged = (event: any) => { + setCableID(0) + setExpansionLine(0) let f = cloneDeep(formComponent); - f.settings.address = event.target.value - .toString() - .split(":") - .pop(); + //split the string + let split: string[] = event.target.value.toString().split(":") + // pop the addres off of the end of the array + f.settings.address = parseInt(split.pop() ?? "0") + // pop the address type out of the array next + f.settings.addressType = parseInt(split.pop() ?? "0") + // f.settings.address = event.target.value + // .toString() + // .split(":") + // .pop(); setFormComponent(f); }; @@ -490,6 +503,21 @@ export default function ComponentSettings(props: Props) { ); }; + const isCableComponent = () => { + return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || + formComponent.settings.type === + quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || + formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE || + (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE && + formComponent.subType() === + quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG)) + } + + const supportsExpansion = () => { + return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE && + formComponent.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) + } + const addForm = () => { return ( @@ -543,14 +571,8 @@ export default function ComponentSettings(props: Props) { formComponent.settings.subtype )} - {(formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE || - formComponent.settings.type === - quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE || - formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE || - (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE && - formComponent.subType() === - quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG)) && - setComponentAddrType()} + {supportsExpansion() && setExpLine()} + {(isCableComponent() && !supportsExpansion())&& setComponentAddrType()} ) : activeStep === 1 ? ( ); }; + + const setExpLine = () => { + return ( + 12} + fullWidth + variant="outlined" + value={expansionLine} + helperText="Enter line the cable is connected to on your expander" + onChange={e => { + let number = parseInt(e.target.value); + if (number < 0 || isNaN(number)) number = 0; + if (number > 12) number = 12; + setExpansionLine(number); + }} + /> + ); + }; + const setComponentAddrType = () => { return ( { let currentAvailability = availability; + console.log(port) switch (port.addressType) { case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY: currentAvailability.set(port.addressType, [{ address: port.address, label: port.label }]); + //since we now have a component type that can be pins or i2c need to remove i2c options if they selected a pin port + currentAvailability.set(quack.AddressType.ADDRESS_TYPE_I2C, new Map()); + break; + case quack.AddressType.ADDRESS_TYPE_I2C: + //since we now have a component type that can be pins or i2c need to remove pin options if they selected the i2c port + currentAvailability.set(quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, []); + break; } return currentAvailability; }; diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 2f5efdd..5e91dd0 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -18,17 +18,20 @@ interface Props { availableOffsets: OffsetAvailabilityMap refreshCallback: () => void } - + interface CompStep { label: string; completed?: boolean; } +let i2cBlacklistAddresses: number[] = [0x70] + export default function DeviceScannedComponents(props: Props){ const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props const compAPI = useComponentAPI(); const deviceAPI = useDeviceAPI() - const [scannedI2C, setScannedI2C] = useState() + const [scannedI2C, setScannedI2C] = useState() //the unmodified scan of addresses + const [validCompAddresses, setValidComponentAddresses] = useState([]) //the filtered array of address data const [components, setComponents] = useState([]) const [steps, setSteps] = useState([]); const { error, success } = useSnackbar(); @@ -59,6 +62,19 @@ export default function DeviceScannedComponents(props: Props){ setSteps(steps) },[components]) + useEffect(()=>{ + let valid: quack.AddressData[] = [] + //filter the address data + scannedI2C?.settings?.foundAddresses.forEach(addrData => { + if(!i2cBlacklistAddresses.includes(addrData.address)){ + if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){ + valid.push(addrData) + } + } + }) + setValidComponentAddresses(valid) + },[scannedI2C]) + const stepper = () => { return ( @@ -205,9 +221,9 @@ export default function DeviceScannedComponents(props: Props){ - {scannedI2C.settings?.foundAddresses && scannedI2C.settings.foundAddresses.length > 0 ? scannedI2C?.settings?.foundAddresses.map((addr, index) => { + {validCompAddresses.length > 0 ? validCompAddresses.map((addr, index) => { return ( - + ) }) : void availablePositions: DevicePositions @@ -16,7 +16,7 @@ interface Props { } export default function ScannedI2C(props: Props){ - const {decimalAddress, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props + const {addressData, deviceProduct, componentSelectionCallback, availablePositions, sensorNum} = props const [types, setTypes] = useState([]) const [subtypeOptions, setSubtypeOptions] = useState>([]) const [selectedPrimaryType, setSelectedPrimaryType] = useState(quack.ComponentType.COMPONENT_TYPE_INVALID) @@ -34,7 +34,7 @@ export default function ScannedI2C(props: Props){ if(i2cMap){ i2cMap.forEach((val, key) => { let addresses = val as number[] ?? [] - if(addresses.length > 0 && addresses.includes(decimalAddress)){ + if(addresses.length > 0 && addresses.includes(addressData.address)){ types.push(key) } }) @@ -47,7 +47,7 @@ export default function ScannedI2C(props: Props){ checkAddressAvailable(types[0]) } setTypes(types) - },[decimalAddress, deviceProduct]) + },[addressData, deviceProduct]) const buildSubtypeOptions = (compType: quack.ComponentType) => { let subtypes = getSubtypes(compType) @@ -69,7 +69,7 @@ export default function ScannedI2C(props: Props){ const checkAddressAvailable = (compType: quack.ComponentType) => { let i2cMap = availablePositions as ComponentAvailabilityMap let compAddresses = i2cMap.get(compType) ?? [] - if (!compAddresses.includes(decimalAddress)){ + if (!compAddresses.includes(addressData.address)){ setAddressInUse(true) }else{ setAddressInUse(false) @@ -86,7 +86,10 @@ export default function ScannedI2C(props: Props){ primaryComponent.settings.type = selectedPrimaryType primaryComponent.settings.subtype = selectedPrimarySubtype primaryComponent.settings.addressType = quack.AddressType.ADDRESS_TYPE_I2C - primaryComponent.settings.address = decimalAddress + primaryComponent.settings.address = addressData.address + primaryComponent.settings.expansionLine = addressData.expansionLine + primaryComponent.settings.muxLine = addressData.muxLine + primaryComponent.settings.name = getFriendlyName(selectedPrimaryType, selectedPrimarySubtype) toAdd.push(primaryComponent) @@ -161,9 +164,6 @@ export default function ScannedI2C(props: Props){ - { - - } : Sensor Not Supported By Product diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx new file mode 100644 index 0000000..eb39433 --- /dev/null +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -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([]); + const [primaryUser, setPrimaryUser] = useState(""); + const [libracartUsername, setLibraCartUserName] = useState(""); + const [libracartCode, setLibraCartCode] = useState(""); + const [libracartAuth, setLibraCartAuth] = useState(""); + //const [activeStep, setActiveStep] = useState(0); + const userAPI = useUserAPI(); + const libracartAPI = useLibraCartProxyAPI(); + //const [dataOps, setDataOps] = useState([]); + 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 ( + + setLibraCartUserName(e.target.value)} + /> + + setPrimaryUser(e.target.value)}> + {teamUsers.map(u => ( + + + + + + + {u.name()} + + + + ))} + + + + + ); + }; + + const newOrgDialog = () => { + return ( + { + setOpenDialog(false); + }}> + Enter New Integration Link + {general()} + + + + + + ); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + LibraCart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform. + + + + {newOrgDialog()} + + ); +} diff --git a/src/models/Bin.ts b/src/models/Bin.ts index c66d8f9..603a9ce 100644 --- a/src/models/Bin.ts +++ b/src/models/Bin.ts @@ -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( diff --git a/src/models/Component.ts b/src/models/Component.ts index 84f5574..473d85d 100644 --- a/src/models/Component.ts +++ b/src/models/Component.ts @@ -67,18 +67,25 @@ export class Component { return quack.ComponentID.fromObject({ type: this.settings.type, addressType: this.settings.addressType, - address: this.settings.address + address: this.settings.address, + expansionLine: this.settings.expansionLine, + muxLine: this.settings.muxLine }); } public locationString(): string { - return ( - or(this.settings.type, 0).toString() + + let compositeLocation = or(this.settings.type, 0).toString() + "-" + or(this.settings.addressType, 0).toString() + "-" + or(this.settings.address, 0).toString() - ); + if(this.settings.expansionLine){ + compositeLocation = compositeLocation + "-" + this.settings.expansionLine + } + if(this.settings.muxLine){ + compositeLocation = compositeLocation + ":" + this.settings.muxLine + } + return compositeLocation; } public type(): quack.ComponentType { @@ -107,7 +114,7 @@ export class Component { return 0; } - public addressDescription = (deviceProduct?: pond.DeviceProduct) => { + public addressDescription(deviceProduct?: pond.DeviceProduct): string { if ( this.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY || (this.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 && diff --git a/src/models/Device.ts b/src/models/Device.ts index e45f7aa..46ce943 100644 --- a/src/models/Device.ts +++ b/src/models/Device.ts @@ -42,11 +42,11 @@ const featureVersions: Map = new Map([ v2Cell: "N/A", v2WifiS3: "N/A", v2CellS3: "N/A", - v2CellBlack: "N/A", + v2CellBlack: "2.1.6", v2CellGreen: "N/A", - v2WifiBlue: "N/A", - v2CellBlue: "N/A", - v2EthBlue: "N/A" + v2WifiBlue: "2.1.6", + v2CellBlue: "2.1.6", + v2EthBlue: "2.1.6" } ] ]); diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index f6badb1..afe12c5 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -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") && } /> } + {user.hasFeature("libra-cart") && + } /> + } {/* Map routes */} } /> } /> diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index f71ca9c..ade8daa 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -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; @@ -436,7 +437,7 @@ export default function SideNavigator(props: Props) { } - {(isAg || isStreamline) && + {(isAg || isStreamline || user.hasFeature("admin")) && } + } + {user.hasFeature("libra-cart") && + + goTo("/libracart")} + classes={getClasses("/libracart")} + > + + + + {open && } + + } ) diff --git a/src/pages/Bins.tsx b/src/pages/Bins.tsx index 477aa05..9145a6a 100644 --- a/src/pages/Bins.tsx +++ b/src/pages/Bins.tsx @@ -1214,37 +1214,6 @@ export default function Bins(props: Props) { } ]} /> - {/* - { - setCardValDisplay("low"); - }}> - Low - - { - setCardValDisplay("average"); - }}> - Average - - { - setCardValDisplay("high"); - }}> - High - - */} @@ -1270,41 +1239,6 @@ export default function Bins(props: Props) { } ]} /> - {/* - { - setBinView("grid"); - sessionStorage.setItem("binsView", "grid"); - }}> - - */} - {/* hidden at dustins request so that grid view and list are the only two */} - {/* { - setBinView("scroll"); - sessionStorage.setItem("binsView", "scroll"); - }}> - - */} - {/* { - setBinView("list"); - sessionStorage.setItem("binsView", "list"); - }}> - - - */} { diff --git a/src/pages/CNHi.tsx b/src/pages/CNHi.tsx index 24be24c..260d614 100644 --- a/src/pages/CNHi.tsx +++ b/src/pages/CNHi.tsx @@ -20,7 +20,7 @@ import PageContainer from "./PageContainer"; export default function CNHi() { const [currentOrg, setCurrentOrg] = useState(""); const cnhiAPI = useCNHiProxyAPI(); - const [organizations, setOrganizations] = useState>(new Map()); + const [organizations, setOrganizations] = useState>(new Map()); const [fieldAccordion, setFieldAccordion] = useState(false); const [dataOptions, setDataOptions] = useState([]); const [{ as }] = useGlobalState(); diff --git a/src/pages/LibraCart.tsx b/src/pages/LibraCart.tsx new file mode 100644 index 0000000..4e93012 --- /dev/null +++ b/src/pages/LibraCart.tsx @@ -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>(new Map()); + const [dataOptions, setDataOptions] = useState([]); + const [{ as }] = useGlobalState(); + + //load organizations for the user + useEffect(() => { + setCurrentOrg(""); + libracartAPI + .listAccounts(0, 0, as) + .then(resp => { + let tempOrgs: Map = 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 ( + + + + { + //setFields(!fields); + updateOrgData(checked, pond.DataOption.DATA_OPTION_DESTINATIONS); + }} + /> + } + /> + + + Import your destinations for the option to link it to a bin + + + + ); + }; + return ( + + + + + + + + + + + {fieldOptions()} + + ); +} diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx index 296c60a..45dd476 100644 --- a/src/pages/Users.tsx +++ b/src/pages/Users.tsx @@ -128,7 +128,8 @@ export default function Users() { "developer", "marketplace", "installer", - "cnhi" + "cnhi", + "libracart" ].sort(); const [rows, setRows] = useState([]) diff --git a/src/pbHelpers/AddressTypes/I2C.ts b/src/pbHelpers/AddressTypes/I2C.ts index 3c15ced..b6bf585 100644 --- a/src/pbHelpers/AddressTypes/I2C.ts +++ b/src/pbHelpers/AddressTypes/I2C.ts @@ -16,7 +16,9 @@ export const I2C: AddressTypeExtension = { [quack.ComponentType.COMPONENT_TYPE_CAPACITANCE, 0x4f], [quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE, 0x4f], [quack.ComponentType.COMPONENT_TYPE_SEN5X, 0x68], - [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b], + [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, 0x70], + [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, 0x6c] ]); const offset = offsets.get(componentType); diff --git a/src/pbHelpers/Bin.ts b/src/pbHelpers/Bin.ts index 1d16928..7c868e0 100644 --- a/src/pbHelpers/Bin.ts +++ b/src/pbHelpers/Bin.ts @@ -16,6 +16,7 @@ export const GetBinShapeDescribers = (): BinShapeDescriber[] => { ]; }; +// DEPRECATED -- the bon now knows its components from the relative object table export const HasGrainCable = (bin?: pond.BinSettings): boolean => { if (bin && bin.deviceComponents) { bin.deviceComponents.forEach(dc => { diff --git a/src/pbHelpers/Component.ts b/src/pbHelpers/Component.ts index e5d0df1..9329878 100644 --- a/src/pbHelpers/Component.ts +++ b/src/pbHelpers/Component.ts @@ -23,7 +23,9 @@ export function getComponentIDString(component?: Component): string { ? componentIDToString({ type: component.settings.type, addressType: component.settings.addressType, - address: component.settings.address + address: component.settings.address, + expansionLine: component.settings.expansionLine, + muxLine: component.settings.muxLine }) : "0-0-0"; } @@ -40,13 +42,18 @@ export function componentIDToString(componentID?: quack.IComponentID | null): st if (!componentID) { return "0-0-0"; } - return ( - or(componentID.type, 0).toString() + + let compositeLocation = or(componentID.type, 0).toString() + "-" + or(componentID.addressType, 0).toString() + "-" + or(componentID.address, 0).toString() - ); + if(componentID.expansionLine){ + compositeLocation = compositeLocation + "-" + componentID.expansionLine + } + if(componentID.muxLine){ + compositeLocation = compositeLocation + ":" + componentID.muxLine + } + return compositeLocation; } export function stringToComponentId(componentIDString: string): quack.ComponentID { diff --git a/src/pbHelpers/ComponentTypes/DragerGasDongle.ts b/src/pbHelpers/ComponentTypes/DragerGasDongle.ts index d0d79f8..bec3b1e 100644 --- a/src/pbHelpers/ComponentTypes/DragerGasDongle.ts +++ b/src/pbHelpers/ComponentTypes/DragerGasDongle.ts @@ -48,16 +48,16 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension { return { type: quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, subtypes: [ - { - key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_NONE, - value: "DRAGER_GAS_DONGLE_SUBTYPE_NONE", - friendlyName: "Drager Gas Chain" - }, - { - key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_DEBUG_VOLTAGE, - value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED", - friendlyName: "Drager Gas Chain - Voltage Debug" - }, + // { + // key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_NONE, + // value: "DRAGER_GAS_DONGLE_SUBTYPE_NONE", + // friendlyName: "Drager Gas Chain" + // }, + // { + // key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_DEBUG_VOLTAGE, + // value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED", + // friendlyName: "Drager Gas Chain - Voltage Debug" + // }, // { // key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_CO_CO2_NO2_O2, // value: "DRAGER_GAS_DONGLE_SUBTYPE_ADVANCED", @@ -100,7 +100,7 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension { isSource: true, isArray: true, isCalibratable: false, - addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY], + addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C], interactionResultTypes: [], states: [], //this is apparently used by the interactions to determine what the possible options for measurement type are diff --git a/src/pbHelpers/ComponentTypes/GrainCable.ts b/src/pbHelpers/ComponentTypes/GrainCable.ts index 5f556a0..c715611 100644 --- a/src/pbHelpers/ComponentTypes/GrainCable.ts +++ b/src/pbHelpers/ComponentTypes/GrainCable.ts @@ -176,7 +176,7 @@ export function GrainCable(subtype: number = 0): ComponentTypeExtension { isSource: true, isArray: true, isCalibratable: false, - addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY], + addressTypes: [quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, quack.AddressType.ADDRESS_TYPE_I2C], interactionResultTypes: [], states: [], measurements: [ diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index 3d09a7f..12aadb7 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -60,7 +60,9 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map 0) addressType = addressType - (7 + offset); @@ -127,6 +130,9 @@ export class DeviceAvailability { break; case quack.AddressType.ADDRESS_TYPE_I2C: case quack.AddressType.ADDRESS_TYPE_SPI: + if(expansionLine){ + break; + } let addressTypePositions = cloneDeep( this.availability.get(addressType) as ComponentAvailabilityMap ); @@ -171,7 +177,8 @@ export function FindAvailablePositions( available.ClaimAddress( component.settings.addressType, component.settings.type, - or(component.settings.address, 0) + or(component.settings.address, 0), + component.settings.expansionLine ); } }); diff --git a/src/products/Bindapt/BindaptAvailability.ts b/src/products/Bindapt/BindaptAvailability.ts index 3f70a11..50575d8 100644 --- a/src/products/Bindapt/BindaptAvailability.ts +++ b/src/products/Bindapt/BindaptAvailability.ts @@ -246,12 +246,13 @@ export const BindaptV2MonitorAvailability: DeviceAvailabilityMap = new Map< [ quack.AddressType.ADDRESS_TYPE_I2C, new Map([ - //[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], + [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], // [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]], @@ -278,12 +279,13 @@ export const BindaptV2AutomateAvailability: DeviceAvailabilityMap = new Map< [ quack.AddressType.ADDRESS_TYPE_I2C, new Map([ - //[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], + [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], // [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]], diff --git a/src/products/CommonIcons/libracartIcon.tsx b/src/products/CommonIcons/libracartIcon.tsx new file mode 100644 index 0000000..c71c55a --- /dev/null +++ b/src/products/CommonIcons/libracartIcon.tsx @@ -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 ; +} \ No newline at end of file diff --git a/src/products/MiVent/MiVentAvailability.ts b/src/products/MiVent/MiVentAvailability.ts index fed201c..0108806 100644 --- a/src/products/MiVent/MiVentAvailability.ts +++ b/src/products/MiVent/MiVentAvailability.ts @@ -52,7 +52,9 @@ export const MiVentV2Availability: DeviceAvailabilityMap = new Map< [quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]], [quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]], [quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]], - [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x6c]], + [quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE, [0x6d]] + ]) ], [quack.AddressType.ADDRESS_TYPE_POWER, [0]], diff --git a/src/providers/index.ts b/src/providers/index.ts index 9689df0..cede030 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -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"; diff --git a/src/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx new file mode 100644 index 0000000..b56f65b --- /dev/null +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -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>; + //list organizations + listAccounts: ( + limit: number, + offset: number, + as?: string, + keys?: string[], + types?: string[] + ) => Promise>; + updateAccount: ( + key: string, + options: pond.DataOption[], + as?: string + ) => Promise>; + listDestinations: ( + limit: number, + offset: number, + libracartKey?: string, + as?: string + ) => Promise>; + syncData: () => Promise +} + +export const LibraCartProxyAPIContext = createContext( + {} as ILibraCartProxyAPIContext +); + +interface Props {} + +export default function LibraCartProvider(props: PropsWithChildren) { + 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( + 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( + 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( + pondURL( + "/libracartAccounts/" + key + "?options=" + options.toString() + (as ? "&as=" + as : "") + ) + ); + }; + + const listDestinations = (limit: number, offset: number, libracartKey?: string, as?: string) => { + return get( + pondURL( + "/libracartDestinations?limit" + limit + "&offset=" + offset + (libracartKey ? "&libracartKey=" + libracartKey : "") + (as ? "&as=" + as : "") + ) + ) + } + + const syncData = () => { + return get(pondURL("/libracartImport" + (as ? "?as=" + as : ""))) + } + + return ( + + {children} + + ); +} + +export const useLibraCartProxyAPI = () => useContext(LibraCartProxyAPIContext); diff --git a/src/providers/pond/pond.tsx b/src/providers/pond/pond.tsx index 6402ae4..0c22353 100644 --- a/src/providers/pond/pond.tsx +++ b/src/providers/pond/pond.tsx @@ -35,6 +35,7 @@ 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"; @@ -87,13 +88,15 @@ export default function PondProvider(props: PropsWithChildren) { - - - - {children} - - - + + + + + {children} + + + + @@ -167,5 +170,6 @@ export { useKeyManagerAPI, useJohnDeereProxyAPI, useCNHiProxyAPI, + useLibraCartProxyAPI, useDevicePresetAPI }; diff --git a/src/userFeatures/UserFeatures.tsx b/src/userFeatures/UserFeatures.tsx index 9f9a2f4..c61cebc 100644 --- a/src/userFeatures/UserFeatures.tsx +++ b/src/userFeatures/UserFeatures.tsx @@ -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,6 +61,14 @@ 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: , + featureCost: 0, + cardImage: FeatureImageTest, + featureTitle: "Libra Cart", + longDescription: "Integrate with Libra Cart to bring your data into our platform" } ]; @@ -80,7 +89,7 @@ export default function Marketplace() { useEffect(() => { let list: ProductDetails[] = []; - if (IsAdaptiveAgriculture()) { + if (IsAdaptiveAgriculture() || user.hasFeature("admin")) { list = list.concat(agFeatureList); } // if(IsAdCon()){