From 3325c9d75b99cf7d70df2ea4e17c518f86bb585d Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 11 Jul 2025 13:56:17 -0600 Subject: [PATCH 01/35] implemented the epansion line stuff made adjustments to to the grain cable by adding i2c as an option as long as you set and expander line, also ajusted DeviceAvailability so that an i2c address will not be claimed if the component has an expansion line set, updated the device wizard to removed pin options if the i2c port was selected and vice versa for pins since we now have a components that supports both types, updated the components setting to set the default address type based on the restriction passed in that is set by the wizard, and fall back to what is was before if one is not passed in --- package-lock.json | 4 +- package.json | 2 +- src/component/ComponentSettings.tsx | 74 ++++++++++++++++----- src/device/DeviceWizard.tsx | 9 ++- src/models/Component.ts | 15 +++-- src/pbHelpers/AddressTypes/I2C.ts | 3 +- src/pbHelpers/Bin.ts | 1 + src/pbHelpers/Component.ts | 15 +++-- src/pbHelpers/ComponentTypes/GrainCable.ts | 2 +- src/pbHelpers/DeviceAvailability.ts | 17 ++++- src/products/Bindapt/BindaptAvailability.ts | 7 +- 11 files changed, 113 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d88f79..46b99c0 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#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#cable_expander", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10889,7 +10889,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#4523ffef42032eb4a923bd50d4cfcccbb1397dbd", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#eff6e4e51be080fdee9503d74ee66a0504436545", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index af322fc..28bd178 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#cable_expander", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/component/ComponentSettings.tsx b/src/component/ComponentSettings.tsx index 54653a2..01ee66d 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); @@ -222,6 +223,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); }; @@ -238,7 +242,6 @@ export default function ComponentSettings(props: Props) { componentType: quack.ComponentType ): number[] => { let positions = availablePositions.get(addressType); - console.log(positions) if (!positions) return []; switch (addressType) { case quack.AddressType.ADDRESS_TYPE_I2C: @@ -306,6 +309,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) => { @@ -372,10 +376,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 @@ -398,11 +402,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); }; @@ -413,7 +425,6 @@ export default function ComponentSettings(props: Props) { for (let i = 0; i < addressTypes.length; i++) { let addressType = addressTypes[i]; let availablePositions = getAvailablePositions(addressType, type); - console.log(availablePositions) availableMenuItemPositions.push( @@ -490,6 +501,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 +569,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/models/Component.ts b/src/models/Component.ts index 84f5574..9b96599 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 { diff --git a/src/pbHelpers/AddressTypes/I2C.ts b/src/pbHelpers/AddressTypes/I2C.ts index c17c743..339ce70 100644 --- a/src/pbHelpers/AddressTypes/I2C.ts +++ b/src/pbHelpers/AddressTypes/I2C.ts @@ -16,7 +16,8 @@ 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, 0x29] + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x29], + [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, 0x70] ]); 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/GrainCable.ts b/src/pbHelpers/ComponentTypes/GrainCable.ts index 4ddfd21..5369000 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 c50a2a4..0d57698 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -11,6 +11,11 @@ export type DevicePositions = number[] | ComponentAvailabilityMap | Configurable export type DeviceAvailabilityMap = Map; export type OffsetAvailabilityMap = Map; +//component types that can be connected to an expander in the I2C port +const ExpansionComponentTypes: quack.ComponentType[] = [ + quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE +] + const DefaultPinArray: ConfigurablePin[] = [ { address: 1, label: "1" }, { address: 2, label: "2" }, @@ -60,7 +65,8 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map 0) addressType = addressType - (7 + offset); @@ -127,6 +134,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 +181,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..be18f38 100644 --- a/src/products/Bindapt/BindaptAvailability.ts +++ b/src/products/Bindapt/BindaptAvailability.ts @@ -246,7 +246,7 @@ 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]], @@ -278,12 +278,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]], From 8b69632be40d90e794e07c25452d37e96426b1b8 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 11 Jul 2025 13:56:39 -0600 Subject: [PATCH 02/35] removed the unused array --- src/pbHelpers/DeviceAvailability.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index 0d57698..72bbbdc 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -11,11 +11,6 @@ export type DevicePositions = number[] | ComponentAvailabilityMap | Configurable export type DeviceAvailabilityMap = Map; export type OffsetAvailabilityMap = Map; -//component types that can be connected to an expander in the I2C port -const ExpansionComponentTypes: quack.ComponentType[] = [ - quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE -] - const DefaultPinArray: ConfigurablePin[] = [ { address: 1, label: "1" }, { address: 2, label: "2" }, From cb5e2a6ba4e9c451c46b03a2eed9091af0c737e1 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 11 Jul 2025 14:12:08 -0600 Subject: [PATCH 03/35] proto update --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46b99c0..437e5d5 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#cable_expander", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10889,7 +10889,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#eff6e4e51be080fdee9503d74ee66a0504436545", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#801fdcc314a081c283564e1ac551f8b012c0d59c", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 28bd178..ed3648f 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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#cable_expander", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", From f7266355515c74d179ff161faffa6532ab2cf6d9 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 11 Jul 2025 15:18:07 -0600 Subject: [PATCH 04/35] forgot to add the address as an option to the monitor --- src/products/Bindapt/BindaptAvailability.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/products/Bindapt/BindaptAvailability.ts b/src/products/Bindapt/BindaptAvailability.ts index be18f38..50575d8 100644 --- a/src/products/Bindapt/BindaptAvailability.ts +++ b/src/products/Bindapt/BindaptAvailability.ts @@ -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]], From 4cfbe9f1c6a89eca04e9c056e37c8722aae3f5f7 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 14 Jul 2025 13:25:54 -0600 Subject: [PATCH 05/35] updated the proto with the new definition for the i2c scan stuff, and updated the react components accordingly --- package-lock.json | 4 ++-- package.json | 2 +- .../autoDetect/DeviceScannedComponents.tsx | 2 +- src/device/autoDetect/ScannedI2C.tsx | 18 +++++++++--------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46b99c0..2c26833 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#cable_expander", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#i2c_detect", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10889,7 +10889,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#eff6e4e51be080fdee9503d74ee66a0504436545", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#955871c0a855e8da5dd10b8d72a1ccc5e391d218", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 28bd178..99939e6 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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#cable_expander", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#i2c_detect", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 2f5efdd..03a847e 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -207,7 +207,7 @@ export default function DeviceScannedComponents(props: Props){ {scannedI2C.settings?.foundAddresses && scannedI2C.settings.foundAddresses.length > 0 ? scannedI2C?.settings?.foundAddresses.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 From a0a54bee2aee697d957fe792c326fc2a4849b044 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 17 Jul 2025 13:42:11 -0600 Subject: [PATCH 06/35] moved all of the libracart suff from the old frontend into the new new, also added libracart as an option alongside jd and cnh to skip the auth0 callback when redirected back to us from there --- package-lock.json | 4 +- package.json | 2 +- src/app/App.tsx | 3 +- .../marketplaceImages/LibraCartGrey.png | Bin 0 -> 45281 bytes .../LibraCart/LibraCartAccess.tsx | 178 ++++++++++++++++++ src/navigation/Router.tsx | 4 + src/navigation/SideNavigator.tsx | 15 ++ src/pages/CNHi.tsx | 2 +- src/pages/LibraCart.tsx | 148 +++++++++++++++ src/pages/Team.tsx | 2 +- src/products/CommonIcons/libracartIcon.tsx | 24 +++ src/providers/pond/libracartProxyAPI.tsx | 133 +++++++++++++ src/providers/pond/pond.tsx | 16 +- src/userFeatures/UserFeatures.tsx | 9 + 14 files changed, 528 insertions(+), 12 deletions(-) create mode 100644 src/assets/marketplaceImages/LibraCartGrey.png create mode 100644 src/integrations/LibraCart/LibraCartAccess.tsx create mode 100644 src/pages/LibraCart.tsx create mode 100644 src/products/CommonIcons/libracartIcon.tsx create mode 100644 src/providers/pond/libracartProxyAPI.tsx diff --git a/package-lock.json b/package-lock.json index 1d88f79..bcf258c 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#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#libracart", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", @@ -10889,7 +10889,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#4523ffef42032eb4a923bd50d4cfcccbb1397dbd", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#174342d58e3d36bda2f2efc08aa3c95a217125e1", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index af322fc..3e0ee84 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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#staging", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#libracart", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", 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 0000000000000000000000000000000000000000..331ac297778c42435f0e66f276fb771917f78b67 GIT binary patch literal 45281 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⪼^)J{g zKLIcu*SQj|yf#1=I+a!*wc^G6Q z2Vsh&H?e`Pcz~Qou^YhhX6Hxfo&9}}5A4}G0NrZ-H@DXmIN!Frtj$F|usE1eC5-y< zDLK_5z=^T@uR&0z#92*E4N#o-bf_gs4}#W$t&CtlUK=}%rB4r7GI;vt;YUDjx5j>`)tF9}UuT385c`5$?xc(>ioYNZ8)F3N^Z{d&g7KC^Qgs!3%c=-Caohu!KGA=|j zN_{mgu6MA)I@$nhS>kD#T*1&&ur+!Ud8a5(JC+_@?6y7Elk-jDaEX@Bbfz|N!vyHf znhcc90$)5Tzk}Gdd?zb{nUK$fpu6IQeFa?^&-NAgpKyT`ugJdZ7HlqT-dXc=x#HlN z-QVqGVL%gsGq}418y1FUa@^vZ<1PC3B=&b)_Fd`oDYw30RM-AWn+?!ZU%0q(hVq(J z)WcBk$e0AA1pU9QjdoA2JwcQuE-e3{7cVa_2l4#cMryAp$z?pta;$0hSXLQ6xC7b>k!_vb{sOQ^u2#9cX~0q4!H(&?jY_r@Sj4)a-N{#J z-imQp8a#?+)kP1N@Em7XwvC}V!{zb~EOS%K%2I)_k!D=$lA?=rJw5WkogHm$q5Ip$ z8c}59f$5dwJ`u`7e;wpBcWEveEeiq1Nm!HbRu>!i15q!3G)25LZiw$@qe}V=2oTWogNMy{1OGU%N=2TEyRA?dn94i zeZ7olZM?R9tLt(1Yzem1^@ZivYWBuPf0Wr}l+aBkCiZm1_n$2seo7Pw##q*{_*pA= zi+PeKDzRaT7PW@Ia^p7atB)O8{B7WgseCn&103q+lp7KcaTxE+L-zJ=Jj4m5v<~QqQ2%yjjFpPuL2q&Er|6l{??<0NP0DZ$fDu5%=*3izy1=dk7OW#1X^o-}j-%3A`_@Rd8s0jVc-;I{4J92o*}2Rkp1 z=MyV_V6DaBGm1r?QdQ5giKce-*U+V#m=|Q99ck4;Uc001m3J8O!*C58aBpB9%QD>p%>Vl|hMg=&B$FxfY0X;FcflaH^+D ztFgt&#>9ge6?Ted2urMV!a~MnZ-H99p3|OZYn>@hD+_g-Kz)6+JcZ(kn%B@dowy(h z^?{U3P*QTt*r8t1)U+la9459-*g7?R)ZW5(M5wP5oP7=s-{Te@Gwg64N(OT;Bqw{* zMWz}q^h#2ZiqD!wPs#Prxn5p=*RvT!emYWv@(uRis?IA^4hk@a)u z*X7oIkfHIED#uqsR-JAhURf+GYdDdTo_#no=5dAzgz+_q4y8QaO~UtjNjH%>w5 zs!FWnZS>V((CAH5P6KiE5p@1QnkK3GnBkY{B-WNAocN`rdXD}c#dMgBhhv7E7dx*P z6cmt>zBMcvNIi$ht4B&o-a88vG0HO!X>E!y!s&g2Vqz*))eAd|syryUSDV$YCaX#= zrE?@TeI%tkxfS2?^I@$^q3wZ+Ts~MHB!$-_S^>^`oqt_9d*QMBeM!j=9FKcU+n(R3 zVITRNvzhuTF!>?#q`@>V=#M}#0bl|dljIj;UT_SnaK-=CwJM4O7yJjQ09CGdNvio; zN}g^Os%t=zK?RnXn|np)>WbIq^w$Snq@>rjHrvw~yjK0KZxexBS7|NnmXw1-a>6m* z`&b20h8$hYX?O@u^(oC4V&7C%C^i4m+Q7uX{KbouH*aJ?r3Q3;&ri`95QNpEg;LM@ zH}!v$q_`aqNi>S&>DoIu;1;ZP<`?VkBODqBi=C%!H^F|aTj+JoJWW^d^xTY(jbXcRyTNR0{`mG?yd(sJgC$j0ZAbRu6W3n)gJQR zfKRk@jl~)I3GpxFhL{8FrO#7VSN1#WT{Nd&*I>R5Nb6zCSakR4JLX#lxXT)+L|&5# zuc{a?sGzeiC~)`ixXrR+LouU12VmBlHv#S@GW--Cv;|O<jN75v@p0AH9 zA$pxHOvhw$Y%`a8=F}vrW;16##OMqC-C{AL!XJF?!@6D$3oOy*uWV)TNHe8o48ka9 zNW5R>@(A`4kXP*N2;~j-6NUQH04#io3&e0xzk{%lg`5!S%m7;c9Wv65*;I*N z)0>x0dx1fNH=p(T<|T=D>h098^hnjZfAOctX${%+)CLwMs$c!a5gwGEZ~Ayy>o#@W zq|yHT+Bt!&SJA(F#4JGzUc}gy_lbe(>Ad1;v=}$44FYVg71D5}-{x0AVOrmOo_5#o z-@lm&S?nHW8*||d4ARFdg6z~iNRAm2kf!C>jvIKOa2JczWqZW?P-3H z<2P;ZRXA(Dmmr;ysU-Otg{QLnbagA2Sn4m9Llw8Q|jCIo|GR36oQ7Ko$Fz| ztcOV|r=!T$z7wOLN%)W+)lh2Q9njw;xu*Ieh>{gKH`9%<>rciuW9>l32)Z$g8*OOe zC2tDQ;H$Dt?zrC?rzbnDF8mFS5!A1yIcXRafI0k>Na1ijTS@z8|$+s_b__PUSWTj z_bl&O2Q5EoR!B#H^_Cu0NRcINs~m+Qa5Lhb3Y~vEph){zsh_NL3(bN{o8hKD^6?b| z6?T1n{f=ch=#%(96&^-e*XcHRA?RS!`4PfQXsR~rCAmiZdbIO+=vkhHxHi(_IXIb! zLs+WE$4bfFFLb+#uT?y&=sqp7GGL>NENwsrJYNi`Dbgw)imAvlyWLB@1qe5UHd|VL zLLVJNlg~0bq{x3`M`5h8ii{S%W`)b>(v0a|MNi;r|IJl0q@z{=m|P07UR>Ox)1xy@71EJ%^g^C(U=>8Wjzd z{VljNS+z!1MDxb{b4S?+4#s*H9+m5YsPekaIr=)!LxCjKHiP&7jAoRsn*H(j^CUv? z*N1IYF%v$wuy{~#c}<&n?8kkT*t#DTr@u}`yUU_1-D`QeAF-2Kx*7{f>2NciHce5mA}5X(qSB zEh{{`3nR(vhog=;xxQQD8lIV7{&TCuA-0T-tKs!>npA5OjeQz{M_RmQHE&r}dI}@= zTK$$YCvDpwMEKOJR~UMP5B~S~zcHEF>+!B3uGOlb*nXDxlvB@8o#H<$A_Gk*Ha1(B z&L|)liabgv)c3?gg>ORiyG#yA zuZ@p^yID+|;7z~Xz14esUGiPwaFrDX$Ohs=N$|Q!8W_HCae1@2coWW;5%NgI{$_J+ z9ni}z`DQ%-_?-D+S;6w+8-$l>5Oe18Rc$MK88@@vCVsN}o1=Gw)MUkZhl8~4mRuX@ zxF`JbV*3N50yN>brP9ZT7)MqW$YOHcU7`jZRAkPlO6&R>e`25%$8A8A*gH1HFl}K0 zUqQWfh^mEx)Uy8XBJ@sfq66WhTVS zDA5w$aYlcgxQV&)v^^cKc`O2y`?V}{>J!IYpwk_zMQD|Lz?N6)_N-;+ghiXXaV?g+w*W#KLNE&r09s z=eK8`ESWNQ3+%7UOtQnbs=%5TpZ9CCNBDEm*z_MyPd;+| zi!J=39!IP|Vr+(={s^ii@1P~dnPZ&B>r_BS{40!tr+euJ1uZ^#{kxq}#-8@XEIpN` zc5B*^PCpJ&A4rXZa2nW9qby@$3#)WL)ATPqX8ULrrppfGY47jfD+$7>SAO1(^8Lsf zwz)~fYf`7cL+3auj%YuDFil**ATCAwb2$>|vV z{=EofLg$}1OI?5)k{GOTx)Yw6K6k(n_1~A{gB6-WFCIlr2=Wt)TcSX-iReGpq!LoI zxA(oB-Y4RgP39~cpj@fMs`5gxv2%gD7!D&N-eo(CFf@qE>BiQ64j-Tt+qt&#aGuUF zmeo7;%r6b}6Q`oP8GDP8__Davi>(==ozQ@<_tb%ey&MdZ-k^+Ox&cuq-w3cL`?!uq zIWIB_<7+|>q=}Du8x~BVwY;md^iPIG2 zS>_iKSY~{*cC}ktbdmTCvI9qG93E=$zFQ$V(u1wP-8vDBX$A?Mvpx`ye8pef_uYl; z*tnMob~WCJs5>{n_5l$ISsjnt!BDyy>H?ZAC?FvBrS&JP2*T&3XO7@rA6W+CAvr#r!-lK_ zE86J4E&yINQ8WGbW3CG_7m^ zZcK|$Y#`&Y6d2vQe*x@G(M(d0NSHRx4sR=x_s=A>yr4^VvHb2X>+afzS`>l-5bT35 z+$BWi%D_qi0SL8Tp^nb)X-%3QmduJ?#vtpFxv?YF(}VXcSqUMflJLz-rX6`d%--8O zcNYTwe%7#l4!#9;jTGj1bpSrd>f=Fwl3;88i7m-J6sUoM;TUGZg_>SK{ukDI-9PAf zjY#^f9dge3ro_4Du^0ZO{cNs$bGN$jmZ@h-i zymlE6d}#^V;@II$P>sKEWmsPJu{>V6VE!a7b{Oc9<<#-8sx?mx(tCGE&xqI3ok283 z@I!PTBMK=}EY{wZso5Ku282)7Yzz8FYpUwf_ftT+Sf-JFFRn`e*8{oP{KI@ zp=AaS1Jt0?Vqn$;Rajhr#d-8M(Zt*R_ z|9c7IedBNuLl0_d#_GFMnffYvA(t7v9%7x^=(V&Bm#^MTJE7ylx8*YUKhrFBARD1n z%+v0$tbyKdt%^V?x8DHK*FH}OQjVtQuVJW&+w2lGY0KAvrp&eX)oNM&%yFH#jC=?f z%pB>pT$(A4j)a1>A?o$3)XIq0lr{zh=)W*syhrNKWaI`M-nK7yy4=@Mj0oc zz7t;|fy($m@*Or!bWBBnF4FmM%f8NgRn5{ML!0Z(r2d^NksRSSLSYa@HaM+tP-;fT z-mkH2(^ngvuit)#Wo7f`czn5noAUoy z_m%M(cbK2$#zY07Ok81enkEgNdWPrckZz3e=@VtwUqee%NouC zE-@Y|D9@>Jwu2G_>fZU*gP=18xUcHUnN(T;KtC_#2)wXWBw_Ze`#roe`c%h&Pz;0Wj~SOP7FsiNz&$hbjCWFLrNi ziR_U~H*Sow8tsVXo2w1M6d?k9(g3It=sCJj_`W8eJSR42bR5KF&6Tki8g{ z^r@*<5`dbE``l~g&B(v#7Q>VnN8NlcLRS&{`AsuS=E26MBQcj1_s&1#SosZ;qtFpu{q;mnLI96ci8L>Fb)qaiw%@wMI?Ym1pb)X=>ca?E^jd{8z#)6yiNFlLYf zC0LK5yiZ?ViajpdMsClmuAKK%lt)XDtt~iVP)+1;4`cp~0;y{1)uU8RYZ zrXt4YWMn2XeHy@Wqby8LM^{InF{$z7R#BCR;LjRP8|=B~wr6o+;q>M=5QV(?=XC{L zWR_K#`wrCg18)FUU^A{YY7=ola3tWn@q0VV;lCbU!JhulOt>b8b_wyr&R)7%;fdl z1Le$iT?KXpP{~@B8zkuI!t<8_=D9*$jzsSN?CHf3Xa1&jMv15&osOAcyT_ zmF`ihyQ7Ya=JVfJxHDq%Ltw&7rk1hkf$$I0nqDRk|VRX&jNr^wRZDKg>L z4O8Q8-MXdZ`b$SA9%TQ(&)&Fkpd&1Wa3bg zDy&1t%K?t8zQOMXos8C~=S5j6Jp3kwfyL)Tl>@B&Mb~L5Q zz{^AOuU{HxQ&9I;R9W6KGZ1gWyFd}qdV}GH=-^MTx8=`>-?hALHiwYQyd@p3SpnE7 zVMJac*d*8pk>oz{GE^>x_$b+H-`ep4M-%M+&|tt^z))uO=`dWTn+-iX1Dv! zneti{@hbfPZ1dZs=}k~zkVKb*!2z~C)z0MYe0~-qe>Jr;J9>QFR9u46_|{Ex2!k7( zVyTW~$bJ?B!@k5*A)CSJFip$ae;aa}qxB^X_o>4avS8ZX07&Lougex3JFJ|aU{`Yv zLkPf)Q{}&`d%Az@-*{F^{>_o@GCP3@i|uVWLaulLCjSF~C7Bx8l9U;IDp>6(NW#&$ zJ4cGYc!3AgoUfDa`m64rgu`bMYQ?;(E4|Fij4mm0RgsZbaS=Vs=whOSQ;x1f^6hQu z@&{^hy}j3PnnaL;J1=xv-jRDvPAr{Hmd>Z5q;v&3elSx3=nWoJ5Y9j4N`S(|3Ga&a z^0VDMM}vpI-O zixKV0U$@nbh>pD7?P7jVAhrB{t?$QSMkZm>f)t3(0(0*$zG|ARIW*DX;E*$a$K1X; zVfQC*n)17v%!jK03R7rlX&sz4lsCQ_@9?&r=Y?J)Ki|fbFI~px7v$&LH~)M3#PzwT z8LgKjqqYvD?GN<#zp*m-lpt8`PV!AC{OeOHZN)qd9%{wH;z=@zQl9w7svk5qU|*lw z^s?YimVU>AFWm#FGS(~^THG;7Q>?KG0Et8XYtnCVrof&1%So#K)PK0J&bk^e($j~H z3zCqKI17Wv1R+AAFa1*`5g3gl9~qdM&Vk^Are0U7Fn+x$l|htcRd(BF^Y|2?W;mL_ znhrjA2U>#QUOQvdQysE!7P_O=rXqWsb|D0K{BtP$bA|`y*s{ki|NCky zDEq2-G+|~(^urA&COYsuf?D-tFL)wWJ$Lz%fy7l7q+@wmO&5Fm$)aD;ZwyZJ`>w?g ztg4dGW|b!-pNftvxy5y5sqtbzJ~crnee!~mKOF{5Vl4wkHVoBJf9R2fe?ySh-8tuD zbzOFNYG%RvV0_oPX8I`AeL`xhPAX|NWB0-|W&6djs@5h0i%iO#o&-i7B?nm1`VF>q) zTp9nzyy^4cm|+KUG(BDONen6^CE!0kDlf2On+0qVfD|z;bXbKnE0c>j$DCow)KB`V zqR!vm7(=+y0hT@bXMUf_Fv!ET+Vz9Q+{BX>zCab=AWi#5PX!%z40&lHaL{9q$V3Se z`ACRPO0S?niXwCMG6I9#^pW&xzMUrR4`d)q;Qn?aL}B=L`$}*(uMwA9jl#@?ts%78 z_R6RrWLGT-FGoK!%FyE)k|(OkvBf{R=ulg)x@L`l9Gt%&9#Nhg`GlSyG@dur`>wl9 zx2a8S>PsyT+a{7b>!}pUYe9Aj%&WT7^m&<61&4h$&J;uL-i%%#($r z$Z<^g9bfHhYx6qTxD6YULUW9v08Q@I_Ef@`shlx^+pUI&x6{ZCKv>6RkX|EqeH zF7y}KGq6QG0eaKG!y`UY(j9qu*-Vi&B|?aBPVRhuD}uQ?SNC=t1Cd| zuHs`o5}f6^SbP5EQ6G!0FxS@~G|M`ZM2@6niWIGW|1-|vGr%e*T5!3=p`j`=lS84I zLKQUwuMP`MgxmR_ zEf$mK-Oj*0hd$hFB{|!U4*Z&Rr}yV`8Ds^5iBb;Kc?O1tkBW8Sp}%66HMa|MKsyI# z2ImgkE+6m8+H%Y8`$*Xsz8ysubIxq5D+f{S`cudaa=SjrQE&j{0i39Ww?CyNnLkK% zI%`J41%hsy{Z=>UnI>#pP75#z3Bq*9PW&-Qs7tfT6J!fU*Ix^Euw5qtJyr|Z`9prH zsl7rYfU#nf*@zzer-41gwv^nY>SO58A37~#bs zAwIJP_qx?&nTv9oKpR{gjw$ypF8o<9QaqPk+?W`0=$&@5@iron*R@u+xiTy3Qe)qK z=tIWwDb8{I=P)m0c)->aiX^b-h2?YYpu@ilZSPxPgyHnNJ=q#T`^#?hMzT{3Ih0KQ znS8%6Prdo-e6JBRt!@W1G$97mWA{Qci!4fr*R;<0+n+n=lZu*}uC_;OKhf9TwZN1= zro0u40)2gDO%9rCl1`lA&9mK!8w`JT(C?E2v1L5Ex_X^2%V2cX8+&xI;2)WX)|qjT zd!VsBq)Yv-hBUznI&+vXWhr=*CXvQ_7scJy&JKR*vp3RpX<2vIM6U|F((~{YGAteL z@9mNkrKf)j*8R691__|K=x<9pj~*5A3kfM`Xz+ng`8o6ZIn+`$noM2?&6(I`S$-2z zyk+W-!q{abvkPh?ym&4PvCm*IFniz4li%$`nmB z_vN@f=(G1Hk^M9>-(GBy%YPR1m@*54me6VB{0nJkUwgK`)Vs#NX_ZAUKJ+-`MV^9m;vFr@O>heg57;ZPm;>>nkArsA=P=7QT)h@!|m2fm^ zJ1H%J@nrKYQf`~R5AT+$20{dCNEr8ncQMx|y?t6XZ`O`hZm|37{~Uz>*~ecjPa*}0 z13ajp8`1y)WNj`6I;V_VZ<6RxQ%Zl>7VKo)|bcbqi{Zbq0uWeZoAU-ut_;adL)Ey=1xTA#ggtFpilH zW!XgBA78J2giD6MROp$Z_JqG;p|ye|8~$>9P2~a|cdUJcrJZ^_PpTGW$ODuV%Uu@%UnX&_4TfH&#$N>wWD_?3f=W z!BLAV9x`flG%3O#QV_p+eHqW8JI8Qnd9)gQApBuoic?NVcJ+xBr$OZfmfhb4v{-P^NpBPrRzlF#S0F9UJO=E_UYH z>ojp+jpS|Q+>mg@9)r$UYv)*t%e1=t->d(-*q_u~Cg@Tg`e-GB+pt`)k@aJ-YC?3a z>tqfTlP^mh=^?yUyUW302D&atZw`e=_yT0A~-o+S9P`WqY45+w&~@7pb%SHs`Om7ysDCboJ-@ z2+|QkNKLTVZ~0{i2@O~3zz%z&+rZ%bR@HdL={I-{cURS8)NYHE@Z`E0a!}25br2@< znPkE&=hqXBt&(d~AKH69*r@lDs%>Z5nu1mje$xlj+D9*?T9(m*s;ZK?&p5RTb(gK~ z_kI4B*lmT=BAy5j>#~y6Le1q8@J_> zQMfmBmxt_zIL$uyl$DhH92}$y&jgPv(h=P~ySD2$<_k9JIcqYew54qa>hR2xl8-O^ z{8C=ayza|QJN9*rfu}L$dSrnsN+_s^hPcFW8EvbbMo$~S5AbMEyW=fd9J#-3yu zdiD!)gisq_Y)nr}bAs>3g*?G0LDyGp3~rPaiOF>QNZ)f9SJ$+@zv}G=o;-;0a`!3Z zHOvh$vQ^d5sY{Dva-s~wM>-0EmF{?dtrULnb{G*PR&W6Lfp?KgakTP6#M56iB)#5B znhZ}`ZeM!T%d@)a$r|exN{$B>N7Xoep|0%iMDO_{kEvU?yrj}Tzprg?mVN8HZ|@`( z&F!9_kDIv7Lj&>OB$9z=L8Y7b-A$BqhJ31jqnbx~|G=cYyD_T60hQaOat}qXn>#zd z2HZ*&CFyS;OulCp<-LEVYG_d$q;h-S)yhrHYBB;b5J z(@A2SsD0=xu0`5?WZoP14!bs2y#VcdaBy%PVqePV*k{w0uB3Y1=lZHMJg$nG07yuB zk!M5P8oNe%Ndr>X%sefWL%hH(`9Uw>2Qqx5BkOi}aO_XS)-YXlMt85mfh*I20xYda>hTQ%w3#I773ctm;(b0Y#Y=i^Shwzqpn1(yA^qjhBCWzD z@Wz-g=9k4Lcf-lxO7a4#pu&|X0shJ zGb99C)nf@i>Prv{HRO1Tnj2nqUiH1Kz0Z@(Ah}`NA`|dLx%f$xKEE^gQJWWd)Kfy=*#M*c>_xa1AnBoNZ4b zv640rTo95{J4gM$C{^&3PWM}y7t_NhX(8w z*2Z=2M2e$kzp)CefNz{^v`~UQr*q!z!ojIDY?}$wpBW&tv zL8c&o+5Y$tgSiM z++SXCR@Af$GsVyMDOr8fCdZRt8^b*d-W^EJW@}5|21T0GrQl#+!*_4V;eDs&H3f5T zEQB4tC>=>)ktzJ?(N&;ZpmwWx`7$mKF0Qj?c5xxnM&_L;u1sSf0kFB}rT0D-Q7tSU zo}2K+s&O3%UC0QDZahyrJoMJqfx`~XPKd;&yniM0$|7t?UozD86Ay3B0V+2gJSqq{ zsU(cb@<)*{9$6e0Gz93&2sxpHI$IRwZF~^5bL$m>y;tiT>j~b<{N-=qTXSizTB@Pa z#7%}Y>qs}af5n<$Ej*X}-Gm5@91cdka`Gt$+{prg2QBYTwgZo?FJHO5=gvbVM;_4@ z)-LfWB?aQL&@%S3!V#GqYRR=Bp1Y@D zh?uDpgGcb+RlbBphx;HT267*Vyuu`+wd7KxJATrUX=#j8w>Ixx^D&aYZM2O#QL8vR zT73_j=GWE{WKY|pD9Cdnk7%_!ZuDg`E7JZSP87KNAVlt>np^&hipL#@njA+1{F4B# zA-JYA99t%xZvA3PjOpy3JPpm9Cbh@;k1zKW2;Vp@?2zpU@{8(I#-+9(A)i^M!LRe= zgdxXj_2>h&Vg}ey^@T3K8;OwVtD0hre1wbQYER^w*vNdPK)B&038NXs#i!Vp1aFIp z&`@^6Q18O!of|7aKn4;bezjaY_?IzANdDiP#z<{l4v zRxt?BD(#b2xQ1B6e;yp*hsga?P{X`JS_>+u6ndAY??`8X9-rr{7rbGM@7oc60Zz38 z_V&MfdMF|ufdV@(FYmMWLC}gXF9fIxwvBL(0adu9@N(GNsCmsY|HU!uP8#z`N<3>K$aYDchhctrJ)7& z+H;9G4%82Qef@7@?W-#a>~Fttk1A@sTF=vol28NgMUo3-n;z@=OSX0|xWpv>cN1Te z2f6!1_Wxb-TfeCFv3u4Fel2gJ3}wMK>X9ZD01hIruyQC*b=K)42AUn=YM*7`X8jdLHGyvHkpN=<9sl9q4(*SmX#35_NAt zB@T0{=niqrN9;(4U&Yz`EzI}Ey0&@7JwHXSs4mA`(Aof|K zrHlt=LLoe&+(J0%B3-8Et_C&zOuIk2s$8wjMs*E+_G&-C`qN8Pp21z~E4XHofGjFk_`GZx<7Nf1USZkJRw{_-vPjnJma5LtjUe#~#f3~I;|nwy*3urt!C&ku(X zODMzb^yWqP5&8MfhXYpb*e}n#eSfbx4L;Z@-~8N<@ArC6J_*1%!-%3W&sFfpn1p zl>yJQyg^3pSJvlMg8iNG+${$uhV=n{64MD5^=|XR{Wer1*hO*&ka^Y=QAR3^A4&V9 z{qLl@sOZx5(6m8PTcfS)_#r0;4G!UI{`aTz!PWgKC_N;6V`ccuV$(kwN-?;9G&Ii!ZeAk~2X$XqOo85}muDJjb%s{K zA`ZR;&}GFi5mUwkqx3>Ew&6kU;#D^(QVx;7?KEC>@sH>DSbjcCd6y%^6$xNKzC^|- zIkGF(^73$aHlA8w)FalUvoc`7d;I?wx4UfP`1I{^T1zdA;;N3kdUN85<6RRDR{)wJ zMRL#h0bOVl6b0b?Ixfng&iIJwxGyHA=ya!Y*=5c26ml4c@}ry_%*fwyRj(GhxX0opnchr7{2rooZ;+tC zWal8`koCQM{*3SK)6e2{Ug!0?p4WI>kNW`68TUD`WMlf38#^{rWGlaEDAu2%gN#Tz z>eAJrN=t7qdz->ES!vPVe+DSh8ci)fUDLhw)J;(9S_MZ>-II8vfRxoY#;cDndnuua zNl--Q8}d83KM+?LuDlWYs*=@D0`vwNIKq@=8{IOc(YWZ3j7!!w;4N+tH;{e8PMnRQ;GqFGzAKDW zbuYJV<)4XN>a4MQc2iQ)>a=io%^*{1mmK=Fk8dR7wqNgaa8mQUpbYkDiHkiZcQ=ki zkVL6)4`eE!O<``g!p#yCcw}C6GDBjW2>?M705w6{M60~Ka$Ifpf$2mg0hMD?&UcAU zvQEyS4yW4in9pCHa(q^``XBZ8D5{(n2&b0ArPeyHL=;tm zkg~3By^#nEKMsxqpfWk|lP{R8VqMxaSZ777Kf9T3+;ief0GobD+5VaixOj6gDu7pA zTy0vuyu2axE56A;wL|B|GZ+WUP59IwndZr2dINA=W?{X9&R@-^c+IT#AOQuY&M-y{J?%}>eCvt-fQ{a>s|OeU zs3<2f$vvV%9md#h*7aN)rsha3cS>-V6iKFzp7QY$Yd>}^)e#&QaJoA)leJqSPD2WG zD7e)x8;a8qBqSt1_Gq}|UvC-N)Sn#tWn*JBK88TvH?p>8JnLlVGri#owAWWt#2Ax1m?NLn6bbMUNBY!y~N-l^TyVOu>Q@S8tJkQ zL^oIBTz)$9&pjW`-riRLna14Q?pL|h_h^`mwgopOCZKhaLqEK!B-8@V1)nN)%>J1_eojx zIQ@)YTggC}NHV)?jMCkm=Twm09&EIoutOgi3JD)yA2B((kqn>fNB@mBN|a!05AJ)H zdunLhsVY$&i>_S@+aC;yJ%6oi-hRHm=L>3HjlDYiSzEHl6*`?KM+CIxooX@Yaw>pa zSx-+7K^N|6EMOySwo^Y{FDD;&zC%vw=yc16kw*E2iwgsk0dV_6=bf9Y?UfR)!mb$r zWSax_1pG{#df6^0c7jb0j9cR|%ed`*C8re{*K~{XIJhKH8vV=X@}5tN74_4dGY#ja zenrvCG0ycEEjL#Eq>9?w2mn%0#J{|dfU0B3q^2YW!Ht=<_2s-gz5*;g)?)hjtwc$w zmDsIYp~0sCuMNp_ZVV*{J#_=LWv%QAr*N=q!&^v`bpB&iKaEe$!rDqU*Vn&!DHIXE zvz){KACWevr7uyK^)F!QiVg%R*qxneDok{hA&~#>D?z>xl9*o%6}m6irmGBY|FyP& z!w*mWkrkbQ7L2re<4}aR0)iBdq3Kat+;qN(1JR~ zxycxEX)xA{46t`d#JSskEiJcjadA;&Sm+HyXj`&^1ni}~=%P$vj3}{N6?2!^ixumR zi2)FH7hu6LN7XBimSwd1`DNcp%X<6R>XwCq%1Dtljk1(4y??2{c8}>0p zuLO!l*#L6^uDMz^N7LJ#@s#IlNgxuBGfce_7PK_7bwcnO#9|}M%F2wa zicN9v9|cg9VqJx^4|1aU;#a!<{PA%Mv~8-K zD5s;N%@K_kqi!*HsQDZRYCMoF>;}f|WlV%OcyMWHb^%3ZYx<{U4_RXkhf_cR*YGeJ zYz4fG<80zlRf_oEi@iX|xyyfP-c>T)`~wqb2!h11jvbmK9o9KPZ~)Ybi6AJi09o77 zkq*dtW2Pl=xL4PN>xf4wULm5|P@7CZ)x0`&buGb^H8oM36kdW}anHs(n-%iLd1LTe zr-QyBeAt+2Pm?MUZ72%MOqwEq8Y!_$Znal2(*wFl5;N`Hg90eXrK^lrEAK7G>c~4p+)q z2$=^z{Ng_u7q2=$-Ge5Oa{9=nG7MflA%2DIQ1GDu+Qf0Y&ij&ZwJ|3KePR)n9f*PuvBLSn?I z$diqVXHNSPxFnTX5U8ZxA+xx7d~l%F*N<^Re>dbV=f4PF5|~_FwFE@^hK+L5LcY5D z1)45J6k`YAaH1X915$#Ku_`W(2Xe47+cFf8cKT1v%$U1V0;&T+uA`}%D@u&UhH9C* z&k7Kw_qLkfgythddbpAr`3D4vRz9_~)68d}d+lIMEMvd7aEXc-H`26W!2V_;zC0>` zO|OB8li2QT175k*ANoe#8yda+w}=#Vbu(n8-`4c8eah#C$l(5P)aB!oS9+V=?qJ%( z_`5;w$;rvegWV3Cb9JVplLQ+q&@YjFrfLfyuJXR~cSM~0zjIBreh8IYK z64W$uX<6Or*z(PZ@)_P*tC3_%$Y0(MpH zbM05)fa$fksva`C*)u-QB5ubDqXAIrJ-d8aFJ=r(UZCs(@6-~TD%gLbD5gU$9lcw5 zaHYOQ#{t__>qT#A9{P0h&PYjWbgM~~L7PFT zlJ006wMVfczPP-#b8rqZzg<>))Nhv-fpc3Hla-AL4_8&;#l`t*x;xXj=kl4)xOtps zJCIPc@2xx4PCwCod}fW=(sloEP9K8w@%ZpB6fFPpk?rv5k&$ zu_39RbUVl_7v9hMhjerV$5AWT<$drIL4&u&Tpn5;YARuZRx*ker%ky~-V#ys@RRE;B-5fG$-Mx&8UxN*znxgtHF;5*lbduUA5I~%~r|DkbI)cDKO)=BMd9sib;^HMO%tTfrOjB+}NA8sK^|6>evmLek@Op zUOA*zIc;s+cz+>XLlXlfP(3?R+p=^=-6@$X`n&gj{&0aKmiAZo#Z8cnV9QOue4l@! zb~!;Gsgd@{+Z^6V`BskJ^tnZlp>SO`=vxcD|=V9Z^vx8LzPSR{l%;+fd!#gQT zc|!pL`UxnAsKBek*(e;bnh&u+h3MWDG{vh(4^<1K0_GKzZL?^v8GmFRAK#=ZF!kVT zcfS1=TizUZe*I8!{n*Ay{vSIQ@{o=z-&d_b z)3m8C2qEWfT%1(;fIBY)O}a>zvzbfK(Ak$WNkX}M_1p|N6jzCyzV{&%awSftFFA8f zo7Zl(waC|Vy8VOi1VEDf@rp92hZt&XM~xy-Ig4KpK0s#Dg@cu=ZFeg&7$&?v3#Av6 z5hu~r^`f9~{n5VbWR```DV8_?5mDgZk^?ZE;a z8-DEN6wX;u+FeLUNKu#bVD5x|tBPJ;F;HM{>Z@N}V0jT6#Nn-_J!u%QJRYyOW|(C4 z(~bSjz1;h|HKs+@5pBW1TV8T4eC97YvFM6h9nR;kU9Bd!cQPz`#lp?qGn|+hH*Gj& z&|MpkS-^Eg`r=nb)Wwx>Vj?DuVcPuFHb>@bMi4X>c!~0tR0?%I0}c9Jzcpc$L^sI_ z@^#=BF`QdJ2Au9>L-Xmvs4eq(?PqGLmNyh6)a!hk!-2a}NEzz^(F4>(cb`f^L#pZE zi)cV_-E07L{C&%8!g~LpaFRyB=(;n0{LiSpeJ<=NTtQG)pMvKp;P3p72u?V{k%@Oo zI&QgVH}4g+6oEy!l+jnS7G=o@$y^#WQL+qufKkS9SN2S&=J^6+L z%M{tle&G%J;;E>()X9x|^2uGeLTa!%gt$(M$y9C2Yo%rQLi4|zE*@}+9V_ z8+1O?E^mtiyGbT+dBdw3KzT(CDDx_CLPK5&hkoHNC-OV|-jy&c87PDg@%T zwuPkS$G-60SX@(QoN|;FPZc@^!Q-32L|FC}UaKRI4#ithCpxMpHr^8uIk6LHNkVX5e5r9Cp zGXyD>9UIt~R>`Ya`du|0NSF4-nVwr-5qPP~>k0u8W>+|`+`b-O+QLY zE>fdG4h5bIMq0?rOUR1q>af?(kkAUIRN6q#QXhcEgXDXZU$!h%rjjq0sV?KRnsIc}sSZ zCgkL8MApWt!)0-O!l0yP=cBW~HGkDQ z#W2uGv+jsA_>QPniFrMAZ(i4iw!Oa>q;wAU0$!+@U2*gB_L7MG^5b#)7KTUH#b`(! z(d=HEr$L8@hj-8r&mby6O>_@1jzu-LJ!4dZy>O4Of4(RN!B3;%+#?c(s;*6keI2b5 z{o2XLa-f7sanR&34-ap5BuB?R{JF`h~21lIQ&|VA}|`f#!A&2f^%x=n}Q+ zR95qJe0uOoBON43+yV8Gi@UCFV;0h5M-#)-q0%$;fG!#tQnlMzwz4f6};*sjB?#-ZbBMOUcI_$ z782R~x#Z-eZSJ$fY)I%7MUo@U-fTp#Eg0rGoY)sX*Q5+S?k0ylwD2>6>Df8V>9hIJ zQbX%kF&apdP)`e&o=C2Dwjck%nDXn`Nj_A_x>q82Q6FEj>KRkEzOoi=X6;Gd;KxN$ zYRdGY)hWu7q^nE0U~J--;Hi?Q{Mjw9X8I*2u3kGPrS|n5Tz4$yU4L1dGgnifHFWvv zfyWlSA#8#6UGrbw@tWW4dDX6X-AA=Vv`n#`9z7Wg27L4I6bWCu-li;)9m$jBAB8`j zzw>KH|3Px!6=|46oqGx$_q3hKI!+zX6cu$sd2?(oIoa&kzD!tvz%!UJfc$ir-FB6T zs9}!-f8xx|Nax8uhl0XjBqJp=6;cbv-Ftb9c9mQmibPNn-;|cEv-?18rsZuoj1+Bq zggG@zDrJkzqrc^LU6D*a8>w*YjBIqQKIto=bLGp+gTfj#d$N_7jUSu4JLQ3O1nhAy zpZCPAU|qaQd5R=&vL?d%G+cn-nl?NMAp8m$syvmO)bp)9&MSqccL<4pm4OpUeZnhQ9!Bs zT=z`9ny*(Jn%j~6{j@p)i8ghnblzMgw|JVm>Xe?~&W?vB8yrtqm~ZE1H|bxC8x2MsuLi zM`U4vObhlK^cd-1eNE*MI&smpcn&XtP*RLfTsoU64l2o!o*5sm!{H_k4BqYjMdME@tO=5nE;iZ6tWU0`SyB zjo2nPR85ubxERfdof_6_3U6AicJfDN<76#tXEn_2UnX*yp&OOAGNRsi%5drmsHmun zTqezY^X4l#1WFYZDR{AXc`J#@Wf&OZX`vW-^Ts|XSW894l9a}smzS6Q#Rnz^h95-# j`!D~yWB;EXm{W=j%}ip;yLP)o@K0S;OQlTdMacgFM1rU& literal 0 HcmV?d00001 diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx new file mode 100644 index 0000000..738ce13 --- /dev/null +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -0,0 +1,178 @@ +import { + Avatar, + Box, + Button, + DialogActions, + DialogContent, + DialogTitle, + Grid, + MenuItem, + TextField +} from "@mui/material"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { teamScope, User } from "models"; +import { pond } from "protobuf-ts/pond"; +import { useSnackbar, useUserAPI } from "providers"; +import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI"; +import React, { useEffect, useState } from "react"; +import { useLocation } from "react-router"; +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 [activeStep, setActiveStep] = useState(0); + const userAPI = useUserAPI(); + const libracartAPI = useLibraCartProxyAPI(); + //const [dataOps, setDataOps] = useState([]); + const { openSnack } = useSnackbar(); + const search = useLocation().search; + const searchParams = new URLSearchParams(search); + //const [{ as }] = useGlobalState(); + // const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; + const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; + + + const submitNewOrganization = () => { + if (libracartCode) { + libracartAPI + .addAccount(teamKey, primaryUser, libracartCode, libracartUsername) + .then(resp => { + //the code was used so remove it from local storage + if (localStorage.getItem("code")) { + localStorage.removeItem("code"); + } + 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]); + + 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()} + + + + ))} + + setLibraCartCode(e.target.value)} + /> + + ); + }; + + const newOrgDialog = () => { + return ( + { + setOpenDialog(false); + }}> + Enter New Integration Link + {general()} + + + + + + ); + }; + + return ( + + + To integrate with Libra Cart, navigate to Integrations page on Libra Cart and "Connect" to + Adaptive Agriculture App: https://staging.cloud.agrimatics.com/grain/integrations + {newOrgDialog()} + + ); +} diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 29362ad..1f2ed7d 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -45,6 +45,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(/\/$/, ""); @@ -337,6 +338,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 38a10cb..f42d5b1 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; @@ -477,6 +478,20 @@ export default function SideNavigator(props: Props) { {open && } + } + {user.hasFeature("libra-cart") && + + goTo("/libracart")} + classes={getClasses("/libracart")} + > + + + + {open && } + + } ) 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..5fc06eb --- /dev/null +++ b/src/pages/LibraCart.tsx @@ -0,0 +1,148 @@ +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Button, + Checkbox, + FormControlLabel, + Grid2 as Grid, + MenuItem, + Select +} from "@mui/material"; +import { ExpandMore } from "@mui/icons-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 [fieldAccordion, setFieldAccordion] = useState(false); + 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.JDAccount.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 ( + + { + setFieldAccordion(expanded); + }}> + }>Field Data + + + + { + //setFields(!fields); + updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS); + }} + /> + } + /> + + + View your data from Libra Cart on your visual Farm + + + + + + ); + }; + return ( + + + + + + + + + + + {fieldOptions()} + + ); +} diff --git a/src/pages/Team.tsx b/src/pages/Team.tsx index 7a8f29f..dd88fdd 100644 --- a/src/pages/Team.tsx +++ b/src/pages/Team.tsx @@ -214,7 +214,7 @@ export default function TeamPage() { } return ( - + {title()} 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/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx new file mode 100644 index 0000000..28f41e6 --- /dev/null +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -0,0 +1,133 @@ +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"; + +export interface ILibraCartProxyAPIContext { + //add new organization + addAccount: ( + teamKey: string, + userID: string, + libracartCode: 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>; + listFields: ( + limit: number, + offset: number, + // order?: "asc" | "desc", + // orderBy?: string, + // search?: string, + as?: string, + asRoot?: boolean + ) => Promise>; +} + +export const LibraCartProxyAPIContext = createContext( + {} as ILibraCartProxyAPIContext +); + +interface Props {} + +export default function LibraCartProvider(props: PropsWithChildren) { + const { children } = props; + const { post, get, put } = useHTTP(); + + const addAccount = ( + teamKey: string, + userID: string, + libracartCode: string, + libracartUsername: string + ) => { + return post( + pondURL( + "/libracartAccounts?team=" + + teamKey + + "&user=" + + userID + + "&code=" + + libracartCode + + "&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 listFields = ( + limit: number, + offset: number, + // order?: "asc" | "desc", + // orderBy?: string, + // search?: string, + as?: string, + asRoot?: boolean + ) => { + return get( + pondURL( + "/libracartFields" + + "?limit=" + + limit + + "&offset=" + + offset + + (as ? "&as=" + as : "") + + (asRoot ? "&asRoot=" + asRoot.toString() : "") + ) + ); + }; + + const updateAccount = (key: string, options: pond.DataOption[], as?: string) => { + return put( + pondURL( + "/libracartAccounts/" + key + "?options=" + options.toString() + (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 8e879f9..0efc34d 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 NoteProvider from "providers/noteAPI"; @@ -86,11 +87,13 @@ export default function PondProvider(props: PropsWithChildren) { - - - {children} - - + + + + {children} + + + @@ -163,5 +166,6 @@ export { useUsageAPI, useKeyManagerAPI, useJohnDeereProxyAPI, - useCNHiProxyAPI + useCNHiProxyAPI, + useLibraCartProxyAPI }; diff --git a/src/userFeatures/UserFeatures.tsx b/src/userFeatures/UserFeatures.tsx index 9f9a2f4..54aba25 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" } ]; From 767ca5ef9795ed2d47901ad8846634ba483e937b Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 18 Jul 2025 11:01:34 -0600 Subject: [PATCH 07/35] update to libracart access to pull the data from the url --- src/integrations/LibraCart/LibraCartAccess.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx index 738ce13..d2a3433 100644 --- a/src/integrations/LibraCart/LibraCartAccess.tsx +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -63,13 +63,21 @@ export default function LibraCartAccess() { } }, [teamKey, userAPI]); + // useEffect(() => { + // let code = localStorage.getItem("state"); + // if (code) { + // setLibraCartCode(code); + // setOpenDialog(true); + // } + // }, [searchParams, libracartCode]); + useEffect(() => { - let code = localStorage.getItem("state"); + let code = new URLSearchParams(window.location.search).get("state"); // not sure if the state is correct here i thought it should be the access_token if (code) { setLibraCartCode(code); setOpenDialog(true); } - }, [searchParams, libracartCode]); + }, [window.location]); const validate = () => { let invalid = false; From 093ad7b2ad7461895cce3d2d1fdf64f8fe4bf239 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 18 Jul 2025 13:46:07 -0600 Subject: [PATCH 08/35] sending the auth from libracart to the add endpoints --- package-lock.json | 2 +- .../LibraCart/LibraCartAccess.tsx | 27 +++++++++++-------- src/providers/pond/libracartProxyAPI.tsx | 4 +++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index bcf258c..d6134d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10889,7 +10889,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#174342d58e3d36bda2f2efc08aa3c95a217125e1", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#e1a765ee24fd1c6e374fd6d7a21edb82feb45c6f", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx index d2a3433..391a3fa 100644 --- a/src/integrations/LibraCart/LibraCartAccess.tsx +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -25,27 +25,22 @@ export default function LibraCartAccess() { 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 search = useLocation().search; - const searchParams = new URLSearchParams(search); //const [{ as }] = useGlobalState(); // const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; const submitNewOrganization = () => { - if (libracartCode) { + if (libracartCode && libracartAuth) { libracartAPI - .addAccount(teamKey, primaryUser, libracartCode, libracartUsername) + .addAccount(teamKey, primaryUser, libracartCode, libracartAuth, libracartUsername) .then(resp => { - //the code was used so remove it from local storage - if (localStorage.getItem("code")) { - localStorage.removeItem("code"); - } openSnack("Added New Libra Cart Account Link"); }) .catch(err => { @@ -72,9 +67,12 @@ export default function LibraCartAccess() { // }, [searchParams, libracartCode]); useEffect(() => { - let code = new URLSearchParams(window.location.search).get("state"); // not sure if the state is correct here i thought it should be the access_token - if (code) { + 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]); @@ -133,7 +131,14 @@ export default function LibraCartAccess() { fullWidth variant="outlined" value={libracartCode} - onChange={e => setLibraCartCode(e.target.value)} + /> + ); diff --git a/src/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx index 28f41e6..3b056c1 100644 --- a/src/providers/pond/libracartProxyAPI.tsx +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -11,6 +11,7 @@ export interface ILibraCartProxyAPIContext { teamKey: string, userID: string, libracartCode: string, + libracartAuth: string, libracartUsername: string ) => Promise>; //list organizations @@ -51,6 +52,7 @@ export default function LibraCartProvider(props: PropsWithChildren) { teamKey: string, userID: string, libracartCode: string, + libracartAuth: string, libracartUsername: string ) => { return post( @@ -61,6 +63,8 @@ export default function LibraCartProvider(props: PropsWithChildren) { userID + "&code=" + libracartCode + + "&auth=" + + libracartAuth + "&libracartUsername=" + libracartUsername ) From c5091578d631be075ea2c10bb8635fae7e299ce9 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 25 Jul 2025 16:19:29 -0600 Subject: [PATCH 09/35] adding versioning for detect i2c on v2 devices --- src/models/Device.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/models/Device.ts b/src/models/Device.ts index e45f7aa..31fb95f 100644 --- a/src/models/Device.ts +++ b/src/models/Device.ts @@ -38,15 +38,15 @@ const featureVersions: Map = new Map([ { photon: "N/A", electron: "N/A", - v2Wifi: "N/A", - v2Cell: "N/A", - v2WifiS3: "N/A", - v2CellS3: "N/A", - v2CellBlack: "N/A", - v2CellGreen: "N/A", - v2WifiBlue: "N/A", - v2CellBlue: "N/A", - v2EthBlue: "N/A" + v2Wifi: "2.1.6", + v2Cell: "2.1.6", + v2WifiS3: "2.1.6", + v2CellS3: "2.1.6", + v2CellBlack: "2.1.6", + v2CellGreen: "2.1.6", + v2WifiBlue: "2.1.6", + v2CellBlue: "2.1.6", + v2EthBlue: "2.1.6" } ] ]); From 131c4ebb891f505b99039ae24bbb7188baeefc3c Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 25 Jul 2025 16:33:43 -0600 Subject: [PATCH 10/35] setting the deprecated boards according to Ashton that wont reach the version for the detectI2C feature to have it as N/A --- src/models/Device.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/models/Device.ts b/src/models/Device.ts index 31fb95f..46ce943 100644 --- a/src/models/Device.ts +++ b/src/models/Device.ts @@ -38,12 +38,12 @@ const featureVersions: Map = new Map([ { photon: "N/A", electron: "N/A", - v2Wifi: "2.1.6", - v2Cell: "2.1.6", - v2WifiS3: "2.1.6", - v2CellS3: "2.1.6", + v2Wifi: "N/A", + v2Cell: "N/A", + v2WifiS3: "N/A", + v2CellS3: "N/A", v2CellBlack: "2.1.6", - v2CellGreen: "2.1.6", + v2CellGreen: "N/A", v2WifiBlue: "2.1.6", v2CellBlue: "2.1.6", v2EthBlue: "2.1.6" From 934c9cb92b2dd9ec09ee23cda7b9c5366c099b7e Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 28 Jul 2025 14:40:28 -0600 Subject: [PATCH 11/35] fixed isse with calibration no longer being defined in the pond proto --- src/component/ComponentForm.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index ef61681..08d6117 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -482,7 +482,7 @@ export default function ComponentForm(props: Props) { const updateCalibrationTypes = (event: any, index: number) => { let f = cloneDeep(form); - f.component.settings.calibrations[index].type = event.target.value as quack.MeasurementType; + f.component.settings.calibrations[index].measurementType = event.target.value as quack.MeasurementType; setForm(f); }; @@ -508,7 +508,7 @@ export default function ComponentForm(props: Props) { if (dimensions !== undefined && dimensions !== null) { dimensions.lengthCm = value; } else { - dimensions = pond.Dimensions.create({ + dimensions = quack.Dimensions.create({ heightCm: 0, lengthCm: value, widthCm: 0 @@ -537,7 +537,7 @@ export default function ComponentForm(props: Props) { if (dimensions !== undefined && dimensions !== null) { dimensions.heightCm = value; } else { - dimensions = pond.Dimensions.create({ + dimensions = quack.Dimensions.create({ heightCm: value, lengthCm: 0, widthCm: 0 @@ -566,7 +566,7 @@ export default function ComponentForm(props: Props) { if (dimensions !== undefined && dimensions !== null) { dimensions.widthCm = value; } else { - dimensions = pond.Dimensions.create({ + dimensions = quack.Dimensions.create({ heightCm: 0, lengthCm: 0, widthCm: value @@ -788,7 +788,7 @@ export default function ComponentForm(props: Props) { // !isMeasurementTypeValid(interaction.settings.conditions[index].measurementType) // } disabled={!component.settings.calibrate} - value={calibration.type ?? quack.MeasurementType.MEASUREMENT_TYPE_INVALID} + value={calibration.measurementType ?? quack.MeasurementType.MEASUREMENT_TYPE_INVALID} onChange={event => updateCalibrationTypes(event, i)} autoFocus={false} margin="normal" @@ -814,8 +814,8 @@ export default function ComponentForm(props: Props) { f.coefficients.push("0"); f.offsets.push("0"); f.component.settings.calibrations.push( - pond.Calibration.create({ - type: quack.MeasurementType.MEASUREMENT_TYPE_INVALID, + quack.Calibration.create({ + measurementType: quack.MeasurementType.MEASUREMENT_TYPE_INVALID, calibrationOffset: 0, calibrationCoefficient: 0 }) From 4ab5b4aedd607997a95079a45e0f0ef8e8d90e4b Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 28 Jul 2025 14:45:06 -0600 Subject: [PATCH 12/35] added mux address as a blacklisted address so it doesn't appear on the card, and changed the i2c address for airflow --- .../autoDetect/DeviceScannedComponents.tsx | 21 ++++++++++++++++--- src/pbHelpers/AddressTypes/I2C.ts | 2 +- src/pbHelpers/DeviceAvailability.ts | 2 +- src/products/MiVent/MiVentAvailability.ts | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 03a847e..6899959 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,18 @@ 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)){ + valid.push(addrData) + } + }) + console.log(valid) + setValidComponentAddresses(valid) + },[scannedI2C]) + const stepper = () => { return ( @@ -205,7 +220,7 @@ 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 ( ) diff --git a/src/pbHelpers/AddressTypes/I2C.ts b/src/pbHelpers/AddressTypes/I2C.ts index 339ce70..2410950 100644 --- a/src/pbHelpers/AddressTypes/I2C.ts +++ b/src/pbHelpers/AddressTypes/I2C.ts @@ -16,7 +16,7 @@ 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, 0x29], + [quack.ComponentType.COMPONENT_TYPE_AIRFLOW, 0x6b], [quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE, 0x70] ]); diff --git a/src/pbHelpers/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index 72bbbdc..5e8807f 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -60,7 +60,7 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map Date: Mon, 28 Jul 2025 16:37:11 -0600 Subject: [PATCH 13/35] added to the filter to remove any expander entries when there is no expander line --- src/device/autoDetect/DeviceScannedComponents.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index 6899959..5e91dd0 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -67,10 +67,11 @@ export default function DeviceScannedComponents(props: Props){ //filter the address data scannedI2C?.settings?.foundAddresses.forEach(addrData => { if(!i2cBlacklistAddresses.includes(addrData.address)){ - valid.push(addrData) + if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){ + valid.push(addrData) + } } }) - console.log(valid) setValidComponentAddresses(valid) },[scannedI2C]) From f9ae15f2c8e751cc83e5927d32d6dcb18e151636 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 1 Aug 2025 15:17:55 -0600 Subject: [PATCH 14/35] added i2c as an option for drager components --- src/pbHelpers/AddressTypes/I2C.ts | 3 ++- .../ComponentTypes/DragerGasDongle.ts | 22 +++++++++---------- src/pbHelpers/DeviceAvailability.ts | 3 ++- src/products/MiVent/MiVentAvailability.ts | 4 +++- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/pbHelpers/AddressTypes/I2C.ts b/src/pbHelpers/AddressTypes/I2C.ts index 3c15ced..e6dd472 100644 --- a/src/pbHelpers/AddressTypes/I2C.ts +++ b/src/pbHelpers/AddressTypes/I2C.ts @@ -16,7 +16,8 @@ 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_DRAGER_GAS_DONGLE, 0x6c] ]); const offset = offsets.get(componentType); 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/DeviceAvailability.ts b/src/pbHelpers/DeviceAvailability.ts index 3d09a7f..7e99038 100644 --- a/src/pbHelpers/DeviceAvailability.ts +++ b/src/pbHelpers/DeviceAvailability.ts @@ -60,7 +60,8 @@ const DefaultAvailability: DeviceAvailabilityMap = new Map Date: Tue, 5 Aug 2025 13:32:01 -0600 Subject: [PATCH 15/35] trying to fix the marketplace not showing up on dev --- src/navigation/Router.tsx | 2 +- src/navigation/SideNavigator.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index f6badb1..6fa48c8 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")); diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index f71ca9c..379fe07 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 { isDevelopment, isStaging } from "utils"; const drawerWidth = 230; @@ -436,7 +437,7 @@ export default function SideNavigator(props: Props) { } - {(isAg || isStreamline) && + {(isAg || isStreamline || isStaging() || isDevelopment()) && Date: Tue, 5 Aug 2025 13:46:24 -0600 Subject: [PATCH 16/35] showing the marketplace stuff regardless of whitelabel if the user has admin --- src/navigation/SideNavigator.tsx | 3 +-- src/userFeatures/UserFeatures.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index 379fe07..86f823f 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -48,7 +48,6 @@ 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 { isDevelopment, isStaging } from "utils"; const drawerWidth = 230; @@ -437,7 +436,7 @@ export default function SideNavigator(props: Props) { } - {(isAg || isStreamline || isStaging() || isDevelopment()) && + {(isAg || isStreamline || user.hasFeature("admin")) && { let list: ProductDetails[] = []; - if (IsAdaptiveAgriculture()) { + if (IsAdaptiveAgriculture() || user.hasFeature("admin")) { list = list.concat(agFeatureList); } // if(IsAdCon()){ From 5d9f4787a5874136e427081383b2283f609d63a8 Mon Sep 17 00:00:00 2001 From: Noor Date: Wed, 6 Aug 2025 09:29:38 -0600 Subject: [PATCH 17/35] minor update --- src/pages/Users.tsx | 3 ++- src/providers/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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/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"; From add87dca1c8257f8f747f54024db2b63496d1ce6 Mon Sep 17 00:00:00 2001 From: Saiyida Noor Date: Wed, 6 Aug 2025 15:30:39 +0000 Subject: [PATCH 18/35] moved all of the libracart suff from the old frontend into the new new, also... --- src/app/App.tsx | 3 +- .../marketplaceImages/LibraCartGrey.png | Bin 0 -> 45281 bytes .../LibraCart/LibraCartAccess.tsx | 191 ++++++++++++++++++ src/navigation/Router.tsx | 4 + src/navigation/SideNavigator.tsx | 15 ++ src/pages/CNHi.tsx | 2 +- src/pages/LibraCart.tsx | 148 ++++++++++++++ src/pages/Users.tsx | 3 +- src/products/CommonIcons/libracartIcon.tsx | 24 +++ src/providers/index.ts | 3 +- src/providers/pond/libracartProxyAPI.tsx | 137 +++++++++++++ src/providers/pond/pond.tsx | 16 +- src/userFeatures/UserFeatures.tsx | 9 + 13 files changed, 545 insertions(+), 10 deletions(-) create mode 100644 src/assets/marketplaceImages/LibraCartGrey.png create mode 100644 src/integrations/LibraCart/LibraCartAccess.tsx create mode 100644 src/pages/LibraCart.tsx create mode 100644 src/products/CommonIcons/libracartIcon.tsx create mode 100644 src/providers/pond/libracartProxyAPI.tsx 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 0000000000000000000000000000000000000000..331ac297778c42435f0e66f276fb771917f78b67 GIT binary patch literal 45281 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⪼^)J{g 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 literal 0 HcmV?d00001 diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx new file mode 100644 index 0000000..391a3fa --- /dev/null +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -0,0 +1,191 @@ +import { + Avatar, + Box, + Button, + DialogActions, + DialogContent, + DialogTitle, + Grid, + MenuItem, + TextField +} from "@mui/material"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { teamScope, User } from "models"; +import { pond } from "protobuf-ts/pond"; +import { useSnackbar, useUserAPI } from "providers"; +import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI"; +import React, { useEffect, useState } from "react"; +import { useLocation } from "react-router"; +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 integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; + const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; + + + 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 ( + + + To integrate with Libra Cart, navigate to Integrations page on Libra Cart and "Connect" to + Adaptive Agriculture App: https://staging.cloud.agrimatics.com/grain/integrations + {newOrgDialog()} + + ); +} diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 6fa48c8..afe12c5 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -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 86f823f..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; @@ -477,6 +478,20 @@ export default function SideNavigator(props: Props) { {open && } + } + {user.hasFeature("libra-cart") && + + goTo("/libracart")} + classes={getClasses("/libracart")} + > + + + + {open && } + + } ) 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..5fc06eb --- /dev/null +++ b/src/pages/LibraCart.tsx @@ -0,0 +1,148 @@ +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Button, + Checkbox, + FormControlLabel, + Grid2 as Grid, + MenuItem, + Select +} from "@mui/material"; +import { ExpandMore } from "@mui/icons-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 [fieldAccordion, setFieldAccordion] = useState(false); + 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.JDAccount.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 ( + + { + setFieldAccordion(expanded); + }}> + }>Field Data + + + + { + //setFields(!fields); + updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS); + }} + /> + } + /> + + + View your data from Libra Cart on your visual Farm + + + + + + ); + }; + 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/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/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..3b056c1 --- /dev/null +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -0,0 +1,137 @@ +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"; + +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>; + listFields: ( + limit: number, + offset: number, + // order?: "asc" | "desc", + // orderBy?: string, + // search?: string, + as?: string, + asRoot?: boolean + ) => Promise>; +} + +export const LibraCartProxyAPIContext = createContext( + {} as ILibraCartProxyAPIContext +); + +interface Props {} + +export default function LibraCartProvider(props: PropsWithChildren) { + const { children } = props; + 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 listFields = ( + limit: number, + offset: number, + // order?: "asc" | "desc", + // orderBy?: string, + // search?: string, + as?: string, + asRoot?: boolean + ) => { + return get( + pondURL( + "/libracartFields" + + "?limit=" + + limit + + "&offset=" + + offset + + (as ? "&as=" + as : "") + + (asRoot ? "&asRoot=" + asRoot.toString() : "") + ) + ); + }; + + const updateAccount = (key: string, options: pond.DataOption[], as?: string) => { + return put( + pondURL( + "/libracartAccounts/" + key + "?options=" + options.toString() + (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 8e879f9..0efc34d 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 NoteProvider from "providers/noteAPI"; @@ -86,11 +87,13 @@ export default function PondProvider(props: PropsWithChildren) { - - - {children} - - + + + + {children} + + + @@ -163,5 +166,6 @@ export { useUsageAPI, useKeyManagerAPI, useJohnDeereProxyAPI, - useCNHiProxyAPI + useCNHiProxyAPI, + useLibraCartProxyAPI }; diff --git a/src/userFeatures/UserFeatures.tsx b/src/userFeatures/UserFeatures.tsx index 85df209..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" } ]; From a4819bdfdfbe818b9a1d2882c76f6e9e44d0dbca Mon Sep 17 00:00:00 2001 From: Saiyida Noor Date: Wed, 6 Aug 2025 17:05:32 +0000 Subject: [PATCH 19/35] minor updates --- .../LibraCart/LibraCartAccess.tsx | 3 +- src/pages/LibraCart.tsx | 80 +++++++++---------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx index 391a3fa..cfeb0f3 100644 --- a/src/integrations/LibraCart/LibraCartAccess.tsx +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -184,8 +184,7 @@ export default function LibraCartAccess() { Link Libra Cart Account To integrate with Libra Cart, navigate to Integrations page on Libra Cart and "Connect" to - Adaptive Agriculture App: https://staging.cloud.agrimatics.com/grain/integrations - {newOrgDialog()} + Adaptive Agriculture App. ); } diff --git a/src/pages/LibraCart.tsx b/src/pages/LibraCart.tsx index 5fc06eb..90f47b0 100644 --- a/src/pages/LibraCart.tsx +++ b/src/pages/LibraCart.tsx @@ -32,9 +32,9 @@ export default function LibraCart() { libracartAPI .listAccounts(0, 0, as) .then(resp => { - let tempOrgs: Map = new Map(); + let tempOrgs: Map = new Map(); resp.data.accounts.forEach(org => { - let organization = pond.JDAccount.fromObject(org); + let organization = pond.LibraCartAccount.fromObject(org); tempOrgs.set(organization.key, organization); }); setOrganizations(tempOrgs); @@ -70,40 +70,40 @@ export default function LibraCart() { }); }; - const fieldOptions = () => { - return ( - - { - setFieldAccordion(expanded); - }}> - }>Field Data - - - - { - //setFields(!fields); - updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS); - }} - /> - } - /> - - - View your data from Libra Cart on your visual Farm - - - - - - ); - }; + // const fieldOptions = () => { + // return ( + // + // { + // setFieldAccordion(expanded); + // }}> + // }>Field Data + // + // + // + // { + // //setFields(!fields); + // updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS); + // }} + // /> + // } + // /> + // + // + // View your data from Libra Cart + // + // + // + // + // + // ); + // }; return ( @@ -117,7 +117,7 @@ export default function LibraCart() { Promise>; - listFields: ( + listDestinations: ( limit: number, offset: number, - // order?: "asc" | "desc", - // orderBy?: string, - // search?: string, - as?: string, - asRoot?: boolean - ) => Promise>; + libracartKey?: string, + as?: string + ) => Promise>; } export const LibraCartProxyAPIContext = createContext( @@ -91,28 +88,6 @@ export default function LibraCartProvider(props: PropsWithChildren) { ); }; - const listFields = ( - limit: number, - offset: number, - // order?: "asc" | "desc", - // orderBy?: string, - // search?: string, - as?: string, - asRoot?: boolean - ) => { - return get( - pondURL( - "/libracartFields" + - "?limit=" + - limit + - "&offset=" + - offset + - (as ? "&as=" + as : "") + - (asRoot ? "&asRoot=" + asRoot.toString() : "") - ) - ); - }; - const updateAccount = (key: string, options: pond.DataOption[], as?: string) => { return put( pondURL( @@ -121,13 +96,21 @@ export default function LibraCartProvider(props: PropsWithChildren) { ); }; + const listDestinations = (limit: number, offset: number, libracartKey?: string, as?: string) => { + return get( + pondURL( + "/libracartDestinations?limit" + limit + "&offset=" + offset + (libracartKey ? "&libracartKey=" + libracartKey : "") + (as ? "&as=" + as : "") + ) + ) + } + return ( {children} From 714a2211549985518cfe1b61536f27878bdeca78 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 13 Aug 2025 10:18:56 -0600 Subject: [PATCH 21/35] removed some logs and added to the logic in fillPercent in the bin model --- src/models/Bin.ts | 4 ++-- src/pages/LibraCart.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/models/Bin.ts b/src/models/Bin.ts index 1b855a8..603a9ce 100644 --- a/src/models/Bin.ts +++ b/src/models/Bin.ts @@ -126,7 +126,6 @@ export class Bin { } public bushels(): number { - console.log(this) let control = this.settings.inventory?.inventoryControl; let bushels = this.settings.inventory?.grainBushels || 0 if (control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR || @@ -140,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/pages/LibraCart.tsx b/src/pages/LibraCart.tsx index cbb006f..4e93012 100644 --- a/src/pages/LibraCart.tsx +++ b/src/pages/LibraCart.tsx @@ -28,7 +28,6 @@ export default function LibraCart() { .listAccounts(0, 0, as) .then(resp => { let tempOrgs: Map = new Map(); - console.log(resp) resp.data.accounts.forEach(org => { let organization = pond.LibraCartAccount.fromObject(org); tempOrgs.set(organization.key, organization); From 7029a18d9cdad06a8fbbdeb7930bebd04b32fdba Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 13 Aug 2025 10:23:02 -0600 Subject: [PATCH 22/35] making sure so use stagings protobuf --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b89d4cd..81f47c4 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#libracart", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "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#c68c727be5b1076bbb534996a13d5e3285c6ca68", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#0e34386a27695f7cd98099015870a19c38f62bc6", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/package.json b/package.json index 89efa4d..fc7a1ac 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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#libracart", + "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", From 31af3ffa65dc7607ae66bedc72b084c5d53d5d6e Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 13 Aug 2025 14:28:24 -0600 Subject: [PATCH 23/35] hard coding in the url for the libracart button --- src/integrations/LibraCart/LibraCartAccess.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx index fae3362..d075b8e 100644 --- a/src/integrations/LibraCart/LibraCartAccess.tsx +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -34,8 +34,6 @@ export default function LibraCartAccess() { //const [dataOps, setDataOps] = useState([]); const { openSnack } = useSnackbar(); //const [{ as }] = useGlobalState(); - // const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; - const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; const submitNewOrganization = () => { @@ -183,7 +181,7 @@ export default function LibraCartAccess() { color="primary" onClick={e => { e.preventDefault(); - window.open(`${integration_url}`, "_blank"); + window.open(`https://staging.cloud.agrimatics.com/grain/integrations`, "_blank"); }}> Link Libra Cart Account From c5541c5366b0cc2fde23db4abecc6f709a224efe Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 14 Aug 2025 09:16:46 -0600 Subject: [PATCH 24/35] putting the background colour back in for the responsive table sticky header --- src/common/ResponsiveTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index 79238ea..ef99665 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -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 } From 050225bdb1d3e136880ec8a144dcd6f2ba577d40 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 14 Aug 2025 14:00:33 -0600 Subject: [PATCH 25/35] adding little description about the data sync and adding a sync button --- package-lock.json | 2 +- .../LibraCart/LibraCartAccess.tsx | 55 ++++++++++++------- src/providers/pond/libracartProxyAPI.tsx | 10 +++- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index b89d4cd..34a1da0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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#c68c727be5b1076bbb534996a13d5e3285c6ca68", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#8308e3c2a3bbc296d152b2df83246de3d0df4321", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/integrations/LibraCart/LibraCartAccess.tsx b/src/integrations/LibraCart/LibraCartAccess.tsx index fae3362..5d31b66 100644 --- a/src/integrations/LibraCart/LibraCartAccess.tsx +++ b/src/integrations/LibraCart/LibraCartAccess.tsx @@ -15,7 +15,7 @@ import { import ResponsiveDialog from "common/ResponsiveDialog"; import { teamScope, User } from "models"; import { pond } from "protobuf-ts/pond"; -import { useSnackbar, useUserAPI } from "providers"; +import { useGlobalState, useSnackbar, useUserAPI } from "providers"; import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI"; import React, { useEffect, useState } from "react"; import TeamSearch from "teams/TeamSearch"; @@ -33,7 +33,7 @@ export default function LibraCartAccess() { const libracartAPI = useLibraCartProxyAPI(); //const [dataOps, setDataOps] = useState([]); const { openSnack } = useSnackbar(); - //const [{ as }] = useGlobalState(); + const [{ as }] = useGlobalState(); // const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL; const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL; @@ -176,28 +176,45 @@ export default function LibraCartAccess() { 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/providers/pond/libracartProxyAPI.tsx b/src/providers/pond/libracartProxyAPI.tsx index d4f8bb5..b56f65b 100644 --- a/src/providers/pond/libracartProxyAPI.tsx +++ b/src/providers/pond/libracartProxyAPI.tsx @@ -4,6 +4,7 @@ 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 @@ -33,6 +34,7 @@ export interface ILibraCartProxyAPIContext { libracartKey?: string, as?: string ) => Promise>; + syncData: () => Promise } export const LibraCartProxyAPIContext = createContext( @@ -43,6 +45,7 @@ interface Props {} export default function LibraCartProvider(props: PropsWithChildren) { const { children } = props; + const [{as}] = useGlobalState(); const { post, get, put } = useHTTP(); const addAccount = ( @@ -104,13 +107,18 @@ export default function LibraCartProvider(props: PropsWithChildren) { ) } + const syncData = () => { + return get(pondURL("/libracartImport" + (as ? "?as=" + as : ""))) + } + return ( {children} From e57f75bf5664457f694e3793a3aa159195e93fab Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 15 Aug 2025 11:18:19 -0600 Subject: [PATCH 26/35] set up an areea chart for bin levels over time if there are more than 10 changes since the bars get kinda cluttered --- src/bin/BinSettings.tsx | 2 - src/bin/graphs/BinLevelAreaGraph.tsx | 67 ++++++++++++++++++++++++++++ src/bin/graphs/BinLevelOverTime.tsx | 42 ++++++++++------- 3 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 src/bin/graphs/BinLevelAreaGraph.tsx diff --git a/src/bin/BinSettings.tsx b/src/bin/BinSettings.tsx index 2c97be1..f970b2b 100644 --- a/src/bin/BinSettings.tsx +++ b/src/bin/BinSettings.tsx @@ -374,7 +374,6 @@ export default function BinSettings(props: Props) { let options: Option[] = [] libracartAPI.listDestinations(0,0,undefined, as) .then(resp=>{ - console.log(resp) //set the options for the search select resp.data.destinations.forEach(d => { let newOp: Option = { @@ -486,7 +485,6 @@ 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 => { 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 63bb51d..33212f5 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -9,6 +9,7 @@ import { } from "@mui/material"; import { blue, grey, red, yellow, orange } from "@mui/material/colors"; import BarGraph, { BarData, RefArea } from "charts/BarGraph"; +import MultiLineGraph, { LineData } from "charts/measurementCharts/MultiLineGraph"; import { Bin } from "models"; import moment, { Moment } from "moment"; import { pond, quack } from "protobuf-ts/pond"; @@ -16,6 +17,7 @@ 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,11 +29,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 [inventoryData, setInventoryData] = useState([]); const [modeAreas, setModeAreas] = useState([]); const [dataLoading, setDataLoading] = useState(false); const [capacity, setCapacity] = useState(); @@ -102,7 +109,7 @@ export default function BinLevelOverTime(props: Props) { fill: getFill(settings.mode) }); currentMode = settings.mode; - let newData: BarData = { + let newData: InventoryAt = { timestamp: moment(hist.timestamp).valueOf(), value: fertilizerBin ? Math.round(bushels * 35.239) @@ -135,7 +142,7 @@ export default function BinLevelOverTime(props: Props) { fill: getFill(bin.settings.mode) }); } - setData(data); + setInventoryData(data); setModeAreas(modeAreas); }) .finally(() => { @@ -191,7 +198,7 @@ export default function BinLevelOverTime(props: Props) { timestamp: currentTime }); } - setData(autoBarData); + setInventoryData(autoBarData); }) .catch(err => {}) .finally(() => { @@ -271,23 +278,31 @@ 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 || - bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART) && autoBinModeChart()} + {binModeChart()} ); } From 2bd04acb6887bf17ecd2c84fd6717e76e9d570c6 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 15 Aug 2025 11:36:54 -0600 Subject: [PATCH 27/35] fixed up the mode graph --- src/bin/graphs/BinLevelOverTime.tsx | 59 ++++++++++------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 33212f5..58ce4d6 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -1,18 +1,13 @@ 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 MultiLineGraph, { LineData } from "charts/measurementCharts/MultiLineGraph"; +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"; @@ -39,7 +34,6 @@ export default function BinLevelOverTime(props: Props) { const binAPI = useBinAPI(); const [{as}] = useGlobalState(); const [inventoryData, setInventoryData] = useState([]); - const [modeAreas, setModeAreas] = useState([]); const [dataLoading, setDataLoading] = useState(false); const [capacity, setCapacity] = useState(); @@ -83,32 +77,14 @@ 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; + if (bushels !== lastBushels) { let newData: InventoryAt = { timestamp: moment(hist.timestamp).valueOf(), value: fertilizerBin @@ -121,6 +97,17 @@ 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) + }) + } }); if (data.length === 0) { let bushels = bin.settings.inventory?.grainBushels ?? 0; @@ -133,17 +120,9 @@ 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) - }); } setInventoryData(data); - setModeAreas(modeAreas); + setModeData(modeData) }) .finally(() => { setDataLoading(false); @@ -290,7 +269,6 @@ export default function BinLevelOverTime(props: Props) { { + console.log(modeData) return ( Date: Fri, 15 Aug 2025 15:51:12 -0600 Subject: [PATCH 28/35] correction for when there is no data for the graph to use the correct inventory (settings or status) --- src/bin/graphs/BinLevelOverTime.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 58ce4d6..1756a60 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -110,7 +110,7 @@ export default function BinLevelOverTime(props: Props) { } }); if (data.length === 0) { - let bushels = bin.settings.inventory?.grainBushels ?? 0; + let bushels = bin.bushels(); let currentTime = moment().valueOf(); data.push({ value: fertilizerBin @@ -166,7 +166,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 From 497e81fc4baa9173ed862986e147cb621daed359 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 18 Aug 2025 14:08:20 -0600 Subject: [PATCH 29/35] fixed bug that was causing the reading in the headers of the bin sensro graphs to be converted evertime the tab changed to "Sensors" --- src/bin/graphs/BinComponentGraph.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/graphs/BinComponentGraph.tsx b/src/bin/graphs/BinComponentGraph.tsx index 6b593a9..3c5a625 100644 --- a/src/bin/graphs/BinComponentGraph.tsx +++ b/src/bin/graphs/BinComponentGraph.tsx @@ -184,7 +184,7 @@ export default function BinComponentGraph(props: Props) { UnitMeasurement.create(m, user)) + lastMeasurement.map(m => UnitMeasurement.create(m, user)) )} /> } From 441e02f65a1657cd734888d6b370d6dc0c5d807f Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 18 Aug 2025 16:11:32 -0600 Subject: [PATCH 30/35] fixed the Duplicate Bin button in the bin actions by importing the BinDuplication react component from the old frontend --- src/bin/BinActions.tsx | 7 ++-- src/bin/BinDuplication.tsx | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/bin/BinDuplication.tsx diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index 5f4d93a..e467a8b 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"; @@ -166,6 +166,7 @@ export default function BinActions(props: Props) { { + console.log("duplicate") setOpenState({ ...openState, duplication: true }); setAnchorEl(null); }} @@ -269,14 +270,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)} + /> + + + + + + + ); +} From 44f227c3e9bb4bd86f0236ffe9e1f3d77b8287fa Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 18 Aug 2025 16:50:53 -0600 Subject: [PATCH 31/35] removing some logs --- src/bin/BinActions.tsx | 1 - src/bin/graphs/BinLevelOverTime.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index e467a8b..a425ea6 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -166,7 +166,6 @@ export default function BinActions(props: Props) { { - console.log("duplicate") setOpenState({ ...openState, duplication: true }); setAnchorEl(null); }} diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 1756a60..59c9bae 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -281,7 +281,6 @@ export default function BinLevelOverTime(props: Props) { }; const binModeChart = () => { - console.log(modeData) return ( Date: Mon, 18 Aug 2025 16:55:49 -0600 Subject: [PATCH 32/35] fix for when there are no changes to the mode over the set period of time --- src/bin/graphs/BinLevelOverTime.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bin/graphs/BinLevelOverTime.tsx b/src/bin/graphs/BinLevelOverTime.tsx index 59c9bae..d9c98cf 100644 --- a/src/bin/graphs/BinLevelOverTime.tsx +++ b/src/bin/graphs/BinLevelOverTime.tsx @@ -109,9 +109,9 @@ export default function BinLevelOverTime(props: Props) { }) } }); + let currentTime = moment().valueOf(); if (data.length === 0) { let bushels = bin.bushels(); - let currentTime = moment().valueOf(); data.push({ value: fertilizerBin ? Math.round(bushels * 35.239) @@ -121,6 +121,13 @@ export default function BinLevelOverTime(props: Props) { timestamp: currentTime }); } + if(modeData.length === 0){ + modeData.push({ + timestamp: currentTime, + value: 1, + fill: getFill(bin.settings.mode) + }) + } setInventoryData(data); setModeData(modeData) }) @@ -206,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 => { From b3245ad1bc27bb95d162aac48395938afc6e2662 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 19 Aug 2025 11:00:26 -0600 Subject: [PATCH 33/35] fixed the function definition since it was preventing cloning of components for the support page --- src/models/Component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/Component.ts b/src/models/Component.ts index 9b96599..473d85d 100644 --- a/src/models/Component.ts +++ b/src/models/Component.ts @@ -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 && From b3569efc5fa224e0ecbb5b82bf7048bd5be62d7d Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 19 Aug 2025 14:24:55 -0600 Subject: [PATCH 34/35] added a use effect to update the value in the time when the initial value in the component changes --- src/common/time/PeriodSelect.tsx | 7 +++++++ 1 file changed, 7 insertions(+) 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); From 7971b0a2180642f175b5adfbba0b54e678cab4ff Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 20 Aug 2025 10:22:28 -0600 Subject: [PATCH 35/35] fixed the bin cards so that mobil now has 2 per row again --- src/bin/BinCardV2.tsx | 3 -- src/bin/BinsList.tsx | 18 +----------- src/pages/Bins.tsx | 66 ------------------------------------------- 3 files changed, 1 insertion(+), 86 deletions(-) diff --git a/src/bin/BinCardV2.tsx b/src/bin/BinCardV2.tsx index 35fcd12..e559c11 100644 --- a/src/bin/BinCardV2.tsx +++ b/src/bin/BinCardV2.tsx @@ -506,9 +506,6 @@ export default function BinCard(props: Props) { + )} - {/* - - */} { 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/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"); - }}> - - - */} {