From a8e7ae1041938e9484f425db5688a96a27b53a6b Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 27 Mar 2025 15:48:00 -0600 Subject: [PATCH] imported in the field settings and actions stuff for drawing fields and the field drawer --- src/field/FieldActions.tsx | 181 +++++++++++ src/field/FieldSettings.tsx | 293 ++++++++++++++++++ src/maps/mapDrawers/FieldDrawer.tsx | 18 +- .../mapObjectControllers/AgMapController.tsx | 6 +- src/weather/weather.tsx | 2 +- 5 files changed, 487 insertions(+), 13 deletions(-) create mode 100644 src/field/FieldActions.tsx create mode 100644 src/field/FieldSettings.tsx diff --git a/src/field/FieldActions.tsx b/src/field/FieldActions.tsx new file mode 100644 index 0000000..fbaf090 --- /dev/null +++ b/src/field/FieldActions.tsx @@ -0,0 +1,181 @@ +import { + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + Theme +} from "@mui/material"; +import { blue } from "@mui/material/colors"; +import RemoveSelfIcon from "@mui/icons-material/ExitToApp"; +import MoreIcon from "@mui/icons-material/MoreVert"; +import ShareObjectIcon from "@mui/icons-material/Share"; +import ObjectUsersIcon from "@mui/icons-material/AccountCircle"; +import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; +import { pond } from "protobuf-ts/pond"; +import React, { useState } from "react"; +import ObjectUsers from "user/ObjectUsers"; +import RemoveSelfFromObject from "user/RemoveSelfFromObject"; +import ShareObject from "user/ShareObject"; +import { isOffline } from "utils/environment"; +import ObjectTeams from "teams/ObjectTeams"; +import { Field, fieldScope } from "models"; +import { makeStyles } from "@mui/styles"; + +const useStyles = makeStyles((theme: Theme) => ({ + shareIcon: { + color: blue["500"], + "&:hover": { + color: blue["600"] + } + }, + removeIcon: { + color: "var(--status-alert)" + }, + red: { + color: "var(--status-alert)" + } +})); + +interface Props { + field: Field; + permissions: pond.Permission[]; + refreshCallback: () => void; +} + +interface OpenState { + share: boolean; + users: boolean; + teams: boolean; + settings: boolean; + removeSelf: boolean; +} + +export default function FieldActions(props: Props) { + const classes = useStyles(); + const { field, permissions, refreshCallback } = props; + const [anchorEl, setAnchorEl] = React.useState(null); + const [openState, setOpenState] = useState({ + share: false, + users: false, + teams: false, + settings: false, + removeSelf: false + }); + + const groupMenu = () => { + const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE); + const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS); + return ( + setAnchorEl(null)} + keepMounted + disableAutoFocusItem> + {!isOffline() && canShare && ( + { + setOpenState({ ...openState, share: true }); + setAnchorEl(null); + }}> + + + + + + )} + {!isOffline() && canManageUsers && ( + { + setOpenState({ ...openState, users: true }); + setAnchorEl(null); + }}> + + + + + + )} + {!isOffline() && canManageUsers && ( + { + setOpenState({ ...openState, teams: true }); + setAnchorEl(null); + }}> + + + + + + )} + { + setOpenState({ ...openState, removeSelf: true }); + setAnchorEl(null); + }}> + + + + + + + ); + }; + + const dialogs = () => { + const key = field.key(); + const label = field.fieldName(); + return ( + + setOpenState({ ...openState, share: false })} + /> + setOpenState({ ...openState, users: false })} + refreshCallback={refreshCallback} + /> + setOpenState({ ...openState, removeSelf: false })} + /> + + ); + }; + + return ( + + ) => setAnchorEl(event.currentTarget)}> + + + {groupMenu()} + {dialogs()} + setOpenState({ ...openState, teams: false })} + /> + + ); +} diff --git a/src/field/FieldSettings.tsx b/src/field/FieldSettings.tsx new file mode 100644 index 0000000..6f17023 --- /dev/null +++ b/src/field/FieldSettings.tsx @@ -0,0 +1,293 @@ +import { + Box, + Button, + DialogActions, + DialogContent, + DialogTitle, + Grid2 as Grid, + Switch, + TextField, + Typography +} from "@mui/material"; +import ColourPicker from "common/ColourPicker"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import React from "react"; +import { useState } from "react"; +import { useEffect } from "react"; +import { useFieldAPI } from "providers"; +import { pond } from "protobuf-ts/pond"; +import { useSnackbar } from "hooks"; +import { Field } from "models"; +import { GrainOptions, ToGrainOption } from "grain"; +import SearchSelect from "common/SearchSelect"; +import { clone } from "lodash"; +import GrainDescriber from "grain/GrainDescriber"; + +interface Props { + selectedField?: Field; + borders?: pond.Shape[]; + //holes?: pond.Shape[]; + open: boolean; + onClose: () => void; + updateFields?: (key: string, settings: pond.FieldSettings) => void; + removeField?: (field: Field) => void; +} + +export default function FieldSettings(props: Props) { + const { selectedField, onClose, removeField, updateFields, borders } = props; + const [fieldName, setFieldName] = useState(""); + const [acres, setAcres] = useState(0); + const [cropType, setCropType] = useState(pond.Grain.GRAIN_INVALID); + const [grainSubtype, setGrainSubtype] = useState(""); + const [landLocation, setLandLocation] = useState(""); + const fieldAPI = useFieldAPI(); + const [featColor, setFeatColor] = useState("green"); + const { openSnack } = useSnackbar(); + const [isCustom, setIsCustom] = useState(false); + const [customType, setCustomType] = useState(""); + const [bushelsPerTonne, setBushelsPerTonne] = useState("1"); + + useEffect(() => { + if (selectedField) { + setFieldName(selectedField.fieldName()); + setCropType(selectedField.crop()); + setAcres(selectedField.settings.acres); + setLandLocation(selectedField.landLoc() ? selectedField.landLoc() : ""); + setGrainSubtype(selectedField.subtype()); + setFeatColor(selectedField.settings.fieldGeoData?.colour ?? "green"); + setBushelsPerTonne(selectedField.bushelsPerTonne().toString()); + if (selectedField.grain() === pond.Grain.GRAIN_CUSTOM) { + setIsCustom(true); + setCustomType(selectedField.customType()); + } else { + setIsCustom(false); + } + } else { + setFieldName(""); + setCropType(0); + setLandLocation(""); + setFeatColor(""); + } + }, [selectedField]); + + const checkValidation = () => { + return fieldName.length > 0 && cropType !== 0 ? false : true; + }; + + const submit = () => { + if (selectedField) { + //update existing field + let settings = selectedField.settings; + settings.fieldName = fieldName; + settings.crop = cropType; + if (settings.fieldGeoData) { + settings.fieldGeoData.colour = featColor; + } + settings.customGrain = customType; + settings.grainSubtype = grainSubtype; + settings.landLocation = landLocation; + settings.acres = acres; + + fieldAPI + .updateField(selectedField.key(), settings) + .then(resp => { + let newField = clone(selectedField); + newField.settings = settings; + openSnack("Updated Field Settings"); + updateFields && updateFields(selectedField.key(), selectedField.settings); + onClose(); + }) + .catch(err => { + openSnack("Failed to Update Field Settings"); + }); + } else { + //create new field + let geo = pond.GeoData.create(); + let settings: pond.FieldSettings = pond.FieldSettings.create(); + + //set the basic settings + settings.fieldName = fieldName; + settings.crop = cropType; + settings.customGrain = customType; + settings.grainSubtype = grainSubtype; + settings.landLocation = landLocation; + settings.acres = acres; + //set the geodata + geo.colour = featColor; + geo.title = fieldName; + if (borders) { + geo.geoShape = borders.length > 1 ? "MultiPolygon" : "Polygon"; + geo.shapes = borders; + } + settings.fieldGeoData = geo; + fieldAPI + .addField(settings) + .then(resp => { + updateFields && updateFields(resp.data.field, settings); + openSnack("New Field Created"); + }) + .catch(err => { + openSnack("failed to add field"); + }); + onClose(); + } + }; + + const deleteField = () => { + if (selectedField) { + fieldAPI + .removeField(selectedField?.key()) + .then(resp => { + openSnack("Field Mapping Deleted"); + //setActiveStep(0); + removeField && removeField(selectedField); + onClose(); + }) + .catch(err => { + openSnack("Failed to delete field mapping"); + }); + } + }; + + const fieldInformation = () => { + return ( + + + Field Information + setFieldName(e.target.value)} + /> + setAcres(+e.target.value)} + /> + {/* Select box for grain type */} + + Supported Grain + + { + setIsCustom(checked); + if (checked) { + setCropType(pond.Grain.GRAIN_CUSTOM); + } else { + setCustomType(""); + } + }} + name="storage" + /> + + Custom Grain + + {isCustom ? ( + + setCustomType(e.target.value)} + /> + setBushelsPerTonne(e.target.value)} + /> + + ) : ( + { + if (option) { + let grainType = pond.Grain[option.value as keyof typeof pond.Grain]; + setCropType(grainType); + setBushelsPerTonne(GrainDescriber(grainType).bushelsPerTonne.toString()); + } + }} + group + options={GrainOptions()} + /> + )} + + setGrainSubtype(e.target.value)} + /> + setLandLocation(e.target.value)} + /> + + Field Colour + setFeatColor(color)} /> + + + + + + {selectedField && ( + + )} + + + + + + + + + ); + }; + + return ( + + {fieldInformation()} + + ); +} diff --git a/src/maps/mapDrawers/FieldDrawer.tsx b/src/maps/mapDrawers/FieldDrawer.tsx index e859e02..bafd966 100644 --- a/src/maps/mapDrawers/FieldDrawer.tsx +++ b/src/maps/mapDrawers/FieldDrawer.tsx @@ -23,7 +23,7 @@ import { pond } from "protobuf-ts/pond"; import { useMobile } from "hooks"; import { useGlobalState, /*useHarvestPlanAPI,*/ useUserAPI } from "providers"; import { makeStyles } from "@mui/styles"; -//import FieldActions from "field/FieldActions"; +import FieldActions from "field/FieldActions"; interface TabPanelProps { children?: React.ReactNode; @@ -311,14 +311,14 @@ export default function FieldDrawer(props: Props) { } : undefined } - // objectActions={ - // field.settings.fieldGeoData && - // field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE ? ( - // {}} /> - // ) : ( - // undefined - // ) - // } + objectActions={ + field.settings.fieldGeoData && + field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE ? ( + {}} /> + ) : ( + undefined + ) + } /> {noteDrawer()} diff --git a/src/maps/mapObjectControllers/AgMapController.tsx b/src/maps/mapObjectControllers/AgMapController.tsx index 0e73078..5a330b8 100644 --- a/src/maps/mapObjectControllers/AgMapController.tsx +++ b/src/maps/mapObjectControllers/AgMapController.tsx @@ -50,7 +50,7 @@ import { import AgMapTools from "../mapObjectTools/agMapTools"; import FieldDrawer from "../mapDrawers/FieldDrawer"; import { coordDistance, shapeFromCoords } from "models/GeometryMapping"; - //import FieldSettings from "field/FieldSettings"; + import FieldSettings from "field/FieldSettings"; import GrainBagSettings from "grainBag/grainBagSettings"; import { makeStyles } from "@mui/styles"; import { Result } from "@mapbox/mapbox-gl-geocoder"; @@ -1839,7 +1839,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder"; longitude={newMarkerLong} latitude={newMarkerLat} /> - {/* { let data = clone(geoData); @@ -1877,7 +1877,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder"; setOpenFieldSettings(false); }} borders={fieldBorders} - /> */} + />