adding the harvest plan stuff to the field page

This commit is contained in:
csawatzky 2025-09-04 14:38:53 -06:00
parent 84885dc163
commit c69636482a
8 changed files with 146 additions and 36 deletions

View file

@ -741,7 +741,7 @@ export default function BinSettings(props: Props) {
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR: case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
return "Hybrid (lidar)" return "Hybrid (lidar)"
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART: case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
return "Auto (LibraCart)" return "Auto (Libra Cart)"
default: default:
return "Manual" return "Manual"
} }
@ -905,7 +905,7 @@ export default function BinSettings(props: Props) {
<FormControlLabel <FormControlLabel
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART} value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
control={<Radio />} control={<Radio />}
label={"Auto (LibraCart)"} label={"Auto (Libra Cart)"}
/> />
} }
</RadioGroup> </RadioGroup>
@ -951,7 +951,7 @@ export default function BinSettings(props: Props) {
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART && {inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
<Box width="100%" padding={2}> <Box width="100%" padding={2}>
<SearchSelect <SearchSelect
label="LibraCart Destination" label="Libra Cart Destination"
selected={lcDestination} selected={lcDestination}
changeSelection={option => { changeSelection={option => {
let newForm = form; let newForm = form;
@ -963,7 +963,7 @@ export default function BinSettings(props: Props) {
options={lcDestinationOptions} options={lcDestinationOptions}
/> />
<Typography variant="caption"> <Typography variant="caption">
The linked bins inventory will be adjusted When the LibraCart Destination data is synced every 6 hours The linked bins inventory will be adjusted When the Libra Cart Destination data is synced every 6 hours
</Typography> </Typography>
</Box> </Box>
} }

View file

@ -63,8 +63,6 @@ export default function FieldActions(props: Props) {
removeSelf: false removeSelf: false
}); });
console.log(permissions)
const groupMenu = () => { const groupMenu = () => {
const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE); const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS); const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);

View file

@ -7,16 +7,17 @@ import {
TextField TextField
} from "@mui/material"; } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { Option } from "common/SearchSelect";
import { Field, HarvestPlan, Task } from "models"; import { Field, HarvestPlan, Task } from "models";
import moment from "moment"; import moment from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState, useHarvestPlanAPI, useTaskAPI } from "providers"; import { useFieldAPI, useGlobalState, useHarvestPlanAPI, useTaskAPI } from "providers";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
interface Props { interface Props {
open: boolean; open: boolean;
close: () => void; close: () => void;
fields: Field[]; fields?: Field[];
plan: HarvestPlan; plan: HarvestPlan;
} }
@ -29,6 +30,8 @@ export default function DuplicateHarvestPlan(props: Props) {
const harvestPlanAPI = useHarvestPlanAPI(); const harvestPlanAPI = useHarvestPlanAPI();
const taskAPI = useTaskAPI(); const taskAPI = useTaskAPI();
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
const [fieldOptions, setFieldOptions] = useState<Field[]>([])
const fieldAPI = useFieldAPI();
const loadTasks = useCallback(() => { const loadTasks = useCallback(() => {
taskAPI.listTasks(50, 0, "asc", "start", plan.key(), undefined).then(resp => { taskAPI.listTasks(50, 0, "asc", "start", plan.key(), undefined).then(resp => {
@ -41,9 +44,25 @@ export default function DuplicateHarvestPlan(props: Props) {
loadTasks(); loadTasks();
}, [loadTasks]); }, [loadTasks]);
useEffect(()=>{
if(fields){
//if fields were passed in use those
setFieldOptions(fields)
}else{
//otherwise load them
fieldAPI.listFields(50, 0, "asc", "fieldName").then(resp => {
setFieldOptions(resp.data.fields.map(f => Field.create(f)))
}).catch(err => {
})
}
},[fields])
//this should load the fields here
const duplicate = () => { const duplicate = () => {
let planSettings = HarvestPlan.clone(plan).settings; let planSettings = HarvestPlan.clone(plan).settings;
planSettings.field = fields[newFieldIndex].key(); planSettings.field = fieldOptions[newFieldIndex].key();
planSettings.createDate = moment.now(); planSettings.createDate = moment.now();
if (title !== "") { if (title !== "") {
planSettings.title = title; planSettings.title = title;
@ -69,7 +88,7 @@ export default function DuplicateHarvestPlan(props: Props) {
value={newFieldIndex} value={newFieldIndex}
onChange={e => setNewFieldIndex(+e.target.value)} onChange={e => setNewFieldIndex(+e.target.value)}
color="primary"> color="primary">
{fields.map((option, i) => ( {fieldOptions.map((option, i) => (
<MenuItem key={option.key()} value={i}> <MenuItem key={option.key()} value={i}>
{option.fieldName()} {option.fieldName()}
</MenuItem> </MenuItem>
@ -98,8 +117,8 @@ export default function DuplicateHarvestPlan(props: Props) {
color="primary" color="primary"
disabled={ disabled={
!ready || !ready ||
(fields[newFieldIndex] && (fieldOptions[newFieldIndex] &&
!fields[newFieldIndex].permissions.includes(pond.Permission.PERMISSION_WRITE)) !fieldOptions[newFieldIndex].permissions.includes(pond.Permission.PERMISSION_WRITE))
}> }>
Duplicate Duplicate
</Button> </Button>

View file

@ -13,7 +13,7 @@ import ShareObjectIcon from "@mui/icons-material/Share";
import ObjectUsersIcon from "@mui/icons-material/AccountCircle"; import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle"; import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import ObjectUsers from "user/ObjectUsers"; import ObjectUsers from "user/ObjectUsers";
import ShareObject from "user/ShareObject"; import ShareObject from "user/ShareObject";
import { isOffline } from "utils/environment"; import { isOffline } from "utils/environment";
@ -43,7 +43,7 @@ const useStyles = makeStyles((theme: Theme) => ({
interface Props { interface Props {
plan: HarvestPlan; plan: HarvestPlan;
planField: Field; planField: Field;
fieldList: Field[]; fieldList?: Field[];
permissions: pond.Permission[]; permissions: pond.Permission[];
refreshCallback: (updatedPlan?: HarvestPlan) => void; refreshCallback: (updatedPlan?: HarvestPlan) => void;
hideNew?: boolean; hideNew?: boolean;
@ -215,7 +215,7 @@ export default function HarvestPlanActions(props: Props) {
setUpdatePlan(true); setUpdatePlan(true);
setOpenState({ ...openState, settings: true }); setOpenState({ ...openState, settings: true });
}}> }}>
<Edit /> Edit or Add Activity <Edit /> Activities
</Button> </Button>
)} )}
<IconButton <IconButton

View file

@ -10,7 +10,7 @@ import {
import { Notes } from "@mui/icons-material"; import { Notes } from "@mui/icons-material";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { Field, HarvestPlan } from "models"; import { Field, HarvestPlan } from "models";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { getThemeType } from "theme"; import { getThemeType } from "theme";
import Chat from "chat/Chat"; import Chat from "chat/Chat";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
@ -23,7 +23,7 @@ interface Props {
permissions: pond.Permission[]; permissions: pond.Permission[];
planField: Field; planField: Field;
loading: boolean; loading: boolean;
fieldList: Field[]; fieldList?: Field[];
changePlan: (updatedPlan?: HarvestPlan) => void; changePlan: (updatedPlan?: HarvestPlan) => void;
} }
@ -175,7 +175,7 @@ export default function HarvestPlanDisplay(props: Props) {
<Typography <Typography
variant="h5" variant="h5"
style={{ fontWeight: 700, marginTop: "auto", marginBottom: "auto" }}> style={{ fontWeight: 700, marginTop: "auto", marginBottom: "auto" }}>
Crop Plan{plan.key() !== "" ? " - " + plan.settings.title : " - None Found"} {plan.key() !== "" ? plan.settings.title : "None Found"}
</Typography> </Typography>
{!isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && ( {!isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && (
<HarvestPlanActions <HarvestPlanActions

View file

@ -0,0 +1,26 @@
import { Box } from "@mui/material"
import ResponsiveTable from "common/ResponsiveTable"
import { useHarvestPlanAPI } from "providers"
import { useCallback, useEffect, useState } from "react"
interface Props {
field?: string
}
export default function HarvestPlanTable(props: Props){
const {field} = props
const harvestAPI = useHarvestPlanAPI()
const [page, setPage] = useState(0)
const [pageSize, setPageSize] = useState(10)
useEffect(()=>{
harvestAPI.listHarvestPlans(pageSize, page*pageSize, "asc", "name", field).then(resp => {
console.log(resp)
})
},[page, pageSize, field])
return (
<Box>
</Box>
)
}

View file

@ -1,10 +1,10 @@
import { Field, HarvestPlan } from "models"; import { Field, fieldScope, HarvestPlan, teamScope } from "models";
import { useGlobalState } from "providers"; import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import { useMobile } from "hooks"; import { useMobile, useSnackbar, useUserAPI } from "hooks";
import { Box, Grid2, IconButton } from "@mui/material"; import { Box, Card, Grid2, IconButton } from "@mui/material";
import FieldActions from "field/FieldActions"; import FieldActions from "field/FieldActions";
import { Settings } from "@mui/icons-material"; import { Settings } from "@mui/icons-material";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
@ -12,28 +12,82 @@ import FieldMinimap from "field/Fieldminimap";
import Weather from "weather/weather"; import Weather from "weather/weather";
import TaskViewer from "tasks/TaskViewer"; import TaskViewer from "tasks/TaskViewer";
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay"; import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
import { cloneDeep } from "lodash";
import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
export default function FieldPage() { export default function FieldPage() {
const { state } = useLocation(); const { state } = useLocation();
//const [{ as }] = useGlobalState() //const [{ as }] = useGlobalState()
const fieldAPI = useFieldAPI();
const userAPI = useUserAPI();
const [{as, user}] = useGlobalState();
const {openSnack} = useSnackbar();
const fieldID = useParams<{ fieldID: string }>()?.fieldID ?? "";
const [field, setField] = useState<Field>(state?.field ? Field.create(state.field) : Field.create()) const [field, setField] = useState<Field>(state?.field ? Field.create(state.field) : Field.create())
const [permissions, setPermissions] = useState<pond.Permission[]>(state?.permissions ? state.permissions : []) const [permissions, setPermissions] = useState<pond.Permission[]>(state?.permissions ? state.permissions : [])
const isMobile = useMobile() const isMobile = useMobile()
const [openFieldSettings, setOpenFieldSettings] = useState(false) const [openFieldSettings, setOpenFieldSettings] = useState(false)
const [planLoading, setPlanLoading] = useState(false) const [planLoading, setPlanLoading] = useState(false)
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create()); const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
const [loading, setLoading] = useState(false)
const hPlanAPI = useHarvestPlanAPI();
const loadField = () => { const loadField = useCallback(() => {
} },[as])
useEffect(()=>{ useEffect(()=>{
if(state.field){ // if already loading do nothing
if(loading) return
//if the field is passed through in the state just use it
if(state?.field){
let field = Field.create(state.field) let field = Field.create(state.field)
field.permissions = state.permissions
setField(field) setField(field)
setPermissions(state.permissions) setPermissions(state.permissions)
return
} }
},[state.field]) //otherwise load the field
setLoading(true)
fieldAPI.getField(fieldID, as).then(resp => {
setField(Field.create(resp.data))
}).catch(err => {
openSnack("Failed to load Field")
}).finally(() => {
setLoading(false)
})
//also get the permissions
let scope = fieldScope(fieldID);
if (as) {
scope = teamScope(as)
}
userAPI.getUser(user.id(), scope).then(resp => {
let f = cloneDeep(field)
f.permissions = resp.permissions
setField(f)
setPermissions(resp.permissions);
});
},[loadField, fieldID, as])
useEffect(() => {
if (field.key() !== "") {
hPlanAPI
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
.then(resp => {
if (resp.data.harvestPlan.length > 0) {
let plan = resp.data.harvestPlan[0];
setHPlan(HarvestPlan.any(plan));
} else {
setHPlan(HarvestPlan.create());
}
setPlanLoading(false);
})
.catch(err => {
//openSnack("Failed to load plan");
});
}
}, [field, as, hPlanAPI]);
const fieldActions = () => { const fieldActions = () => {
return ( return (
@ -60,14 +114,12 @@ export default function FieldPage() {
} }
const harvestPlans = () => { const harvestPlans = () => {
console.log(permissions)
return ( return (
<HarvestPlanDisplay <HarvestPlanDisplay
plan={hPlan} plan={hPlan}
permissions={permissions} permissions={permissions}
planField={field} planField={field}
loading={planLoading} loading={planLoading}
fieldList={[field]}
changePlan={updatedPlan => { changePlan={updatedPlan => {
if (updatedPlan) { if (updatedPlan) {
setHPlan(updatedPlan); setHPlan(updatedPlan);
@ -87,18 +139,31 @@ export default function FieldPage() {
const desktopView = () => { const desktopView = () => {
return ( return (
<Grid2 container> <Grid2 container spacing={1}>
<Grid2 size={4}> <Grid2 size={4}>
{map()} <Card raised>
{map()}
</Card>
</Grid2> </Grid2>
<Grid2 size={3}> <Grid2 size={3}>
<Card raised>
{weather()} {weather()}
</Card>
</Grid2> </Grid2>
<Grid2 size={5}> <Grid2 size={5}>
<Card raised>
{tasks()} {tasks()}
</Card>
</Grid2> </Grid2>
<Grid2 size={12}> <Grid2 size={4}>
<Card raised>
{harvestPlans()} {harvestPlans()}
</Card>
</Grid2>
<Grid2 size={8}>
<Card>
<HarvestPlanTable field={field.key()}/>
</Card>
</Grid2> </Grid2>
</Grid2> </Grid2>
) )
@ -116,8 +181,10 @@ export default function FieldPage() {
return ( return (
<PageContainer> <PageContainer>
<Box padding={2}>
{fieldActions()} {fieldActions()}
{isMobile ? mobileView() : desktopView()} {isMobile ? mobileView() : desktopView()}
</Box>
</PageContainer> </PageContainer>
) )
} }

View file

@ -9,7 +9,7 @@ import { permissionToString } from "pbHelpers/Permission";
export interface IFieldAPIContext { export interface IFieldAPIContext {
addField: (field: pond.FieldSettings, otherTeam?: string) => Promise<any>; addField: (field: pond.FieldSettings, otherTeam?: string) => Promise<any>;
getField: (fieldId: string, otherTeam?: string) => Promise<any>; getField: (fieldId: string, otherTeam?: string) => Promise<AxiosResponse<pond.Field>>;
listFields: ( listFields: (
limit: number, limit: number,
offset: number, offset: number,
@ -53,8 +53,8 @@ export default function FieldProvider(props: PropsWithChildren<Props>) {
const getField = (fieldId: string, otherTeam?: string) => { const getField = (fieldId: string, otherTeam?: string) => {
const view = otherTeam ? otherTeam : as const view = otherTeam ? otherTeam : as
if (view) return get(pondURL("/field/" + fieldId + "?as=" + view)); if (view) return get<pond.Field>(pondURL("/field/" + fieldId + "?as=" + view));
return get(pondURL("/field/" + fieldId)); return get<pond.Field>(pondURL("/field/" + fieldId));
}; };
const removeField = (key: string, otherTeam?: string) => { const removeField = (key: string, otherTeam?: string) => {