From cb5e2a6ba4e9c451c46b03a2eed9091af0c737e1 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 11 Jul 2025 14:12:08 -0600 Subject: [PATCH 01/63] 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 02/63] 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 a0a54bee2aee697d957fe792c326fc2a4849b044 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 17 Jul 2025 13:42:11 -0600 Subject: [PATCH 03/63] 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 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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..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 04/63] 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 05/63] 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 06/63] 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 07/63] 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 08/63] 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 ad479e2b452d2b17e7b56d05f7ed1a487c06372e Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 5 Aug 2025 10:34:20 -0600 Subject: [PATCH 09/63] added in the device preset stuff for bin modes since it was missed in the initial update to the new platform --- src/device/presets/devicePresetCard.tsx | 108 ++++++++--------- src/providers/pond/devicePresetAPI.tsx | 150 ++++++++++++++++++++++++ src/providers/pond/pond.tsx | 8 +- 3 files changed, 210 insertions(+), 56 deletions(-) create mode 100644 src/providers/pond/devicePresetAPI.tsx diff --git a/src/device/presets/devicePresetCard.tsx b/src/device/presets/devicePresetCard.tsx index b01def2..2839df3 100644 --- a/src/device/presets/devicePresetCard.tsx +++ b/src/device/presets/devicePresetCard.tsx @@ -6,7 +6,7 @@ import { DialogActions, DialogContent, DialogTitle, - Grid, + Grid2 as Grid, InputAdornment, MenuItem, Slider, @@ -19,7 +19,7 @@ import { DevicePreset } from "models/DevicePreset"; import { ObjectTypeString } from "objects/ObjectDescriber"; import { pond } from "protobuf-ts/pond"; import { useSnackbar } from "providers"; -// import { useDevicePresetAPI } from "providers/pond/devicePresetAPI"; +import { useDevicePresetAPI } from "providers/pond/devicePresetAPI"; import React, { useEffect, useState } from "react"; import { getTemperatureUnit } from "utils"; @@ -43,7 +43,7 @@ export default function DevicePresetCard(props: Props) { removeCallback, updateCallback } = props; - // const devicePresetAPI = useDevicePresetAPI(); + const devicePresetAPI = useDevicePresetAPI(); const [presetName, setPresetName] = useState(""); const [presetType, setPresetType] = useState(1); const [controllerType, setControllerType] = useState(1); @@ -81,22 +81,22 @@ export default function DevicePresetCard(props: Props) { p.settings.temperature = tempC; p.settings.humidity = hum; - // devicePresetAPI - // .addDevicePreset(p.name, p.settings, objectKey, typestring) - // .then(resp => { - // //get the temp key from the preset - // let temp = p.key; - // //update the key in the preset with the key that came back from the add - // p.key = resp.data.key; - // //use the callback function passing in the preset and the temp key - // if (addCallback) { - // addCallback(p, temp); - // } - // openSnack("New preset saved"); - // }) - // .catch(err => { - // openSnack("Failed to save new preset"); - // }); + devicePresetAPI + .addDevicePreset(p.name, p.settings, objectKey, typestring) + .then(resp => { + //get the temp key from the preset + let temp = p.key; + //update the key in the preset with the key that came back from the add + p.key = resp.data.key; + //use the callback function passing in the preset and the temp key + if (addCallback) { + addCallback(p, temp); + } + openSnack("New preset saved"); + }) + .catch(err => { + openSnack("Failed to save new preset"); + }); }; const updatePreset = () => { @@ -108,32 +108,32 @@ export default function DevicePresetCard(props: Props) { newPreset.settings.temperature = tempC; newPreset.settings.humidity = hum; - // devicePresetAPI - // .updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings) - // .then(resp => { - // if (updateCallback) { - // updateCallback(oldPreset, newPreset); - // } - // openSnack("Preset Updated"); - // }) - // .catch(err => { - // openSnack("There was a problem updating the preset"); - // }); + devicePresetAPI + .updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings) + .then(resp => { + if (updateCallback) { + updateCallback(oldPreset, newPreset); + } + openSnack("Preset Updated"); + }) + .catch(err => { + openSnack("There was a problem updating the preset"); + }); }; const removePreset = () => { - // devicePresetAPI - // .removeDevicePreset(preset.key) - // .then(resp => { - // openSnack("Preset has been deleted"); - // setOpenDeleteConfirmation(false); - // if (removeCallback) { - // removeCallback(preset); - // } - // }) - // .catch(err => { - // openSnack("There was a problem deleting this preset"); - // }); + devicePresetAPI + .removeDevicePreset(preset.key) + .then(resp => { + openSnack("Preset has been deleted"); + setOpenDeleteConfirmation(false); + if (removeCallback) { + removeCallback(preset); + } + }) + .catch(err => { + openSnack("There was a problem deleting this preset"); + }); }; const deleteConfirmation = () => { @@ -192,7 +192,7 @@ export default function DevicePresetCard(props: Props) { {deleteConfirmation()} - + - + {presetType !== pond.PresetType.PRESET_TYPE_UNKNOWN && controllerType !== pond.ControllerType.CONTROLLER_TYPE_UNKNOWN && ( @@ -216,7 +216,7 @@ export default function DevicePresetCard(props: Props) { )} - + {/* type */} - + {/* controller type */} {/* temp slider */} {isMobile ? ( - + ) : ( - + Temperature - + - + + ) : ( - + Humidity - + - + Promise>; + //list + listDevicePresets: ( + limit: number, + offset: number, + order?: "asc" | "desc", + orderBy?: string, + search?: string, + keys?: string[], + types?: string[] + ) => Promise>; + //update + updateDevicePreset: ( + key: string, + name: string, + settings: pond.DevicePresetSettings + ) => Promise>; + + //remove + removeDevicePreset: (key: string) => Promise>; + + //get + getDevicePreset: (key: string) => Promise>; +} + +export const DevicePresetAPIcontext = createContext( + {} as IDevicePresetInterface +); + +interface Props {} + +export default function DevicePresetProvider(props: PropsWithChildren) { + const { children } = props; + const { get, del, post, put } = useHTTP(); + const [{ as }] = useGlobalState(); + + //add + const addDevicePreset = ( + name: string, + settings: pond.DevicePresetSettings, + parentKey?: string, + parentType?: string + ) => { + let key = ""; + let type = ""; + if (parentKey && parentType) { + key = "&parentKey=" + parentKey; + type = "&parentType=" + parentType; + } + if (as) { + return post( + pondURL("/devicePreset?name=" + name + "&as=" + as + key + type), + settings + ); + } + return post( + pondURL("/devicePreset?name=" + name + key + type), + settings + ); + }; + //list + const listDevicePresets = ( + limit: number, + offset: number, + order?: "asc" | "desc", + orderBy?: string, + search?: string, + keys?: string[], + types?: string[] + ) => { + let asText = ""; + if (as) asText = "&as=" + as; + return get( + pondURL( + "/devicePresets?limit=" + + limit + + "&offset=" + + offset + + ("&order=" + (order ? order : "asc")) + + ("&by=" + (orderBy ? orderBy : "key")) + + (search ? "&search=" + search : "") + + (keys ? "&keys=" + keys.toString() : "") + + (types ? "&types=" + types.toString() : "") + + asText + ) + ); + }; + //update + const updateDevicePreset = (key: string, name: string, settings: pond.DevicePresetSettings) => { + if (as) { + return put( + pondURL("/devicePreset/" + key + "?as=" + as + "&name=" + name), + settings + ); + } + return put( + pondURL("/devicePreset/" + key + "?name=" + name), + settings + ); + }; + + //remove + const removeDevicePreset = (key: string) => { + if (as) { + return del(pondURL("/devicePreset/" + key + "?as=" + as)); + } + return del(pondURL("/devicePreset/" + key + "?as=" + as)); + }; + + //get + const getDevicePreset = (key: string) => { + if (as) { + return del(pondURL("/devicePreset/" + key + "?as=" + as)); + } + return del(pondURL("/devicePreset/" + key + "?as=" + as)); + }; + return ( + + {children} + + ); +} + +export const useDevicePresetAPI = () => useContext(DevicePresetAPIcontext); diff --git a/src/providers/pond/pond.tsx b/src/providers/pond/pond.tsx index 8e879f9..6402ae4 100644 --- a/src/providers/pond/pond.tsx +++ b/src/providers/pond/pond.tsx @@ -36,6 +36,7 @@ import UsageProvider, { useUsageAPI } from "./usageAPI"; import KeyManagerProvider, { useKeyManagerAPI } from "./keyManagerAPI"; import JohnDeereProvider, { useJohnDeereProxyAPI } from "./johnDeereProxyAPI"; import CNHiProvider, { useCNHiProxyAPI } from "./cnhiProxyAPI"; +import DevicePresetProvider, { useDevicePresetAPI } from "./devicePresetAPI"; // import NoteProvider from "providers/noteAPI"; export const pondURL = (partial: string, demo: boolean = false): string => { @@ -88,7 +89,9 @@ export default function PondProvider(props: PropsWithChildren) { - {children} + + {children} + @@ -163,5 +166,6 @@ export { useUsageAPI, useKeyManagerAPI, useJohnDeereProxyAPI, - useCNHiProxyAPI + useCNHiProxyAPI, + useDevicePresetAPI }; From 814054e5a3cd9a91705b1640676052043436324f Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 5 Aug 2025 13:32:01 -0600 Subject: [PATCH 10/63] 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 11/63] 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 12/63] 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 13/63] 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 14/63] 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 17/63] 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 18/63] 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 19/63] 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 85ccecdcdc1906644f7dd80f31cf058dce2ca274 Mon Sep 17 00:00:00 2001 From: Carter Date: Wed, 13 Aug 2025 14:37:38 -0600 Subject: [PATCH 20/63] hotfix: if getting user with team fails, just get the user --- package-lock.json | 2 +- src/app/UserWrapper.tsx | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb7d904..0e4f8f9 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#master", "query-string": "^9.2.1", "react": "^18.3.1", "react-beautiful-dnd": "^13.1.1", diff --git a/src/app/UserWrapper.tsx b/src/app/UserWrapper.tsx index 722705e..cb4cdf8 100644 --- a/src/app/UserWrapper.tsx +++ b/src/app/UserWrapper.tsx @@ -82,7 +82,20 @@ export default function UserWrapper(props: Props) { firmware: new Map() }) }).catch(() => { - setGlobal(globalDefault) + userAPI.getUser(user_id).then(user => { + setGlobal({ + user: user ? user : User.create(), + team: globalDefault.team, + as: "", + showErrors: false, + userTeamPermissions: [], + backgroundTasksComplete: false, + firmware: new Map() + }) + }).catch(() => { + setGlobal(globalDefault) + }) + }) .finally(() => { setLoading(false) From 2dde79970da30fe24635328cc9b122b628e9c566 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 13 Aug 2025 15:46:07 -0600 Subject: [PATCH 21/63] hotfix to have the duplicate button available to users with the installer feature flag --- src/bin/BinCardV2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/BinCardV2.tsx b/src/bin/BinCardV2.tsx index 35fcd12..683e598 100644 --- a/src/bin/BinCardV2.tsx +++ b/src/bin/BinCardV2.tsx @@ -490,7 +490,7 @@ export default function BinCard(props: Props) { return ( - {user.hasFeature("admin") && ( + {user.hasFeature("installer") && ( Date: Thu, 14 Aug 2025 09:16:46 -0600 Subject: [PATCH 22/63] 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 2e3192e9af773cc3fde753b90b428cbef0f24cb4 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 14 Aug 2025 09:18:09 -0600 Subject: [PATCH 23/63] hotfix - putting back the background colour for the resposive 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 24/63] 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 25/63] 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 26/63] 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 27/63] 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 28/63] 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 eae0628e8be9e149a64f9cdbe7b757b5c5993677 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 18 Aug 2025 14:59:32 -0600 Subject: [PATCH 29/63] hotfix for the plenum column on the devices page to be renamed and the bin page sensors graph headers reading going through multiple conversions --- src/bin/graphs/BinComponentGraph.tsx | 2 +- src/pages/Devices.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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)) )} /> } diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 7721ec1..673f097 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -472,7 +472,7 @@ export default function Devices() { ] if (hasPlenums) { columns.push({ - title: "Plenum", + title: "Temp/Humidity", // sortKey: "hi", // disableSort: true, render: (device: Device) => { From 441e02f65a1657cd734888d6b370d6dc0c5d807f Mon Sep 17 00:00:00 2001 From: csawatzky Date: Mon, 18 Aug 2025 16:11:32 -0600 Subject: [PATCH 30/63] 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/63] 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/63] 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/63] 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/63] 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 8a9d7af6ae3c1191a9a283d012be1dba43018348 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 19 Aug 2025 15:34:47 -0600 Subject: [PATCH 35/63] adding temp oats to the grain describer to have a user test which is more accurate --- src/grain/GrainDescriber.ts | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/grain/GrainDescriber.ts b/src/grain/GrainDescriber.ts index ce394b3..769826c 100644 --- a/src/grain/GrainDescriber.ts +++ b/src/grain/GrainDescriber.ts @@ -48,7 +48,7 @@ const defaultGrain: GrainExtension = { bushelsPerTonne: 0 }; -export const GrainExtensions: Map = new Map([ +export const GrainExtensions: Map = new Map([ [pond.Grain.GRAIN_INVALID, defaultGrain], [pond.Grain.GRAIN_NONE, defaultGrain], [ @@ -190,7 +190,7 @@ export const GrainExtensions: Map = new Map([ [ pond.Grain.GRAIN_OATS, { - name: "Oats", + name: "Oats a", group: "Oats", equation: Equation.henderson, a: 0.000085511, @@ -203,6 +203,40 @@ export const GrainExtensions: Map = new Map([ weightConversionKg: 15.4222988297197, bushelsPerTonne: 64.842 } + ], + [ + 29, + { + name: "Oats b", + group: "Oats", + equation: Equation.chungPfost, + a: 442.85, + b: 0.21228, + c: 35.803, + setTempC: 32.0, + targetMC: 13.6, + img: OatImg, + colour: "#79955a", + weightConversionKg: 15.4222988297197, + bushelsPerTonne: 64.842 + } + ], + [ + 30, + { + name: "Oats c", + group: "Oats", + equation: Equation.oswin, + a: 12.412, + b: -0.060707, + c: 2.9397, + setTempC: 32.0, + targetMC: 13.6, + img: OatImg, + colour: "#79955a", + weightConversionKg: 15.4222988297197, + bushelsPerTonne: 64.842 + } ], [ pond.Grain.GRAIN_PEANUTS, From e313fd10ae008d16322d8a1a904ccbb69151b6ba Mon Sep 17 00:00:00 2001 From: csawatzky Date: Tue, 19 Aug 2025 15:51:58 -0600 Subject: [PATCH 36/63] proto update for the test oats enum --- package-lock.json | 2 +- src/grain/GrainDescriber.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e4f8f9..fe947bc 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#0b7dd45fa720d6c3f20ac7bafac4a3f0bdbc7ab6", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#0cdf4d5e3038ef13318da0841886a7c9f0926f8d", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/grain/GrainDescriber.ts b/src/grain/GrainDescriber.ts index 769826c..c140a85 100644 --- a/src/grain/GrainDescriber.ts +++ b/src/grain/GrainDescriber.ts @@ -48,7 +48,7 @@ const defaultGrain: GrainExtension = { bushelsPerTonne: 0 }; -export const GrainExtensions: Map = new Map([ +export const GrainExtensions: Map = new Map([ [pond.Grain.GRAIN_INVALID, defaultGrain], [pond.Grain.GRAIN_NONE, defaultGrain], [ @@ -205,7 +205,7 @@ export const GrainExtensions: Map = new Map } ], [ - 29, + pond.Grain.GRAIN_OATS_B, { name: "Oats b", group: "Oats", @@ -222,7 +222,7 @@ export const GrainExtensions: Map = new Map } ], [ - 30, + pond.Grain.GRAIN_OATS_C, { name: "Oats c", group: "Oats", From 7971b0a2180642f175b5adfbba0b54e678cab4ff Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 20 Aug 2025 10:22:28 -0600 Subject: [PATCH 37/63] 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"); - }}> - - - */} { From 30806a61c47e82404101bb1a354b915cb4e37e23 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 20 Aug 2025 16:06:40 -0600 Subject: [PATCH 38/63] expanding the json file so the the new scd30 subtype for co2 has the mode/calibration part --- src/component/ComponentMode.json | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/component/ComponentMode.json b/src/component/ComponentMode.json index 16cea43..2c66a6e 100644 --- a/src/component/ComponentMode.json +++ b/src/component/ComponentMode.json @@ -38,6 +38,45 @@ ] } }, + { + "type": 12, + "subtype": 1, + "mode": { + "labelA": "Power Mode", + "entryA": "select", + "dictionaryA": [ + { + "key": "High Power", + "value": 1, + "restricted": false + }, + { + "key": "Low Power", + "value": 2, + "restricted": false + }, + { + "key": "Calibration", + "value": 3, + "restricted": true + } + ], + "labelB": "Number of Averages", + "entryB": "number", + "dictionaryB": [ + { + "key": "min", + "value": 1, + "restricted": false + }, + { + "key": "max", + "value": 8, + "restricted": false + } + ] + } + }, { "type": 28, "subtype": 0, From 731cf06e606ac7d1df98f3fed8a58c29bfbb7fb1 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 20 Aug 2025 16:13:13 -0600 Subject: [PATCH 39/63] change to the form so that if a device has individual calibrations it can still use the mode stuff --- src/component/ComponentForm.tsx | 168 +++++++++++++++++--------------- 1 file changed, 89 insertions(+), 79 deletions(-) diff --git a/src/component/ComponentForm.tsx b/src/component/ComponentForm.tsx index 08d6117..99568da 100644 --- a/src/component/ComponentForm.tsx +++ b/src/component/ComponentForm.tsx @@ -699,6 +699,86 @@ export default function ComponentForm(props: Props) { ); }; + const componentMode = () => { + const { component, coefficient, offset } = form; + return ( + + + + + } + label="Mode" + labelPlacement="top" + className={classes.switchControl} + disabled={!canEdit} + /> + + + + {compMode.mode.entryA && ( + + {compMode.mode.entryA === "select" && + compMode.mode.dictionaryA.map((elem: any) => { + if (!elem.restricted || (elem.restricted && user.hasAdmin())) { + return ( + + {elem.key} + + ); + } + return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined + })} + + )} + + + {compMode.mode.entryB && ( + + {compMode.mode.entryB === "select" && + compMode.mode.dictionaryB.map((elem: any) => ( + + {elem.key} + + ))} + + )} + + + ) + } + const describeSource = (measurementType: quack.MeasurementType): MeasurementDescriber => { const { component } = form; return describeMeasurement(measurementType, component.type(), component.subType()); @@ -1046,87 +1126,17 @@ export default function ComponentForm(props: Props) { } - {!compMode ? ( - device.featureSupported("individualCalibrations") ? ( - individualCalibration() - ) : ( - blanketCalibration() - ) - ) : ( + {device.featureSupported("individualCalibrations") ? ( - - - - } - label="Mode" - labelPlacement="top" - className={classes.switchControl} - disabled={!canEdit} - /> - - - - {compMode.mode.entryA && ( - - {compMode.mode.entryA === "select" && - compMode.mode.dictionaryA.map((elem: any) => { - if (!elem.restricted || (elem.restricted && user.hasAdmin())) { - return ( - - {elem.key} - - ); - } - return undefined; //TODO-CS: Time permitting re-think the structure of this function so that I don't have to return undefined - })} - - )} - - - {compMode.mode.entryB && ( - - {compMode.mode.entryB === "select" && - compMode.mode.dictionaryB.map((elem: any) => ( - - {elem.key} - - ))} - - )} - + {compMode && componentMode()} + {individualCalibration()} + ) : ( + !compMode ? ( + blanketCalibration() + ):( + componentMode() + ) )} Date: Thu, 21 Aug 2025 14:33:22 -0600 Subject: [PATCH 40/63] added cache-busting service-worker and removed caching for service-worker.js (vite uses sw.js) --- nginx.conf | 6 ++++++ public/service-worker.js | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 public/service-worker.js diff --git a/nginx.conf b/nginx.conf index b390bff..1537b32 100644 --- a/nginx.conf +++ b/nginx.conf @@ -71,6 +71,12 @@ http { add_header Cache-Control "no-store, no-cache, must-revalidate"; } + # Force re-cache of old service-worker + location = /service-worker.js { + root /usr/share/nginx/html; # adjust if your build folder is elsewhere + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + # Redirect old /index.html requests to new file location = /index.html { return 301 /indexV2.html; diff --git a/public/service-worker.js b/public/service-worker.js new file mode 100644 index 0000000..6b6a19c --- /dev/null +++ b/public/service-worker.js @@ -0,0 +1,24 @@ +// public/service-worker.js +self.addEventListener('install', () => self.skipWaiting()); + +self.addEventListener('activate', async () => { + // Unregister old CRA SW + const regs = await self.registration.scope ? [self.registration] : await self.clients.getRegistrations(); + for (const reg of regs) { + if (reg && reg.scriptURL.endsWith('service-worker.js')) { + await reg.unregister(); + } + } + + // Clear all caches + if ('caches' in self) { + const keys = await caches.keys(); + for (const key of keys) { + await caches.delete(key); + } + } + + // Reload all clients + const clients = await self.clients.matchAll({ type: 'window' }); + clients.forEach(client => client.navigate(client.url)); +}); From 27c69c3e553fc1acd8dc19fa680bc9affc16481a Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 22 Aug 2025 13:35:27 -0600 Subject: [PATCH 41/63] hotfix for the bin duplication in the bin actions of the bin page --- src/bin/BinActions.tsx | 6 ++-- src/bin/BinDuplication.tsx | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/bin/BinDuplication.tsx diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx index 5f4d93a..a425ea6 100644 --- a/src/bin/BinActions.tsx +++ b/src/bin/BinActions.tsx @@ -24,7 +24,7 @@ import RemoveSelfFromObject from "user/RemoveSelfFromObject"; import ShareObject from "user/ShareObject"; import { isOffline } from "utils/environment"; import ObjectTeams from "teams/ObjectTeams"; -// import BinDuplication from "./BinDuplication"; +import BinDuplication from "./BinDuplication"; import BinsIcon from "products/Bindapt/BinsIcon"; import HelpIcon from "@mui/icons-material/Help"; import BinSensors from "./BinSensors"; @@ -269,14 +269,14 @@ export default function BinActions(props: Props) { closeDialogCallback={() => setOpenState({ ...openState, users: false })} refreshCallback={refreshCallback} /> - {/* { setOpenState({ ...openState, duplication: false }); }} bin={bin} refreshCallback={refreshCallback} - /> */} + /> void; + refreshCallback: () => void; +} + +export default function BinDuplication(props: Props) { + const { open, bin, closeDialog, refreshCallback } = props; + const [binDupName, setBinDupName] = useState("(copy of) " + bin.name()); + const binAPI = useBinAPI(); + const { openSnack } = useSnackbar(); + + useEffect(() => { + setBinDupName("(copy of) " + bin.name()); + }, [bin]); + + const duplicate = () => { + let settings = bin.settings; + settings.name = binDupName; + binAPI + .addBin(settings) + .then(resp => { + openSnack("Successfully duplicated bin"); + }) + .catch(err => { + openSnack("Failed to duplicate bin"); + }); + refreshCallback(); + closeDialog(); + }; + + return ( + + Create Duplicate Bin + + + This will create a new bin with the same settings as {bin.name()}. + + setBinDupName(e.target.value)} + /> + + + + + + + ); +} From b2f3fac57e6a028e9f79c47a5b4ea9c925795007 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Fri, 22 Aug 2025 14:50:29 -0600 Subject: [PATCH 42/63] fixing the drying calculator by making sure to update the initial time when the mode is changed --- src/bin/BinVisualizerV2.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx index 0db4690..3e5e131 100644 --- a/src/bin/BinVisualizerV2.tsx +++ b/src/bin/BinVisualizerV2.tsx @@ -250,6 +250,7 @@ export default function BinVisualizer(props: Props) { const [cfmHighOpen, setCFMHighOpen] = useState(false); const [{ user }] = useGlobalState(); const [newPreset, setNewPreset] = useState(pond.BinMode.BIN_MODE_NONE); + const [newBinMode, setNewBinMode] = useState(pond.BinMode.BIN_MODE_NONE); const [selectedCable, setSelectedCable] = useState(); const [openNodeDialog, setOpenNodeDialog] = useState(false); const [cableDevice, setCableDevice] = useState(); @@ -345,6 +346,7 @@ export default function BinVisualizer(props: Props) { setGrainOption(ToGrainOption(bin.settings.inventory.grainType)); setBushPerTonne(bin.settings.inventory.bushelsPerTonne.toFixed(2)); setGrainSubtype(bin.subtype()); + setNewBinMode(bin.settings.mode); } if (bin.settings) { let t = bin.settings.outdoorTemp; @@ -1967,6 +1969,7 @@ export default function BinVisualizer(props: Props) { const setModeStorage = () => { setNewPreset(pond.BinMode.BIN_MODE_STORAGE); + setNewBinMode(pond.BinMode.BIN_MODE_STORAGE); }; const setModeCooldown = () => { @@ -1974,6 +1977,7 @@ export default function BinVisualizer(props: Props) { return; } setNewPreset(pond.BinMode.BIN_MODE_COOLDOWN); + setNewBinMode(pond.BinMode.BIN_MODE_COOLDOWN); }; const setModeDrying = () => { @@ -1982,13 +1986,16 @@ export default function BinVisualizer(props: Props) { bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture ) { setNewPreset(pond.BinMode.BIN_MODE_HYDRATING); + setNewBinMode(pond.BinMode.BIN_MODE_HYDRATING); } else { setNewPreset(pond.BinMode.BIN_MODE_DRYING); + setNewBinMode(pond.BinMode.BIN_MODE_DRYING); } }; const closeMoistureDialog = () => { setNewPreset(0); + setNewBinMode(bin.settings.mode); setShowInputMoisture(false); }; @@ -2049,6 +2056,7 @@ export default function BinVisualizer(props: Props) {