Merge branch 'libracart' into dev_environment

This commit is contained in:
csawatzky 2025-08-13 10:20:47 -06:00
commit a1ef6ea89b
9 changed files with 139 additions and 92 deletions

4
package-lock.json generated
View file

@ -42,7 +42,7 @@
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"notistack": "^3.0.1", "notistack": "^3.0.1",
"openweathermap-ts": "^1.2.10", "openweathermap-ts": "^1.2.10",
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#libracart",
"query-string": "^9.2.1", "query-string": "^9.2.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
@ -10911,7 +10911,7 @@
}, },
"node_modules/protobuf-ts": { "node_modules/protobuf-ts": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#1f0c0924fc9bca00e4512a29ffa75904cc8468c0", "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#c68c727be5b1076bbb534996a13d5e3285c6ca68",
"dependencies": { "dependencies": {
"protobufjs": "^6.8.8" "protobufjs": "^6.8.8"
} }

View file

@ -54,7 +54,7 @@
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"notistack": "^3.0.1", "notistack": "^3.0.1",
"openweathermap-ts": "^1.2.10", "openweathermap-ts": "^1.2.10",
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev", "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#libracart",
"query-string": "^9.2.1", "query-string": "^9.2.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",

View file

@ -58,7 +58,7 @@ import { Bin } from "models";
import moment from "moment"; import moment from "moment";
import { GetBinShapeDescribers } from "pbHelpers/Bin"; import { GetBinShapeDescribers } from "pbHelpers/Bin";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useBinAPI, useBinYardAPI, useGlobalState } from "providers"; import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
// import { useHistory } from "react-router"; // import { useHistory } from "react-router";
import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils"; import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
@ -176,7 +176,7 @@ export default function BinSettings(props: Props) {
const grainOptions = GrainOptions(); const grainOptions = GrainOptions();
const grainUseOptions = GetGrainUseOptions(); const grainUseOptions = GetGrainUseOptions();
const [inputCapacity, setInputCapacity] = useState<string>(""); const [inputCapacity, setInputCapacity] = useState<string>("");
const [{ as }] = useGlobalState(); const [{ user, as }] = useGlobalState();
const [grainDiff, setGrainDiff] = useState(0); const [grainDiff, setGrainDiff] = useState(0);
const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false); const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false);
const [grainUpdate, setGrainUpdate] = useState(false); const [grainUpdate, setGrainUpdate] = useState(false);
@ -197,10 +197,13 @@ export default function BinSettings(props: Props) {
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>( const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
); );
const [storageType, setStorageType] = useState<pond.BinStorage>( const [storageType, setStorageType] = useState<pond.BinStorage>(
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
); );
//libracart stuff
const libracartAPI = useLibraCartProxyAPI()
const [lcDestination, setlcDestination] = useState<Option | null>()
const [lcDestinationOptions, setlcDestinationOptions] = useState<Option[]>([])
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@ -365,9 +368,34 @@ export default function BinSettings(props: Props) {
} }
}, [binYardAPI, form.yardKey, userID, as, props.binYards]); }, [binYardAPI, form.yardKey, userID, as, props.binYards]);
const loadLibraCartDestinations = useCallback(()=>{
if(inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
//load the destinations for the user/team they are viewing as
let options: Option[] = []
libracartAPI.listDestinations(0,0,undefined, as)
.then(resp=>{
console.log(resp)
//set the options for the search select
resp.data.destinations.forEach(d => {
let newOp: Option = {
label: d.name,
value: d.id
}
options.push(newOp)
//set the current option based on the key in the form
if (d.id === form.libracartDestinationKey){
setlcDestination(newOp)
}
})
setlcDestinationOptions(options)
}).catch(()=>{})
}
},[libracartAPI, inventoryControl, as, form.libracartDestinationKey])
useEffect(() => { useEffect(() => {
loadBinYards(); loadBinYards();
}, [loadBinYards]); loadLibraCartDestinations();
}, [loadBinYards, loadLibraCartDestinations]);
useEffect(() => { useEffect(() => {
if (bin?.settings.specs?.bushelCapacity) { if (bin?.settings.specs?.bushelCapacity) {
@ -458,6 +486,7 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold form.inventory.autoThreshold = autoFillThreshold
} }
console.log(form)
binAPI binAPI
.updateBin(bin.key(), form, as) .updateBin(bin.key(), form, as)
.then(response => { .then(response => {
@ -713,6 +742,8 @@ export default function BinSettings(props: Props) {
return "Auto (cable)" return "Auto (cable)"
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR: case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
return "Hybrid (lidar)" return "Hybrid (lidar)"
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
return "Auto (LibraCart)"
default: default:
return "Manual" return "Manual"
} }
@ -860,7 +891,7 @@ export default function BinSettings(props: Props) {
<FormControlLabel <FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC} value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC}
control={<Radio />} control={<Radio />}
label={"Auto"} label={"Auto (Cable)"}
/> />
<FormControlLabel <FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR} value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR}
@ -872,6 +903,13 @@ export default function BinSettings(props: Props) {
control={<Radio />} control={<Radio />}
label={"Hybrid (Lidar)"} label={"Hybrid (Lidar)"}
/> />
{user.hasFeature("libra-cart") &&
<FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
control={<Radio />}
label={"Auto (LibraCart)"}
/>
}
</RadioGroup> </RadioGroup>
</AccordionDetails> </AccordionDetails>
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC && {inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
@ -912,6 +950,25 @@ export default function BinSettings(props: Props) {
/> />
</Box> </Box>
} }
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
<Box width="100%" padding={2}>
<SearchSelect
label="LibraCart Destination"
selected={lcDestination}
changeSelection={option => {
let newForm = form;
newForm.libracartDestinationKey = option?.value;
setForm(newForm);
setlcDestination(option ? option : null);
}}
disabled={!canEdit}
options={lcDestinationOptions}
/>
<Typography variant="caption">
The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours
</Typography>
</Box>
}
</Accordion> </Accordion>
{empty ? ( {empty ? (
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center"> <Box marginTop={3} display="flex" flexDirection="column" alignItems="center">

View file

@ -238,7 +238,8 @@ export default function BinLevelOverTime(props: Props) {
let control = bin.inventoryControl() let control = bin.inventoryControl()
//for automatic lidar and cables get the data from measurements as an object measurement //for automatic lidar and cables get the data from measurements as an object measurement
if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR || if(control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART
){ ){
//load the measurement data for the bar graph //load the measurement data for the bar graph
loadMeasurementData() loadMeasurementData()
@ -307,7 +308,8 @@ export default function BinLevelOverTime(props: Props) {
{dataLoading ? <CircularProgress /> : inventoryChart()} {dataLoading ? <CircularProgress /> : inventoryChart()}
{/* when using auto will need to make a new chart just for the bin modes since the auto inventory comes from another place */} {/* 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_LIDAR ||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC) && autoBinModeChart()} bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART) && autoBinModeChart()}
</Card> </Card>
); );
} }

View file

@ -1,3 +1,4 @@
import { Help } from "@mui/icons-material";
import { import {
Avatar, Avatar,
Box, Box,
@ -5,9 +6,11 @@ import {
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Grid, Grid2 as Grid,
MenuItem, MenuItem,
TextField TextField,
Tooltip,
Typography
} from "@mui/material"; } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { teamScope, User } from "models"; import { teamScope, User } from "models";
@ -15,7 +18,6 @@ import { pond } from "protobuf-ts/pond";
import { useSnackbar, useUserAPI } from "providers"; import { useSnackbar, useUserAPI } from "providers";
import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI"; import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useLocation } from "react-router";
import TeamSearch from "teams/TeamSearch"; import TeamSearch from "teams/TeamSearch";
export default function LibraCartAccess() { export default function LibraCartAccess() {
@ -109,7 +111,7 @@ export default function LibraCartAccess() {
{teamUsers.map(u => ( {teamUsers.map(u => (
<MenuItem key={u.id()} value={u.id()}> <MenuItem key={u.id()} value={u.id()}>
<Grid container direction="row" alignItems="center"> <Grid container direction="row" alignItems="center">
<Grid item> <Grid>
<Avatar <Avatar
alt={u.name()} alt={u.name()}
src={ src={
@ -117,7 +119,7 @@ export default function LibraCartAccess() {
} }
/> />
</Grid> </Grid>
<Grid item> <Grid>
<Box marginLeft={2}>{u.name()}</Box> <Box marginLeft={2}>{u.name()}</Box>
</Grid> </Grid>
</Grid> </Grid>
@ -174,6 +176,8 @@ export default function LibraCartAccess() {
return ( return (
<Box style={{ padding: 10 }}> <Box style={{ padding: 10 }}>
<Grid container direction="row" alignContent="center" alignItems="center" wrap="nowrap">
<Grid>
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
@ -183,8 +187,18 @@ export default function LibraCartAccess() {
}}> }}>
Link Libra Cart Account Link Libra Cart Account
</Button> </Button>
To integrate with Libra Cart, navigate to Integrations page on Libra Cart and "Connect" to </Grid>
Adaptive Agriculture App: https://staging.cloud.agrimatics.com/grain/integrations <Grid>
<Tooltip
title='The integration can be found by selecting my account in the corner, selecting the "Manage Account" option and then selecting integrations in the left side menu'
>
<Help />
</Tooltip>
</Grid>
</Grid>
{newOrgDialog()} {newOrgDialog()}
</Box> </Box>
); );

View file

@ -126,8 +126,10 @@ export class Bin {
} }
public bushels(): number { public bushels(): number {
let control = this.settings.inventory?.inventoryControl;
let bushels = this.settings.inventory?.grainBushels || 0 let bushels = this.settings.inventory?.grainBushels || 0
if (this.settings.inventory?.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){ if (control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
control === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
bushels = this.status.grainBushels bushels = this.status.grainBushels
} }
return bushels return bushels
@ -137,7 +139,8 @@ export class Bin {
let fill = 0; let fill = 0;
if (this.settings.inventory && this.settings.specs) { if (this.settings.inventory && this.settings.specs) {
let bushels = this.settings.inventory.grainBushels let bushels = this.settings.inventory.grainBushels
if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR){ if (this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
this.settings.inventory.inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
bushels = this.status.grainBushels bushels = this.status.grainBushels
} }
fill = Math.round( fill = Math.round(

View file

@ -1,7 +1,4 @@
import { import {
Accordion,
AccordionDetails,
AccordionSummary,
Box, Box,
Button, Button,
Checkbox, Checkbox,
@ -10,7 +7,6 @@ import {
MenuItem, MenuItem,
Select Select
} from "@mui/material"; } from "@mui/material";
import { ExpandMore } from "@mui/icons-material";
import LibraCartAccess from "integrations/LibraCart/LibraCartAccess"; import LibraCartAccess from "integrations/LibraCart/LibraCartAccess";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
@ -22,7 +18,6 @@ export default function LibraCart() {
const [currentOrg, setCurrentOrg] = useState(""); const [currentOrg, setCurrentOrg] = useState("");
const libracartAPI = useLibraCartProxyAPI(); const libracartAPI = useLibraCartProxyAPI();
const [organizations, setOrganizations] = useState<Map<string, pond.LibraCartAccount>>(new Map()); const [organizations, setOrganizations] = useState<Map<string, pond.LibraCartAccount>>(new Map());
const [fieldAccordion, setFieldAccordion] = useState(false);
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]); const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
const [{ as }] = useGlobalState(); const [{ as }] = useGlobalState();
@ -70,40 +65,31 @@ export default function LibraCart() {
}); });
}; };
// const fieldOptions = () => { const fieldOptions = () => {
// return ( return (
// <React.Fragment> <React.Fragment>
// <Accordion <Grid container direction="row" spacing={2} alignItems="center" alignContent="center">
// expanded={fieldAccordion} <Grid size={6}>
// onChange={(_, expanded) => { <FormControlLabel
// setFieldAccordion(expanded); label="Destinations"
// }}> control={
// <AccordionSummary expandIcon={<ExpandMore />}>Field Data</AccordionSummary> <Checkbox
// <AccordionDetails> checked={dataOptions.includes(pond.DataOption.DATA_OPTION_DESTINATIONS)}
// <Grid container direction="row" spacing={2} alignItems="center" alignContent="center"> onChange={(_, checked) => {
// <Grid size={6}> //setFields(!fields);
// <FormControlLabel updateOrgData(checked, pond.DataOption.DATA_OPTION_DESTINATIONS);
// label="Field Boundaries" }}
// control={ />
// <Checkbox }
// checked={dataOptions.includes(pond.DataOption.DATA_OPTION_FIELDS)} />
// onChange={(_, checked) => { </Grid>
// //setFields(!fields); <Grid size={6}>
// updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS); Import your destinations for the option to link it to a bin
// }} </Grid>
// /> </Grid>
// } </React.Fragment>
// /> );
// </Grid> };
// <Grid size={6}>
// View your data from Libra Cart
// </Grid>
// </Grid>
// </AccordionDetails>
// </Accordion>
// </React.Fragment>
// );
// };
return ( return (
<PageContainer> <PageContainer>
<LibraCartAccess /> <LibraCartAccess />
@ -136,13 +122,13 @@ export default function LibraCart() {
})} })}
</Select> </Select>
</Grid> </Grid>
{/* <Grid> <Grid>
<Button onClick={submit} variant="contained" color="primary"> <Button onClick={submit} variant="contained" color="primary">
Update Update
</Button> </Button>
</Grid> */}
</Grid> </Grid>
{/* <Box style={{ padding: 10 }}>{fieldOptions()}</Box> */} </Grid>
<Box style={{ padding: 10 }}>{fieldOptions()}</Box>
</PageContainer> </PageContainer>
); );
} }

View file

@ -27,15 +27,12 @@ export interface ILibraCartProxyAPIContext {
options: pond.DataOption[], options: pond.DataOption[],
as?: string as?: string
) => Promise<AxiosResponse<pond.UpdateLibraCartAccountResponse>>; ) => Promise<AxiosResponse<pond.UpdateLibraCartAccountResponse>>;
listFields: ( listDestinations: (
limit: number, limit: number,
offset: number, offset: number,
// order?: "asc" | "desc", libracartKey?: string,
// orderBy?: string, as?: string
// search?: string, ) => Promise<AxiosResponse<pond.ListLibraCartDestinationsResponse>>;
as?: string,
asRoot?: boolean
) => Promise<AxiosResponse<pond.ListLibraCartFieldsResponse>>;
} }
export const LibraCartProxyAPIContext = createContext<ILibraCartProxyAPIContext>( export const LibraCartProxyAPIContext = createContext<ILibraCartProxyAPIContext>(
@ -91,28 +88,6 @@ export default function LibraCartProvider(props: PropsWithChildren<Props>) {
); );
}; };
const listFields = (
limit: number,
offset: number,
// order?: "asc" | "desc",
// orderBy?: string,
// search?: string,
as?: string,
asRoot?: boolean
) => {
return get<pond.ListFieldsResponse>(
pondURL(
"/libracartFields" +
"?limit=" +
limit +
"&offset=" +
offset +
(as ? "&as=" + as : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "")
)
);
};
const updateAccount = (key: string, options: pond.DataOption[], as?: string) => { const updateAccount = (key: string, options: pond.DataOption[], as?: string) => {
return put<pond.UpdateLibraCartAccountResponse>( return put<pond.UpdateLibraCartAccountResponse>(
pondURL( pondURL(
@ -121,13 +96,21 @@ export default function LibraCartProvider(props: PropsWithChildren<Props>) {
); );
}; };
const listDestinations = (limit: number, offset: number, libracartKey?: string, as?: string) => {
return get<pond.ListLibraCartDestinationsResponse>(
pondURL(
"/libracartDestinations?limit" + limit + "&offset=" + offset + (libracartKey ? "&libracartKey=" + libracartKey : "") + (as ? "&as=" + as : "")
)
)
}
return ( return (
<LibraCartProxyAPIContext.Provider <LibraCartProxyAPIContext.Provider
value={{ value={{
addAccount, addAccount,
listAccounts, listAccounts,
updateAccount, updateAccount,
listFields listDestinations
}}> }}>
{children} {children}
</LibraCartProxyAPIContext.Provider> </LibraCartProxyAPIContext.Provider>

View file

@ -91,7 +91,9 @@ export default function PondProvider(props: PropsWithChildren<any>) {
<LibraCartProvider> <LibraCartProvider>
<UsageProvider> <UsageProvider>
<KeyManagerProvider> <KeyManagerProvider>
<DevicePresetProvider>
{children} {children}
</DevicePresetProvider>
</KeyManagerProvider> </KeyManagerProvider>
</UsageProvider> </UsageProvider>
</LibraCartProvider> </LibraCartProvider>