frontend/src/maps/mapDrawers/FieldDrawer.tsx
2026-02-10 15:59:46 -06:00

326 lines
10 KiB
TypeScript

import {
Box,
Divider,
Drawer,
Grid2 as Grid,
IconButton,
Tab,
Tabs,
Theme,
Typography
} from "@mui/material";
import { Notes } from "@mui/icons-material";
import DisplayDrawer from "common/DisplayDrawer";
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
import React, { useEffect, useState } from "react";
import TaskViewer from "tasks/TaskViewer";
import Weather from "weather/weather";
import { getThemeType } from "theme";
import GrainDescriber from "grain/GrainDescriber";
import Chat from "chat/Chat";
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";
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
function TabPanelMine(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
aria-labelledby={`simple-tab-${index}`}
style={{ height: "94%", paddingTop: "15px" }}
{...other}>
{value === index && <React.Fragment>{children}</React.Fragment>}
</div>
);
}
interface Props {
open: boolean;
closeDrawer: () => void;
selectedFieldKey: string;
fields: Map<string, Field>;
openSettings: (fieldKey: string) => void;
moveMap: (long: number, lat: number) => void;
}
const useStyles = makeStyles((theme: Theme) => ({
avatar: {
color: getThemeType() === "light" ? theme.palette.common.black : theme.palette.common.white,
backgroundColor: "transparent",
width: theme.spacing(5),
height: theme.spacing(5),
border: "1px solid",
borderColor: theme.palette.divider
},
dark: {
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
padding: 5
},
light: {
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
padding: 5
}
}));
export default function FieldDrawer(props: Props) {
const { open, closeDrawer, selectedFieldKey, fields, moveMap } = props;
const [field, setField] = useState<Field>(Field.create());
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
const [value, setValue] = useState(0);
const classes = useStyles();
const [openNote, setOpenNote] = useState(false);
const isMobile = useMobile();
const [{ as, user }] = useGlobalState();
const hPlanAPI = useHarvestPlanAPI();
const [planLoading, setPlanLoading] = useState(false);
const userAPI = useUserAPI();
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
useEffect(() => {
let field = fields.get(selectedFieldKey);
if (field) {
setField(field);
}
}, [selectedFieldKey, fields]);
useEffect(() => {
let scope;
if (as) {
//if they are viewing as a team regardless of whether the field is adaptive or external (from JD or CNH) get the users permission to the team
scope = teamScope(as);
} else if (
field.settings.fieldGeoData &&
field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE
) {
//if they are viewing as a user and the field is an adaptive field get the users permission to the field
scope = fieldScope(field.key());
}
//if the scope was set get the permissions the user has in that scope
if (scope) {
userAPI.getUser(user.id(), scope).then(resp => {
field.permissions = resp.permissions;
setPermissions(resp.permissions);
});
} else {
//if scope wasn't set that means that they are viewing an external field as a user and so will have full permissions
//because they would have full permissions to the organization that the field is linked to since users cannot share organizations
//and they are only linked to team/user combinations during creation and cannot be shared afterward
let perms = [
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_WRITE,
pond.Permission.PERMISSION_SHARE,
pond.Permission.PERMISSION_USERS
];
field.permissions = perms;
setPermissions(perms);
}
}, [as, user, userAPI, field]); //eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (field.key() !== "") {
hPlanAPI
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
.then(resp => {
if (resp.data.harvestPlan) {
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 handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
const displayNext = () => {
let arr = Array.from(fields.values());
let index = arr.indexOf(field);
let found = false;
do {
if (index === arr.length - 1) {
index = 0;
} else {
index++;
}
let nextField = arr[index];
let location = nextField.center();
if (
location !== null &&
location !== undefined &&
location.longitude !== 0 &&
location.longitude !== 0
) {
setField(nextField);
moveMap(location.longitude, location.latitude);
found = true;
}
} while (!found);
};
const displayPrev = () => {
let arr = Array.from(fields.values());
let index = arr.indexOf(field);
let found = false;
do {
if (index === 0) {
index = arr.length - 1;
} else {
index--;
}
let nextField = arr[index];
let location = nextField.center();
if (
location !== null &&
location !== undefined &&
location.longitude !== 0 &&
location.longitude !== 0
) {
setField(nextField);
moveMap(location.longitude, location.latitude);
found = true;
}
} while (!found);
};
const drawerBody = () => {
let taskLoadKeys: string[] = [];
if (!planLoading) {
field.key() !== "" && taskLoadKeys.push(field.key());
hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
}
return (
<React.Fragment>
<Tabs
style={{ position: "sticky" }}
value={value}
centered
onChange={handleChange}
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
<Tab label="Field Overview" />
<Tab label="Weather" />
<Tab label="Field Activities" />
</Tabs>
<TabPanelMine value={value} index={0}>
<Box paddingLeft={2} paddingRight={2} paddingBottom={5}>
<IconButton className={classes.avatar} onClick={() => setOpenNote(true)}>
<Notes />
</IconButton>
<Box>
<Typography variant="h5" style={{ fontWeight: 700 }}>
{field.fieldName()} Details
</Typography>
</Box>
<Grid container direction="column" alignItems="center" width="100%">
<Grid container direction="row" justifyContent="space-between" width="100%" className={classes.dark}>
<Grid>Approximate Area:</Grid>
<Grid>{field.acres()}ac</Grid>
</Grid>
<Grid container direction="row" justifyContent="space-between" width="100%" className={classes.light}>
<Grid>Grain:</Grid>
<Grid>
{field.grain() === pond.Grain.GRAIN_CUSTOM
? field.customType()
: GrainDescriber(field.crop()).name}
</Grid>
</Grid>
<Grid container direction="row" justifyContent="space-between" width="100%" className={classes.dark}>
<Grid>Grain Variant:</Grid>
<Grid>{field.settings.grainSubtype}</Grid>
</Grid>
<Grid container direction="row" justifyContent="space-between" width="100%" className={classes.light}>
<Grid>Land Location:</Grid>
<Grid>{field.landLoc()}</Grid>
</Grid>
</Grid>
<Box padding={3}>
<Divider style={{ padding: 2 }} />
</Box>
<HarvestPlanDisplay
plan={hPlan}
permissions={permissions}
planField={field}
loading={planLoading}
fieldList={Array.from(fields.values())}
changePlan={updatedPlan => {
if (updatedPlan) {
setHPlan(updatedPlan);
}
}}
/>
</Box>
</TabPanelMine>
<TabPanelMine value={value} index={1}>
<Weather longitude={field.center().longitude} latitude={field.center().latitude} />
</TabPanelMine>
<TabPanelMine value={value} index={2}>
<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton />
</TabPanelMine>
</React.Fragment>
);
};
const noteDrawer = () => {
return (
<Drawer
anchor={isMobile ? "bottom" : "right"}
open={openNote}
onClose={() => setOpenNote(false)}>
<Box height={isMobile ? "50vh" : "100vh"} padding={2}>
<Typography style={{ fontWeight: 650 }}>Notes</Typography>
<Chat type={pond.NoteType.NOTE_TYPE_FIELD} objectKey={field.key()} />
</Box>
</Drawer>
);
};
return (
<React.Fragment>
<DisplayDrawer
open={open}
onClose={closeDrawer}
displayNext={displayNext}
displayPrev={displayPrev}
title={field.name()}
width={"40vw"}
drawerBody={drawerBody()}
updateElement={
field.settings.fieldGeoData &&
field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE
? () => {
props.openSettings(selectedFieldKey);
}
: undefined
}
objectActions={
field.settings.fieldGeoData &&
field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE ? (
<FieldActions field={field} permissions={permissions} refreshCallback={() => {}} />
) : (
undefined
)
}
/>
{noteDrawer()}
</React.Fragment>
);
}