From 586c971795cb8a007b5cb634a64d0de8b93922b6 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 31 Dec 2025 13:58:38 -0600 Subject: [PATCH] finished implementing the custom grain functionality on the grains page and the basic desktop view, and also am now using permissions to disable the buttons ro add/update/remove --- package-lock.json | 2 +- src/grain/CustomGrainForm.tsx | 1 + src/pages/Grains.tsx | 244 +++++++++++++++++++++++++++++--- src/providers/pond/grainAPI.tsx | 18 ++- 4 files changed, 244 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccb9fd3..f6a295e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10953,7 +10953,7 @@ }, "node_modules/protobuf-ts": { "version": "1.0.0", - "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#2aa771366c36285eac26e4e0143c1759380e2fc9", + "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#917c54113cbb7e14d5037a647b9f6784bf3a8aad", "dependencies": { "protobufjs": "^6.8.8" } diff --git a/src/grain/CustomGrainForm.tsx b/src/grain/CustomGrainForm.tsx index ecdf899..2a0379a 100644 --- a/src/grain/CustomGrainForm.tsx +++ b/src/grain/CustomGrainForm.tsx @@ -30,6 +30,7 @@ export default function CustomGrainForm(props: Props) { setConstantC(grainSettings.c.toString()) setKgPerBushel(grainSettings.kgPerBushel.toString()) setBushelsPerTonne(grainSettings.bushelsPerTonne.toString()) + setNewGrainSettings(grainSettings) } },[grainSettings]) diff --git a/src/pages/Grains.tsx b/src/pages/Grains.tsx index 2b6b227..ae4b3ac 100644 --- a/src/pages/Grains.tsx +++ b/src/pages/Grains.tsx @@ -1,28 +1,144 @@ -import { useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import PageContainer from "./PageContainer"; import CustomGrainForm from "grain/CustomGrainForm"; -import { Box, Button, Grid2, Typography } from "@mui/material"; +import { Box, Button, DialogActions, DialogContent, DialogTitle, Grid2, Typography } from "@mui/material"; import { pond } from "protobuf-ts/pond"; import ButtonGroup from "common/ButtonGroup"; -import { useMobile } from "hooks"; +import { useMobile, useSnackbar, useUserAPI } from "hooks"; +import { useGrainAPI } from "providers/pond/grainAPI"; +import ResponsiveTable from "common/ResponsiveTable"; +import { GrainExtension } from "grain"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { useGlobalState } from "providers"; +import { Scope } from "models"; export default function Grains() { const [currentCustomGrain, setCurrentCustomGrain] = useState() + const [formValid, setFormValid] = useState(false) + const [selectedGrain, setSelectedGrain] = useState() const [displayCustom, setDisplayCustom] = useState(false) - const [grainTableData, setGrainTableData] = useState() + const { openSnack } = useSnackbar() + const [tableSize, setTableSize] = useState(5) + const [tablePage, setTablePage] = useState(0) + const [tableTotal, setTableTotal] = useState(0) + const grainAPI = useGrainAPI() + const userAPI = useUserAPI() + const [{as, user}] = useGlobalState(); + const [permissions, setPermissions] = useState([]) + const [openRemoveConfirmation, setOpenRemoveConfirmation] = useState(false) + const [openUpdateConfirmation, setOpenUpdateConfirmation] = useState(false) + const [grainObjectTableData, setGrainObjectTableData] = useState([]) + const [supportedGrainTableData, setSupportedGrainTableData] = useState([]) const isMobile = useMobile() //function to load the first set of custom grains - const loadCustomGrain = () => {} + const loadCustomGrain = useCallback(() => { + grainAPI.listGrains(tableSize, tablePage * tableSize, "asc", "name").then(resp => { + setGrainObjectTableData(resp.data.grains) + setTableTotal(resp.data.total) + }).catch(err => { + openSnack("There was an issue loading your custom grains") + }) + },[tableSize, tablePage]) + + useEffect(() => { + if(displayCustom){ + loadCustomGrain() + } + //if the user is viewing as a team get their permissions to the team, if they are not then we will get their permission to the grain typ individually when a type is selected + if(as){ + userAPI.getUser(user.id(), { key: as, kind: "team" } as Scope).then(resp => { + setPermissions(resp.permissions); + }); + } + },[loadCustomGrain, tableSize, tablePage, displayCustom, as]) + + const equationName = (enumVal: number) => { + switch(enumVal){ + case pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST: + return "Chung-Pfost" + case pond.MoistureEquation.MOISTURE_EQUATION_HALSEY: + return "Halsey" + case pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON: + return "Henderson" + case pond.MoistureEquation.MOISTURE_EQUATION_OSWIN: + return "Oswin" + default: + return "None" + } + } //function to update - //tabs to select supported and custom - const grainTab = () => {} - - //table that will display the grains (both for the supported grain and custom for the user) - const grainTable = () => { + //table that will display the custom grains created by the user + const grainObjectTable = () => { + return ( + + rows={grainObjectTableData} + onRowClick={(grainObj) => { + setCurrentCustomGrain(grainObj.settings ?? pond.GrainSettings.create()) + setSelectedGrain(grainObj) + setFormValid(true) + /** if they are viewin as a team leave the permissions alone, if not then use the users permissions to the grain type, + * for the moment grain types are not shareable so this will be unused, but this will cover our bases if we want to implement sharing in the future + * */ + if (!as) { + userAPI.getUser(user.id(), { key: grainObj.key, kind: "grain" } as Scope).then(resp => { + setPermissions(resp.permissions); + }); + } + }} + columns={[ + { + title: "Name", + render: row => + + {row.name} + + + }, + { + title: "Group", + render: row => + + {row.settings?.group} + + + }, + { + title: "Equation", + render: row => + + {equationName(row.settings?.equation ?? 0)} + + + } + ]} + page={tablePage} + pageSize={tableSize} + handleRowsPerPageChange={(e) => { + setTableSize(e.target.value) + }} + setPage={(page) => { + setTablePage(page) + }} + total={tableTotal} + /> + ) + } + //table that will display the grain extensions provided by us + const supportedGrainTable = () => { + return ( + + rows={supportedGrainTableData} + page={tablePage} + pageSize={tableSize} + handleRowsPerPageChange={() => {}} + setPage={() => {}} + total={tableTotal} + /> + ) } //the grain display for supported grain type to show the rest of the data such as what constants are used in the formula @@ -32,11 +148,101 @@ export default function Grains() { ) } + const updateGrain = () => { + if(selectedGrain && currentCustomGrain){ + grainAPI.updateGrain(selectedGrain.key, currentCustomGrain).then(resp => { + openSnack("Grain type updated") + }).catch(err => { + openSnack("There was an issue updating the selected grain type") + }) + } + } + const updateGrainConfirmation = () => { + return ( + {setOpenUpdateConfirmation(false)}}> + Update Custom Grain + + Update existing custom grain type? +
+ Note that updating the grain here will not change any bins or components this type was used for, those will retain the old settings. +
+ + + + +
+ ) + } + + const addNewGrain = () => { + if(currentCustomGrain){ + grainAPI.addGrain(currentCustomGrain).then(resp => { + //new grain added + openSnack("New grain type added") + }).catch(err => { + openSnack("There was a problem creating the new grain type") + }) + } + } + + const removeGrain = () => { + if(selectedGrain){ + grainAPI.removeGrain(selectedGrain.key).then(resp => { + openSnack("Grain type removed") + }).catch(err => { + openSnack("There was a problem removing the selected grain type") + }) + } + } + + + const removeConfirmation = () => { + return ( + {setOpenRemoveConfirmation(false)}}> + Remove Custom Grain + + Remove custom grain type? +
+ Note that removing the grain here will not change any bins or components this type was used for, those will retain the old settings. +
+ + + + +
+ ) + } + + const customGrainActions = () => { + return ( + + + {selectedGrain && + + } + + + {selectedGrain && + + } + + + + ) + } + //the grain display for custom grain the user has defined const customGrainDisplay = () => { return ( - - {}}/> + + { + setCurrentCustomGrain(newSettings) + setFormValid(formValid) + }} + /> + {customGrainActions()} ) } @@ -44,10 +250,10 @@ export default function Grains() { const desktopView = () => { return ( - - + + {displayCustom ? grainObjectTable() : supportedGrainTable()} - + {displayCustom ? customGrainDisplay() : supportedDisplay()} @@ -73,20 +279,22 @@ export default function Grains() { title: "Supported", function: () => { setDisplayCustom(false) + setTablePage(0) } }, { title: "Custom", function: () => { setDisplayCustom(true) + setTablePage(0) } } ]} /> - {!isMobile ? desktopView() : mobileView()} - {/* - */} + {!isMobile ? desktopView() : mobileView()} + {updateGrainConfirmation()} + {removeConfirmation()} ) } \ No newline at end of file diff --git a/src/providers/pond/grainAPI.tsx b/src/providers/pond/grainAPI.tsx index 37e1dc1..0df0055 100644 --- a/src/providers/pond/grainAPI.tsx +++ b/src/providers/pond/grainAPI.tsx @@ -22,6 +22,7 @@ export interface IGrainInterface { types?: string[], otherTeam?: string, ) => Promise>; + updateGrain: (key: string, settings: pond.GrainSettings, otherTeam?: string) => Promise> removeGrain: (key: string, otherTeam?: string) => Promise>; } @@ -31,7 +32,7 @@ interface Props {} export default function GrainProvider(props: PropsWithChildren) { const { children } = props; - const { get, del, post } = useHTTP(); + const { get, del, post, put } = useHTTP(); const [{ as }] = useGlobalState(); //add @@ -79,7 +80,6 @@ export default function GrainProvider(props: PropsWithChildren) { get( pondURL( "/grains?limit=" + - "?limit=" + limit + "&offset=" + offset + @@ -98,6 +98,19 @@ export default function GrainProvider(props: PropsWithChildren) { }) }) }; + + //update + const updateGrain = (key: string, settings: pond.GrainSettings, otherTeam?: string) => { + const view = otherTeam ? otherTeam : as + if (view) { + return put( + pondURL("/grains/"+ key + "?&as=" + view), + settings + ); + } + return put(pondURL("/grains/" + key), settings); + } + //remove const removeGrain = (key: string, otherTeam?: string) => { const view = otherTeam ? otherTeam : as @@ -113,6 +126,7 @@ export default function GrainProvider(props: PropsWithChildren) { addGrain, getGrain, listGrains, + updateGrain, removeGrain }}> {children}