Merge branch 'dev_environment' of gitlab.com:brandx/bxt-app into dev_environment
This commit is contained in:
commit
6d21ec0fc6
24 changed files with 2534 additions and 101 deletions
|
|
@ -679,16 +679,16 @@ export default function BinyardDisplay(props: Props) {
|
||||||
className={classes.gridList}
|
className={classes.gridList}
|
||||||
cols={
|
cols={
|
||||||
props.insert && !isMobile
|
props.insert && !isMobile
|
||||||
? 3.25
|
? 3
|
||||||
: width === "xs"
|
: width === "xs"
|
||||||
? 3.25
|
? 3
|
||||||
: width === "sm"
|
: width === "sm"
|
||||||
? 5.5
|
? 5
|
||||||
: width === "md"
|
: width === "md"
|
||||||
? 6.5
|
? 6
|
||||||
: width === "lg"
|
: width === "lg"
|
||||||
? 7.5
|
? 7
|
||||||
: 8.5
|
: 8
|
||||||
}>
|
}>
|
||||||
{binMetrics &&
|
{binMetrics &&
|
||||||
binMetrics.grainInventory.map((inv, key) => (
|
binMetrics.grainInventory.map((inv, key) => (
|
||||||
|
|
|
||||||
220
src/common/ObjectControls.tsx
Normal file
220
src/common/ObjectControls.tsx
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
import {
|
||||||
|
Grid2 as Grid,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Tooltip,
|
||||||
|
Theme,
|
||||||
|
Box
|
||||||
|
} from "@mui/material";
|
||||||
|
//import ObjectTeamsIcon from "@material-ui/icons/SupervisedUserCircle";
|
||||||
|
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import DeviceIcon from "products/Bindapt/BindaptIcon";
|
||||||
|
import { Device, Scope } from "models";
|
||||||
|
import ObjectTeams from "teams/ObjectTeams";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { Link, SupervisedUserCircle as ObjectTeamsIcon } from "@mui/icons-material";
|
||||||
|
//import { useHistory } from "react-router";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
actions: JSX.Element;
|
||||||
|
objectButton?: JSX.Element;
|
||||||
|
mapFunction?: () => void;
|
||||||
|
linkDeviceFunction?: () => void;
|
||||||
|
notesButton?: JSX.Element;
|
||||||
|
teamScope?: Scope;
|
||||||
|
permissions?: pond.Permission[];
|
||||||
|
devices?: Device[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
avatar: {
|
||||||
|
color: useThemeType() === "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
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function ObjectControls(props: Props) {
|
||||||
|
const {
|
||||||
|
mapFunction,
|
||||||
|
linkDeviceFunction,
|
||||||
|
teamScope,
|
||||||
|
permissions,
|
||||||
|
actions,
|
||||||
|
devices,
|
||||||
|
notesButton,
|
||||||
|
objectButton
|
||||||
|
} = props;
|
||||||
|
const [openTeams, setOpenTeams] = useState(false);
|
||||||
|
//const history = useHistory();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
// const goToDevice = (dev: Device) => {
|
||||||
|
// history.push("/devices/" + dev.id());
|
||||||
|
// };
|
||||||
|
|
||||||
|
const goToDevice = (dev: Device) => {
|
||||||
|
let path = "/devices/" + dev.id();
|
||||||
|
navigate(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{teamScope && permissions && (
|
||||||
|
<ObjectTeams
|
||||||
|
scope={teamScope}
|
||||||
|
label="Teams"
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openTeams}
|
||||||
|
refreshCallback={() => {}}
|
||||||
|
closeDialogCallback={() => {
|
||||||
|
setOpenTeams(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deviceNavButton = () => {
|
||||||
|
if (devices) {
|
||||||
|
if (devices.length > 1) {
|
||||||
|
return (
|
||||||
|
<Tooltip key="devices" title="devices">
|
||||||
|
<IconButton
|
||||||
|
key="devButton"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
className={classes.avatar}
|
||||||
|
component="span">
|
||||||
|
<DeviceIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return devices.map(dev => (
|
||||||
|
<Tooltip key={dev.id()} title={dev.name()}>
|
||||||
|
<IconButton
|
||||||
|
key={dev.id()}
|
||||||
|
onClick={() => goToDevice(dev)}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
className={classes.avatar}
|
||||||
|
component="span">
|
||||||
|
<DeviceIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deviceMenu = () => {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="groupMenu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
//classes={{ paper: classes.menuPaper }}
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{devices &&
|
||||||
|
devices.map(dev => (
|
||||||
|
<MenuItem key={dev.id()} onClick={() => goToDevice(dev)}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DeviceIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={dev.name()} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ padding: 10 }}>
|
||||||
|
{dialogs()}
|
||||||
|
<Grid container direction="row">
|
||||||
|
{/* buttons on the left side start here */}
|
||||||
|
<Grid>
|
||||||
|
<Grid container direction="row">
|
||||||
|
{objectButton}
|
||||||
|
{notesButton}
|
||||||
|
{deviceNavButton()}
|
||||||
|
{deviceMenu()}
|
||||||
|
{mapFunction && (
|
||||||
|
<Grid>
|
||||||
|
<IconButton
|
||||||
|
onClick={mapFunction}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
className={classes.avatar}
|
||||||
|
component="span">
|
||||||
|
<FieldsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{teamScope && permissions && (
|
||||||
|
<Grid>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenTeams(true);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
className={classes.avatar}
|
||||||
|
component="span">
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
{/* bottons on the right side start here */}
|
||||||
|
<Grid>
|
||||||
|
{linkDeviceFunction && (
|
||||||
|
<Tooltip title="Link Devices">
|
||||||
|
<IconButton onClick={linkDeviceFunction}>
|
||||||
|
<Link />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{actions}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
181
src/field/FieldActions.tsx
Normal file
181
src/field/FieldActions.tsx
Normal file
|
|
@ -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 | HTMLElement>(null);
|
||||||
|
const [openState, setOpenState] = useState<OpenState>({
|
||||||
|
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 (
|
||||||
|
<Menu
|
||||||
|
id="groupMenu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{!isOffline() && canShare && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, share: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ShareObjectIcon className={classes.shareIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, users: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, teams: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Teams" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, removeSelf: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RemoveSelfIcon className={classes.red} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Leave" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const key = field.key();
|
||||||
|
const label = field.fieldName();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ShareObject
|
||||||
|
scope={fieldScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.share}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
|
||||||
|
/>
|
||||||
|
<ObjectUsers
|
||||||
|
scope={fieldScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.users}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={fieldScope(key)}
|
||||||
|
label={label}
|
||||||
|
isDialogOpen={openState.removeSelf}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, removeSelf: false })}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton
|
||||||
|
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
{groupMenu()}
|
||||||
|
{dialogs()}
|
||||||
|
<ObjectTeams
|
||||||
|
scope={fieldScope(field.key())}
|
||||||
|
label="Teams"
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.teams}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
293
src/field/FieldSettings.tsx
Normal file
293
src/field/FieldSettings.tsx
Normal file
|
|
@ -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<number>(0);
|
||||||
|
const [cropType, setCropType] = useState<pond.Grain>(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 (
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
<DialogTitle>Field Information</DialogTitle>
|
||||||
|
<TextField
|
||||||
|
value={fieldName}
|
||||||
|
margin="normal"
|
||||||
|
color="primary"
|
||||||
|
label="Field Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
autoFocus
|
||||||
|
onChange={e => setFieldName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
value={acres}
|
||||||
|
margin="normal"
|
||||||
|
color="primary"
|
||||||
|
label="Acres"
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
autoFocus
|
||||||
|
//helperText="If you are unsure of the acres they will be calculated automatically based on the drawn field boundaries"
|
||||||
|
onChange={e => setAcres(+e.target.value)}
|
||||||
|
/>
|
||||||
|
{/* Select box for grain type */}
|
||||||
|
<Grid container alignItems="center">
|
||||||
|
<Grid>Supported Grain</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Switch
|
||||||
|
color="default"
|
||||||
|
value={isCustom}
|
||||||
|
checked={isCustom}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
setIsCustom(checked);
|
||||||
|
if (checked) {
|
||||||
|
setCropType(pond.Grain.GRAIN_CUSTOM);
|
||||||
|
} else {
|
||||||
|
setCustomType("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
name="storage"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid>Custom Grain</Grid>
|
||||||
|
</Grid>
|
||||||
|
{isCustom ? (
|
||||||
|
<React.Fragment>
|
||||||
|
<TextField
|
||||||
|
label="Grain Type"
|
||||||
|
margin="normal"
|
||||||
|
style={{ marginTop: 0 }}
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={customType}
|
||||||
|
color="primary"
|
||||||
|
onChange={e => setCustomType(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Bushels Per Tonne"
|
||||||
|
margin="normal"
|
||||||
|
style={{ marginTop: 0 }}
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
value={bushelsPerTonne}
|
||||||
|
color="primary"
|
||||||
|
onChange={e => setBushelsPerTonne(e.target.value)}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
<SearchSelect
|
||||||
|
label="Type"
|
||||||
|
selected={ToGrainOption(cropType)}
|
||||||
|
changeSelection={option => {
|
||||||
|
if (option) {
|
||||||
|
let grainType = pond.Grain[option.value as keyof typeof pond.Grain];
|
||||||
|
setCropType(grainType);
|
||||||
|
setBushelsPerTonne(GrainDescriber(grainType).bushelsPerTonne.toString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
group
|
||||||
|
options={GrainOptions()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Grain Variant"
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={grainSubtype}
|
||||||
|
color="primary"
|
||||||
|
onChange={e => setGrainSubtype(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Land Location"
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
value={landLocation}
|
||||||
|
color="primary"
|
||||||
|
onChange={e => setLandLocation(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Box marginTop={3}>
|
||||||
|
Field Colour
|
||||||
|
<ColourPicker onChange={color => setFeatColor(color)} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<DialogActions>
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid>
|
||||||
|
{selectedField && (
|
||||||
|
<Button onClick={deleteField} variant="contained" style={{ background: "red" }}>
|
||||||
|
<Typography>Delete Field</Typography>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Button onClick={onClose} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={submit}
|
||||||
|
disabled={checkValidation()}
|
||||||
|
color="primary">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DialogActions>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog fullWidth fullScreen={false} open={props.open} onClose={onClose}>
|
||||||
|
<DialogContent>{fieldInformation()}</DialogContent>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,29 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
width: "auto",
|
width: "auto",
|
||||||
marginRight: theme.spacing(1)
|
marginRight: theme.spacing(1)
|
||||||
|
},
|
||||||
|
sliderRoot: {},
|
||||||
|
sliderThumb: {
|
||||||
|
left: 23
|
||||||
|
},
|
||||||
|
mobileSliderThumb: {
|
||||||
|
left: 28
|
||||||
|
},
|
||||||
|
sliderTrack: {
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
sliderRail: {
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
sliderValLabel: {
|
||||||
|
// left: -20,
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
top: -15,
|
||||||
|
background: "transparent"
|
||||||
|
},
|
||||||
|
sliderLabel: {
|
||||||
|
background: "gold"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -210,7 +233,7 @@ export default function GrainBagVisualizer(props: Props) {
|
||||||
position="relative"
|
position="relative"
|
||||||
height={1}
|
height={1}
|
||||||
width={1}
|
width={1}
|
||||||
bgcolor={theme.palette.background.paper}
|
//bgcolor={theme.palette.background.paper}
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
|
|
@ -256,7 +279,14 @@ export default function GrainBagVisualizer(props: Props) {
|
||||||
<Slider
|
<Slider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
value={fillLevel}
|
value={fillLevel}
|
||||||
style={{ color: sliderColour }}
|
classes={{
|
||||||
|
root: classes.sliderRoot,
|
||||||
|
rail: classes.sliderRail,
|
||||||
|
track: classes.sliderTrack,
|
||||||
|
thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
|
||||||
|
valueLabel: classes.sliderValLabel,
|
||||||
|
}}
|
||||||
|
style={{height: "100%", color: sliderColour }}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
valueLabelDisplay="auto"
|
valueLabelDisplay="auto"
|
||||||
|
|
@ -292,14 +322,14 @@ export default function GrainBagVisualizer(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card raised className={classes.cardContent}>
|
<Card raised className={classes.cardContent}>
|
||||||
<Grid container justifyContent="flex-start" alignItems="stretch">
|
<Grid container justifyContent="flex-start" alignItems="stretch" width={"100%"}>
|
||||||
<Grid style={{ position: "relative" }}>
|
<Grid style={{ position: "relative" }} size={6}>
|
||||||
{overview()}
|
{overview()}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid style={{ position: "relative" }}>
|
<Grid style={{ position: "relative" }} size={5}>
|
||||||
{visual()}
|
{visual()}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>{controls()}</Grid>
|
<Grid size={1}>{controls()}</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* dialog for grain inventory transactions */}
|
{/* dialog for grain inventory transactions */}
|
||||||
<GrainTransaction
|
<GrainTransaction
|
||||||
|
|
|
||||||
108
src/harvestPlan/DuplicateHarvestPlan.tsx
Normal file
108
src/harvestPlan/DuplicateHarvestPlan.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
MenuItem,
|
||||||
|
TextField
|
||||||
|
} from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { Field, HarvestPlan, /*Task*/ } from "models";
|
||||||
|
import moment from "moment";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useHarvestPlanAPI/*, useTaskAPI*/ } from "providers";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
close: () => void;
|
||||||
|
fields: Field[];
|
||||||
|
plan: HarvestPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DuplicateHarvestPlan(props: Props) {
|
||||||
|
const { open, close, fields, plan } = props;
|
||||||
|
const [newFieldIndex, setNewFieldIndex] = useState(0);
|
||||||
|
//const [planTasks, setPlanTasks] = useState<Task[]>([]);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const harvestPlanAPI = useHarvestPlanAPI();
|
||||||
|
//const taskAPI = useTaskAPI();
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
|
||||||
|
// const loadTasks = useCallback(() => {
|
||||||
|
// taskAPI.listTasks(50, 0, "asc", "start", plan.key()).then(resp => {
|
||||||
|
// setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
||||||
|
// setReady(true);
|
||||||
|
// });
|
||||||
|
// }, [taskAPI, plan]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// loadTasks();
|
||||||
|
// }, [loadTasks]);
|
||||||
|
|
||||||
|
const duplicate = () => {
|
||||||
|
let planSettings = HarvestPlan.clone(plan).settings;
|
||||||
|
planSettings.field = fields[newFieldIndex].key();
|
||||||
|
planSettings.createDate = moment.now();
|
||||||
|
if (title !== "") {
|
||||||
|
planSettings.title = title;
|
||||||
|
}
|
||||||
|
harvestPlanAPI.addHarvestPlan(planSettings).then(resp => {
|
||||||
|
// let tasks = planTasks;
|
||||||
|
// tasks.forEach(task => {
|
||||||
|
// task.settings.objectKey = resp.data.harvestPlan;
|
||||||
|
// taskAPI.addTask(task.settings);
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>Duplicate Crop Plan</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
select
|
||||||
|
label="Field"
|
||||||
|
value={newFieldIndex}
|
||||||
|
onChange={e => setNewFieldIndex(+e.target.value)}
|
||||||
|
color="primary">
|
||||||
|
{fields.map((option, i) => (
|
||||||
|
<MenuItem key={option.key()} value={i}>
|
||||||
|
{option.fieldName()}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
label="Title"
|
||||||
|
value={title}
|
||||||
|
helperText={"Leave blank to use the same title*"}
|
||||||
|
onChange={e => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={close}
|
||||||
|
variant="contained"
|
||||||
|
style={{ color: "black", backgroundColor: "red" }}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={duplicate}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
!ready ||
|
||||||
|
(fields[newFieldIndex] &&
|
||||||
|
!fields[newFieldIndex].permissions.includes(pond.Permission.PERMISSION_WRITE))
|
||||||
|
}>
|
||||||
|
Duplicate
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
235
src/harvestPlan/HarvestPlanActions.tsx
Normal file
235
src/harvestPlan/HarvestPlanActions.tsx
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Theme
|
||||||
|
} from "@mui/material";
|
||||||
|
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 ShareObject from "user/ShareObject";
|
||||||
|
import { isOffline } from "utils/environment";
|
||||||
|
import ObjectTeams from "teams/ObjectTeams";
|
||||||
|
import { planScope, HarvestPlan, Field } from "models";
|
||||||
|
import HarvestSettings from "./HarvestSettings";
|
||||||
|
import DuplicateHarvestPlan from "./DuplicateHarvestPlan";
|
||||||
|
import { Edit } from "@mui/icons-material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { blue } from "@mui/material/colors";
|
||||||
|
|
||||||
|
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 {
|
||||||
|
plan: HarvestPlan;
|
||||||
|
planField: Field;
|
||||||
|
fieldList: Field[];
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
refreshCallback: (updatedPlan?: HarvestPlan) => void;
|
||||||
|
hideNew?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpenState {
|
||||||
|
share: boolean;
|
||||||
|
users: boolean;
|
||||||
|
teams: boolean;
|
||||||
|
settings: boolean;
|
||||||
|
removeSelf: boolean;
|
||||||
|
duplicate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function HarvestPlanActions(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { plan, planField, fieldList, permissions, refreshCallback, hideNew } = props;
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const [openState, setOpenState] = useState<OpenState>({
|
||||||
|
share: false,
|
||||||
|
users: false,
|
||||||
|
teams: false,
|
||||||
|
settings: false,
|
||||||
|
removeSelf: false,
|
||||||
|
duplicate: false
|
||||||
|
});
|
||||||
|
const [updatePlan, setUpdatePlan] = useState(false);
|
||||||
|
|
||||||
|
const groupMenu = () => {
|
||||||
|
const canShare = permissions.includes(pond.Permission.PERMISSION_SHARE);
|
||||||
|
const canManageUsers = permissions.includes(pond.Permission.PERMISSION_USERS);
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="groupMenu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{!isOffline() &&
|
||||||
|
planField.permissions.includes(pond.Permission.PERMISSION_WRITE) &&
|
||||||
|
!hideNew && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setUpdatePlan(false);
|
||||||
|
setOpenState({ ...openState, settings: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon></ListItemIcon>
|
||||||
|
<ListItemText primary="New Plan" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && planField.permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, duplicate: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon></ListItemIcon>
|
||||||
|
<ListItemText primary="Duplicate Plan" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canShare && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, share: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ShareObjectIcon className={classes.shareIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, users: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{!isOffline() && canManageUsers && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, teams: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Teams" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const key = plan.key();
|
||||||
|
const label = plan.settings.title;
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<HarvestSettings
|
||||||
|
field={planField}
|
||||||
|
update={updatePlan}
|
||||||
|
open={openState.settings}
|
||||||
|
plan={plan.key() !== "" ? plan : undefined}
|
||||||
|
close={(refresh, updatedPlan) => {
|
||||||
|
setOpenState({ ...openState, settings: false });
|
||||||
|
if (refresh) {
|
||||||
|
refreshCallback(updatedPlan);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DuplicateHarvestPlan
|
||||||
|
open={openState.duplicate}
|
||||||
|
close={() => {
|
||||||
|
setOpenState({ ...openState, duplicate: false });
|
||||||
|
refreshCallback();
|
||||||
|
}}
|
||||||
|
fields={fieldList}
|
||||||
|
plan={plan}
|
||||||
|
/>
|
||||||
|
<ShareObject
|
||||||
|
scope={planScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.share}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
|
||||||
|
/>
|
||||||
|
<ObjectUsers
|
||||||
|
scope={planScope(key)}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.users}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<ObjectTeams
|
||||||
|
scope={planScope(key)}
|
||||||
|
label="Teams"
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.teams}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
{plan.key() !== "" && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setUpdatePlan(true);
|
||||||
|
setOpenState({ ...openState, settings: true });
|
||||||
|
}}>
|
||||||
|
<Edit /> Edit or Add Activity
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{groupMenu()}
|
||||||
|
{dialogs()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
209
src/harvestPlan/HarvestPlanDisplay.tsx
Normal file
209
src/harvestPlan/HarvestPlanDisplay.tsx
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Grid2 as Grid,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
LinearProgress,
|
||||||
|
Drawer
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Notes } from "@mui/icons-material";
|
||||||
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import { Field, HarvestPlan } from "models";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { getThemeType } from "theme";
|
||||||
|
import Chat from "chat/Chat";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
import HarvestPlanActions from "./HarvestPlanActions";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
plan: HarvestPlan;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
planField: Field;
|
||||||
|
loading: boolean;
|
||||||
|
fieldList: Field[];
|
||||||
|
changePlan: (updatedPlan?: HarvestPlan) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
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
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function HarvestPlanDisplay(props: Props) {
|
||||||
|
const { plan, loading, planField, fieldList, changePlan, permissions } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const [openNote, setOpenNote] = useState(false);
|
||||||
|
const isMobile = useMobile();
|
||||||
|
|
||||||
|
const planTable = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid container direction="column">
|
||||||
|
<Grid className={classes.dark}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Break Even Yield:</Typography>
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
{(
|
||||||
|
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
||||||
|
plan.bushelPrice()
|
||||||
|
).toFixed(2)}{" "}
|
||||||
|
BU
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.light}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Break Even Sales Price</Typography>
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
$
|
||||||
|
{(
|
||||||
|
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
||||||
|
plan.yieldTarget()
|
||||||
|
).toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="column" style={{ marginTop: 20 }}>
|
||||||
|
<Grid className={classes.dark}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Harvest Year:</Typography>
|
||||||
|
<Typography variant="subtitle1">{plan.harvestYear()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.light}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Title:</Typography>
|
||||||
|
<Typography variant="subtitle1">{plan.settings.title}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.dark}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Grain:</Typography>
|
||||||
|
<Typography variant="subtitle1">{GrainDescriber(plan.cropType()).name}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.light}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Variant:</Typography>
|
||||||
|
<Typography variant="subtitle1">{plan.grainType()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.dark}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Bushel Price:</Typography>
|
||||||
|
<Typography variant="subtitle1">${plan.bushelPrice()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.light}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Yield Target:</Typography>
|
||||||
|
<Typography variant="subtitle1">{plan.yieldTarget()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.dark}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Total Cost (acre):</Typography>
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
${(plan.totalEquipmentCost() + plan.totalMaterialCost()).toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid className={classes.light}>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Total Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">
|
||||||
|
$
|
||||||
|
{(
|
||||||
|
(plan.totalEquipmentCost() + plan.totalMaterialCost()) *
|
||||||
|
planField.acres()
|
||||||
|
).toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</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_HARVEST_PLAN} objectKey={plan.key()} />
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{loading ? (
|
||||||
|
<Box paddingTop="20px">
|
||||||
|
<LinearProgress color="primary" />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton
|
||||||
|
disabled={plan.key() === ""}
|
||||||
|
className={classes.avatar}
|
||||||
|
onClick={() => setOpenNote(true)}>
|
||||||
|
<Notes />
|
||||||
|
</IconButton>
|
||||||
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
style={{ fontWeight: 700, marginTop: "auto", marginBottom: "auto" }}>
|
||||||
|
Crop Plan{plan.key() !== "" ? " - " + plan.settings.title : " - None Found"}
|
||||||
|
</Typography>
|
||||||
|
{!isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<HarvestPlanActions
|
||||||
|
planField={planField}
|
||||||
|
fieldList={fieldList}
|
||||||
|
plan={plan}
|
||||||
|
permissions={permissions}
|
||||||
|
refreshCallback={updatedPlan => {
|
||||||
|
changePlan(updatedPlan);
|
||||||
|
}} //possibly need to re-load plan here
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<HarvestPlanActions
|
||||||
|
planField={planField}
|
||||||
|
fieldList={fieldList}
|
||||||
|
plan={plan}
|
||||||
|
permissions={permissions}
|
||||||
|
refreshCallback={updatedPlan => {
|
||||||
|
changePlan(updatedPlan);
|
||||||
|
}} //possibly need to re-load plan here
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{plan.key() !== "" && planTable()}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
{noteDrawer()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
728
src/harvestPlan/HarvestSettings.tsx
Normal file
728
src/harvestPlan/HarvestSettings.tsx
Normal file
|
|
@ -0,0 +1,728 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Grid2 as Grid,
|
||||||
|
InputAdornment,
|
||||||
|
MenuItem,
|
||||||
|
Tab,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Tabs,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Tooltip,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { useMobile, useSnackbar } from "hooks";
|
||||||
|
import { Field, HarvestPlan/*, Task*/ } from "models";
|
||||||
|
import moment from "moment";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useHarvestPlanAPI/*, useTaskAPI*/ } from "providers";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
//import TaskSettings from "tasks/TaskSettings";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
close: (reload?: boolean, updatedPlan?: HarvestPlan) => void;
|
||||||
|
field: Field;
|
||||||
|
update?: boolean;
|
||||||
|
plan?: HarvestPlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cropOptions = [
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: "Barley"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
label: "Canola"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
label: "Corn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 6,
|
||||||
|
label: "Oats"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 7,
|
||||||
|
label: "Peanuts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 8,
|
||||||
|
label: "Rapeseed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 9,
|
||||||
|
label: "Long Grain Rice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 10,
|
||||||
|
label: "Medium Grain Rice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 11,
|
||||||
|
label: "Short Grain Rice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 12,
|
||||||
|
label: "Sorghum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 13,
|
||||||
|
label: "Soybeans"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 14,
|
||||||
|
label: "Sunflower"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: "Buckwheat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 15,
|
||||||
|
label: "Durum Wheat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 16,
|
||||||
|
label: "Hard Red Wheat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 17,
|
||||||
|
label: "Unretted Flax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 18,
|
||||||
|
label: "Dew Retted Flax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 19,
|
||||||
|
label: "Yellow Peas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 20,
|
||||||
|
label: "Robin Lentils"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 21,
|
||||||
|
label: "Redberry Lentils"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 22,
|
||||||
|
label: "Blaze Lentils"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
stepper: {
|
||||||
|
padding: theme.spacing(0.5)
|
||||||
|
},
|
||||||
|
secondaryColor: {
|
||||||
|
color: theme.palette.secondary.main
|
||||||
|
},
|
||||||
|
textSecondaryColor: {
|
||||||
|
color: theme.palette.text.secondary
|
||||||
|
},
|
||||||
|
bottomSpacing: {
|
||||||
|
marginBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
width: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
flexShrink: 1
|
||||||
|
},
|
||||||
|
tabSmall: {
|
||||||
|
width: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
flexShrink: 1,
|
||||||
|
minWidth: "48px"
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
width: "16%",
|
||||||
|
minWidth: "16%"
|
||||||
|
},
|
||||||
|
buttonSpacing: {
|
||||||
|
margin: 5
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const steps = ["Pre-Seeding", "Seeding", "Post-Seeding", "Harvest", "Fall"];
|
||||||
|
|
||||||
|
export default function HarvestSettings(props: Props) {
|
||||||
|
const { open, plan, close, field, update } = props;
|
||||||
|
const [planKey, setPlanKey] = useState<string>();
|
||||||
|
const [newPlan, setNewPlan] = useState(HarvestPlan.create());
|
||||||
|
const [activeStep, setActiveStep] = useState(0);
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const classes = useStyles();
|
||||||
|
//const [planTasks, setPlanTasks] = useState<Task[]>([]);
|
||||||
|
//const taskAPI = useTaskAPI();
|
||||||
|
const harvestPlanAPI = useHarvestPlanAPI();
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [cropType, setCropType] = useState(0);
|
||||||
|
const [customCropType, setCustomCropType] = useState("");
|
||||||
|
const [useCustom, setUseCustom] = useState(false);
|
||||||
|
const [variety, setVariety] = useState("");
|
||||||
|
const [harvestYear, setHarvestYear] = useState(new Date().getFullYear());
|
||||||
|
const [targetYield, setTargetYield] = useState(0);
|
||||||
|
const [price, setPrice] = useState("");
|
||||||
|
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
||||||
|
const [type, setType] = useState("");
|
||||||
|
//const [taskToEdit, setTaskToEdit] = useState<Task>();
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
// const loadTasks = useCallback(() => {
|
||||||
|
// if (planKey) {
|
||||||
|
// taskAPI.getMultiTasks([planKey]).then(resp => {
|
||||||
|
// setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }, [taskAPI, planKey]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// loadTasks();
|
||||||
|
// }, [loadTasks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!plan || (plan && plan.permissions.includes(pond.Permission.PERMISSION_WRITE))) {
|
||||||
|
setActiveStep(0);
|
||||||
|
} else {
|
||||||
|
setActiveStep(1);
|
||||||
|
}
|
||||||
|
setPlanKey(undefined);
|
||||||
|
setTitle("");
|
||||||
|
setVariety("");
|
||||||
|
setHarvestYear(new Date().getFullYear());
|
||||||
|
setTargetYield(0);
|
||||||
|
setPrice("");
|
||||||
|
if (update && plan) {
|
||||||
|
setPlanKey(plan.key());
|
||||||
|
setTitle(plan.settings.title);
|
||||||
|
setCropType(plan.cropType());
|
||||||
|
setCustomCropType(plan.customCropType());
|
||||||
|
setVariety(plan.grainType());
|
||||||
|
setHarvestYear(plan.harvestYear());
|
||||||
|
setTargetYield(plan.yieldTarget());
|
||||||
|
setPrice(plan.settings.bushelPrice.toFixed(2).toString());
|
||||||
|
} else {
|
||||||
|
setCropType(field.crop());
|
||||||
|
setCustomCropType(field.customType());
|
||||||
|
setUseCustom(field.crop() === pond.Grain.GRAIN_CUSTOM);
|
||||||
|
}
|
||||||
|
}, [plan, field, update]);
|
||||||
|
|
||||||
|
const closeDialog = (reload?: boolean, updatedPlan?: HarvestPlan) => {
|
||||||
|
close(reload, updatedPlan);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const showTasks = (type: string) => {
|
||||||
|
// let tasks: Task[] = [];
|
||||||
|
// let costTotal = 0;
|
||||||
|
// let secondaryCostTotal = 0;
|
||||||
|
// planTasks.forEach(task => {
|
||||||
|
// if (task.settings.type === type) {
|
||||||
|
// tasks.push(task);
|
||||||
|
// costTotal += task.cost();
|
||||||
|
// secondaryCostTotal += task.secondaryCost();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// let frag = (
|
||||||
|
// <React.Fragment>
|
||||||
|
// <Table>
|
||||||
|
// <TableHead>
|
||||||
|
// <TableRow>
|
||||||
|
// <TableCell>Title</TableCell>
|
||||||
|
// <TableCell>Description</TableCell>
|
||||||
|
// {!isMobile && <TableCell>Material Cost(acre)</TableCell>}
|
||||||
|
// {!isMobile && <TableCell>Equipment Cost(acre)</TableCell>}
|
||||||
|
// <TableCell>Total(acre)</TableCell>
|
||||||
|
// {!isMobile && <TableCell>Start -End</TableCell>}
|
||||||
|
// <TableCell>Edit</TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// </TableHead>
|
||||||
|
// <TableBody>
|
||||||
|
// {tasks.map((task, i) => (
|
||||||
|
// <TableRow key={i}>
|
||||||
|
// <TableCell>{task.title()}</TableCell>
|
||||||
|
// <TableCell>{task.description()}</TableCell>
|
||||||
|
// {!isMobile && <TableCell>${task.cost().toFixed(2)}</TableCell>}
|
||||||
|
// {!isMobile && <TableCell>${task.secondaryCost().toFixed(2)}</TableCell>}
|
||||||
|
// <TableCell>
|
||||||
|
// ${(Math.round((task.cost() + task.secondaryCost()) * 100) / 100).toFixed(2)}
|
||||||
|
// </TableCell>
|
||||||
|
// {!isMobile && (
|
||||||
|
// <TableCell>
|
||||||
|
// {task.start()} {task.end()}
|
||||||
|
// </TableCell>
|
||||||
|
// )}
|
||||||
|
// <TableCell>
|
||||||
|
// <Button
|
||||||
|
// color="primary"
|
||||||
|
// onClick={() => {
|
||||||
|
// setTaskToEdit(task);
|
||||||
|
// setNewTaskDialog(true);
|
||||||
|
// }}>
|
||||||
|
// Edit
|
||||||
|
// </Button>
|
||||||
|
// </TableCell>
|
||||||
|
// </TableRow>
|
||||||
|
// ))}
|
||||||
|
// </TableBody>
|
||||||
|
// </Table>
|
||||||
|
// <Box>
|
||||||
|
// <Typography>
|
||||||
|
// Total: $
|
||||||
|
// {(Math.round((costTotal + secondaryCostTotal) * field.acres() * 100) / 100).toFixed(2)}
|
||||||
|
// </Typography>
|
||||||
|
// </Box>
|
||||||
|
// </React.Fragment>
|
||||||
|
// );
|
||||||
|
// return frag;
|
||||||
|
// };
|
||||||
|
|
||||||
|
const savePlan = () => {
|
||||||
|
let newPlanSettings = pond.HarvestPlanSettings.create({
|
||||||
|
title: title,
|
||||||
|
field: field.key(),
|
||||||
|
createDate: moment.now(),
|
||||||
|
cropType: cropType,
|
||||||
|
grainType: variety,
|
||||||
|
harvestYear: harvestYear,
|
||||||
|
yieldTarget: targetYield,
|
||||||
|
bushelPrice: !isNaN(parseFloat(price)) ? Math.round(parseFloat(price) * 100) / 100 : 0
|
||||||
|
});
|
||||||
|
setNewPlan(HarvestPlan.create(pond.HarvestPlan.fromObject({ settings: newPlanSettings })));
|
||||||
|
|
||||||
|
harvestPlanAPI.addHarvestPlan(newPlanSettings).then(resp => {
|
||||||
|
setPlanKey(resp.data.harvestPlan);
|
||||||
|
openSnack("Plan Created");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePlan = () => {
|
||||||
|
//material costs per acre
|
||||||
|
let preseedMatCost = 0;
|
||||||
|
let seedMatCost = 0;
|
||||||
|
let postseedMatCost = 0;
|
||||||
|
let harvestMatCost = 0;
|
||||||
|
let fallMatCost = 0;
|
||||||
|
|
||||||
|
//equipment costs per acre
|
||||||
|
let preseedEqCost = 0;
|
||||||
|
let seedEqCost = 0;
|
||||||
|
let postseedEqCost = 0;
|
||||||
|
let harvestEqCost = 0;
|
||||||
|
let fallEqCost = 0;
|
||||||
|
// planTasks.forEach(task => {
|
||||||
|
// switch (task.settings.type) {
|
||||||
|
// case "preseed":
|
||||||
|
// preseedMatCost += task.cost();
|
||||||
|
// preseedEqCost += task.secondaryCost();
|
||||||
|
// break;
|
||||||
|
// case "seed":
|
||||||
|
// seedMatCost += task.cost();
|
||||||
|
// seedEqCost += task.secondaryCost();
|
||||||
|
// break;
|
||||||
|
// case "postseed":
|
||||||
|
// postseedMatCost += task.cost();
|
||||||
|
// postseedEqCost += task.secondaryCost();
|
||||||
|
// break;
|
||||||
|
// case "harvest":
|
||||||
|
// harvestMatCost += task.cost();
|
||||||
|
// harvestEqCost += task.secondaryCost();
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// fallMatCost += task.cost();
|
||||||
|
// fallEqCost += task.secondaryCost();
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
let tempPlan = plan ?? newPlan;
|
||||||
|
tempPlan.settings.cropType = cropType;
|
||||||
|
tempPlan.settings.field = field.key();
|
||||||
|
tempPlan.settings.title = title;
|
||||||
|
tempPlan.settings.grainType = variety;
|
||||||
|
tempPlan.settings.harvestYear = harvestYear;
|
||||||
|
tempPlan.settings.yieldTarget = targetYield;
|
||||||
|
tempPlan.settings.bushelPrice = !isNaN(parseFloat(price))
|
||||||
|
? Math.round(parseFloat(price) * 100) / 100
|
||||||
|
: 0;
|
||||||
|
tempPlan.settings.preSeedMaterials = preseedMatCost;
|
||||||
|
tempPlan.settings.preSeedEquipment = preseedEqCost;
|
||||||
|
tempPlan.settings.seedMaterials = seedMatCost;
|
||||||
|
tempPlan.settings.seedEquipment = seedEqCost;
|
||||||
|
tempPlan.settings.postSeedMaterials = postseedMatCost;
|
||||||
|
tempPlan.settings.postSeedEquipment = postseedEqCost;
|
||||||
|
tempPlan.settings.harvestMaterials = harvestMatCost;
|
||||||
|
tempPlan.settings.harvestEquipment = harvestEqCost;
|
||||||
|
tempPlan.settings.fallMaterials = fallMatCost;
|
||||||
|
tempPlan.settings.fallEquipment = fallEqCost;
|
||||||
|
if (planKey) {
|
||||||
|
harvestPlanAPI
|
||||||
|
.updateHarvestPlan(planKey, tempPlan.settings)
|
||||||
|
.then(resp => openSnack("Plan Updated"));
|
||||||
|
}
|
||||||
|
closeDialog(true, tempPlan);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePlan = () => {
|
||||||
|
if (planKey) {
|
||||||
|
harvestPlanAPI.removeHarvestPlan(planKey).then(resp => {
|
||||||
|
openSnack("Plan Deleted");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setDeleteOpen(false);
|
||||||
|
closeDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextStep = () => {
|
||||||
|
setActiveStep(prevStep => prevStep + 1);
|
||||||
|
};
|
||||||
|
const handleBack = () => {
|
||||||
|
setActiveStep(prevActiveStep => prevActiveStep - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepper = () => {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="center" width="100%">
|
||||||
|
<Tabs
|
||||||
|
value={activeStep}
|
||||||
|
onChange={(_, value) => setActiveStep(value)}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
variant="fullWidth"
|
||||||
|
aria-label="bin tabs"
|
||||||
|
classes={{ root: classes.tabs }}>
|
||||||
|
<Tab
|
||||||
|
key={0}
|
||||||
|
classes={{ root: classes.tab }}
|
||||||
|
disabled={
|
||||||
|
!planKey || !(plan && plan.permissions.includes(pond.Permission.PERMISSION_WRITE))
|
||||||
|
}
|
||||||
|
label={"General"}
|
||||||
|
aria-label={"general"}
|
||||||
|
/>
|
||||||
|
{steps.map((label, i) => (
|
||||||
|
<Tab
|
||||||
|
key={i + 1}
|
||||||
|
classes={{ root: classes.tab }}
|
||||||
|
disabled={!planKey}
|
||||||
|
label={label}
|
||||||
|
aria-label={label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const general = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
label="Title"
|
||||||
|
value={title}
|
||||||
|
onChange={e => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
{useCustom ? (
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
label="Crop"
|
||||||
|
value={customCropType}
|
||||||
|
onChange={e => setCustomCropType(e.target.value)}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
select
|
||||||
|
label="Crop"
|
||||||
|
value={cropType}
|
||||||
|
onChange={e => setCropType(+e.target.value)}
|
||||||
|
color="primary">
|
||||||
|
{cropOptions.map(option => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
)}
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="text"
|
||||||
|
label="Variety"
|
||||||
|
value={variety}
|
||||||
|
onChange={e => setVariety(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="number"
|
||||||
|
label="Year"
|
||||||
|
value={harvestYear}
|
||||||
|
onChange={e => {
|
||||||
|
!isNaN(+e.target.value) && setHarvestYear(+e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="number"
|
||||||
|
label="Target Yield per acre"
|
||||||
|
value={targetYield}
|
||||||
|
onChange={e => {
|
||||||
|
!isNaN(+e.target.value) && setTargetYield(+e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
id="bushPrice"
|
||||||
|
label="Estimated sale price per bushel"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
helperText="Must be a valid number*"
|
||||||
|
value={price}
|
||||||
|
error={isNaN(+price) && price !== ""}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <InputAdornment position="start">$</InputAdornment>
|
||||||
|
}}
|
||||||
|
onChange={e => {
|
||||||
|
setPrice(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const preSeed = () => {
|
||||||
|
return (
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setType("preseed");
|
||||||
|
setNewTaskDialog(true);
|
||||||
|
}}>
|
||||||
|
Add Crop Activity
|
||||||
|
</Button>
|
||||||
|
<Typography>Planned Activities</Typography>
|
||||||
|
{/* {showTasks("preseed")} */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const seed = () => {
|
||||||
|
return (
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setType("seed");
|
||||||
|
setNewTaskDialog(true);
|
||||||
|
}}>
|
||||||
|
Add Crop Activity
|
||||||
|
</Button>
|
||||||
|
<Typography>Planned Activities</Typography>
|
||||||
|
{/* {showTasks("seed")} */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const postSeed = () => {
|
||||||
|
return (
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setType("postseed");
|
||||||
|
setNewTaskDialog(true);
|
||||||
|
}}>
|
||||||
|
Add Crop Activity
|
||||||
|
</Button>
|
||||||
|
<Typography>Planned Activities</Typography>
|
||||||
|
{/* {showTasks("postseed")} */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const harvest = () => {
|
||||||
|
return (
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setType("harvest");
|
||||||
|
setNewTaskDialog(true);
|
||||||
|
}}>
|
||||||
|
Add Crop Activity
|
||||||
|
</Button>
|
||||||
|
<Typography>Planned Activities</Typography>
|
||||||
|
{/* {showTasks("harvest")} */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fall = () => {
|
||||||
|
return (
|
||||||
|
<Box textAlign="center">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setType("fall");
|
||||||
|
setNewTaskDialog(true);
|
||||||
|
}}>
|
||||||
|
Add Crop Activity
|
||||||
|
</Button>
|
||||||
|
<Typography>Planned Activities</Typography>
|
||||||
|
{/* {showTasks("fall")} */}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepperContent = (step: number) => {
|
||||||
|
switch (step) {
|
||||||
|
case 1:
|
||||||
|
return preSeed();
|
||||||
|
case 2:
|
||||||
|
return seed();
|
||||||
|
case 3:
|
||||||
|
return postSeed();
|
||||||
|
case 4:
|
||||||
|
return harvest();
|
||||||
|
case 5:
|
||||||
|
return fall();
|
||||||
|
default:
|
||||||
|
return general();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={deleteOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
Are you sure you wish to delete plan? This action is irreversible.
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setDeleteOpen(false)}>Cancel</Button>
|
||||||
|
<Button onClick={deletePlan}>
|
||||||
|
<Typography color="error">Delete</Typography>
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ResponsiveDialog open={open} onClose={() => closeDialog()} maxWidth="lg">
|
||||||
|
<DialogTitle>Create New Crop Plan</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{stepper()}
|
||||||
|
{stepperContent(activeStep)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid>
|
||||||
|
{planKey && plan && field.permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{ color: "black", backgroundColor: "red" }}
|
||||||
|
onClick={() => setDeleteOpen(true)}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
className={classes.buttonSpacing}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => closeDialog()}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{activeStep === 0 && !planKey && (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button
|
||||||
|
className={classes.buttonSpacing}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
savePlan();
|
||||||
|
}}
|
||||||
|
color="primary">
|
||||||
|
Create Plan
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
{activeStep > 0 && (
|
||||||
|
<Button
|
||||||
|
className={classes.buttonSpacing}
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleBack}
|
||||||
|
color="primary">
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{activeStep < 5 && (activeStep > 0 || planKey) && (
|
||||||
|
<Button
|
||||||
|
className={classes.buttonSpacing}
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleNextStep}
|
||||||
|
color="primary">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{(planKey || activeStep > 0) && (
|
||||||
|
<Tooltip title="Finish Harvest Plan">
|
||||||
|
<Button
|
||||||
|
className={classes.buttonSpacing}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={updatePlan}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
{deleteDialog()}
|
||||||
|
{/* <TaskSettings
|
||||||
|
task={taskToEdit}
|
||||||
|
hasCost
|
||||||
|
costTitle="Material Cost(acres)"
|
||||||
|
secondaryCostTitle="Equipment Cost(acre)"
|
||||||
|
objectKey={planKey}
|
||||||
|
type={type}
|
||||||
|
open={newTaskDialog}
|
||||||
|
onClose={r => {
|
||||||
|
setType("");
|
||||||
|
setTaskToEdit(undefined);
|
||||||
|
setNewTaskDialog(false);
|
||||||
|
if (r) {
|
||||||
|
loadTasks();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
Grid,
|
Grid2 as Grid,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
|
|
@ -355,7 +355,7 @@ export default function MapBase(props: Props) {
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Grid container direction={isMobile ? "column" : "row"}>
|
<Grid container direction={isMobile ? "column" : "row"}>
|
||||||
<Grid item>
|
<Grid>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={classes.iconButtons}
|
className={classes.iconButtons}
|
||||||
style={
|
style={
|
||||||
|
|
@ -371,7 +371,7 @@ export default function MapBase(props: Props) {
|
||||||
<MarkerMove />
|
<MarkerMove />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={classes.iconButtons}
|
className={classes.iconButtons}
|
||||||
style={msHovered ? { background: "grey" } : {}}
|
style={msHovered ? { background: "grey" } : {}}
|
||||||
|
|
@ -382,7 +382,7 @@ export default function MapBase(props: Props) {
|
||||||
<Layers />
|
<Layers />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={classes.iconButtons}
|
className={classes.iconButtons}
|
||||||
style={hcHovered ? { background: "grey" } : {}}
|
style={hcHovered ? { background: "grey" } : {}}
|
||||||
|
|
@ -393,7 +393,7 @@ export default function MapBase(props: Props) {
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={classes.iconButtons}
|
className={classes.iconButtons}
|
||||||
style={
|
style={
|
||||||
|
|
@ -542,7 +542,7 @@ export default function MapBase(props: Props) {
|
||||||
|
|
||||||
const onCreate = (event: any, objectToCut?: string, measurement?: boolean) => {
|
const onCreate = (event: any, objectToCut?: string, measurement?: boolean) => {
|
||||||
let feature = event.features[0];
|
let feature = event.features[0];
|
||||||
console.log(feature)
|
//console.log(feature)
|
||||||
//if the string exist we are adding a hole to an existing object
|
//if the string exist we are adding a hole to an existing object
|
||||||
if (objectToCut) {
|
if (objectToCut) {
|
||||||
if (props.cutHoleInPolygon) {
|
if (props.cutHoleInPolygon) {
|
||||||
|
|
@ -554,7 +554,7 @@ export default function MapBase(props: Props) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (props.addNewShape) {
|
if (props.addNewShape) {
|
||||||
console.log("add new shape")
|
//console.log("add new shape")
|
||||||
props.addNewShape(feature.geometry.coordinates, feature.geometry.type);
|
props.addNewShape(feature.geometry.coordinates, feature.geometry.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -665,7 +665,7 @@ export default function MapBase(props: Props) {
|
||||||
latitude={homePin.latitude}
|
latitude={homePin.latitude}
|
||||||
draggable
|
draggable
|
||||||
onDragEnd={homeDragEnd}
|
onDragEnd={homeDragEnd}
|
||||||
offset={[0, -25]}>
|
offset={[0, -30]}>
|
||||||
<Box
|
<Box
|
||||||
className={classes.pin}
|
className={classes.pin}
|
||||||
style={{ pointerEvents: "none", width: 50, height: 50, background: "red" }}>
|
style={{ pointerEvents: "none", width: 50, height: 50, background: "red" }}>
|
||||||
|
|
@ -674,11 +674,11 @@ export default function MapBase(props: Props) {
|
||||||
transform: "rotate(-45deg)",
|
transform: "rotate(-45deg)",
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
marginTop: -10,
|
marginTop: 10,
|
||||||
marginLeft: -10,
|
marginLeft: 7,
|
||||||
pointerEvents: "none"
|
pointerEvents: "none"
|
||||||
}}>
|
}}>
|
||||||
<HomeIcon type="light" />
|
<HomeIcon type="light" height={30} width={30} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Marker>
|
</Marker>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export default function DrawController(props: Props) {
|
||||||
const cutRef = useRef(); //the object id for what shape to cut a hole
|
const cutRef = useRef(); //the object id for what shape to cut a hole
|
||||||
const mRef = useRef<boolean>();
|
const mRef = useRef<boolean>();
|
||||||
const lineLimitRef = useRef<number | undefined>();
|
const lineLimitRef = useRef<number | undefined>();
|
||||||
const pointsRef = useRef<number[][]>([]);
|
//const pointsRef = useRef<number[][]>([]);
|
||||||
const { current: map } = useMap();
|
const { current: map } = useMap();
|
||||||
|
|
||||||
//use effect is just for adding and removing the controller from the map
|
//use effect is just for adding and removing the controller from the map
|
||||||
|
|
@ -71,18 +71,18 @@ export default function DrawController(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
map.on("click", e => {
|
// map.on("click", e => {
|
||||||
if (map.hasControl(draw) && draw.getMode() === "draw_line_string") {
|
// if (map.hasControl(draw) && draw.getMode() === "draw_line_string") {
|
||||||
if (lineLimitRef.current) {
|
// if (lineLimitRef.current) {
|
||||||
pointsRef.current.push([e.lngLat.lng, e.lngLat.lat]);
|
// pointsRef.current.push([e.lngLat.lng, e.lngLat.lat]);
|
||||||
if (pointsRef.current.length === lineLimitRef.current) {
|
// if (pointsRef.current.length === lineLimitRef.current) {
|
||||||
draw.changeMode("simple_select"); //changing the mode causes the create event to fire
|
// draw.changeMode("simple_select"); //changing the mode causes the create event to fire
|
||||||
pointsRef.current = [];
|
// pointsRef.current = [];
|
||||||
draw.changeMode("draw_line_string");
|
// draw.changeMode("draw_line_string");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}, [map]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [map]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import {
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Notes } from "@mui/icons-material";
|
import { Notes } from "@mui/icons-material";
|
||||||
import DisplayDrawer from "common/DisplayDrawer";
|
import DisplayDrawer from "common/DisplayDrawer";
|
||||||
//import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
|
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
|
||||||
import { Field, fieldScope, /*HarvestPlan,*/ teamScope } from "models";
|
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
//import TaskViewer from "tasks/TaskViewer";
|
//import TaskViewer from "tasks/TaskViewer";
|
||||||
import Weather from "weather/weather";
|
import Weather from "weather/weather";
|
||||||
|
|
@ -21,9 +21,9 @@ import GrainDescriber from "grain/GrainDescriber";
|
||||||
import Chat from "chat/Chat";
|
import Chat from "chat/Chat";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import { useGlobalState, /*useHarvestPlanAPI,*/ useUserAPI } from "providers";
|
import { useGlobalState, useHarvestPlanAPI, useUserAPI } from "providers";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
//import FieldActions from "field/FieldActions";
|
import FieldActions from "field/FieldActions";
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -77,13 +77,13 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
export default function FieldDrawer(props: Props) {
|
export default function FieldDrawer(props: Props) {
|
||||||
const { open, closeDrawer, selectedFieldKey, fields, moveMap } = props;
|
const { open, closeDrawer, selectedFieldKey, fields, moveMap } = props;
|
||||||
const [field, setField] = useState<Field>(Field.create());
|
const [field, setField] = useState<Field>(Field.create());
|
||||||
//const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
|
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
|
||||||
const [value, setValue] = useState(0);
|
const [value, setValue] = useState(0);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [openNote, setOpenNote] = useState(false);
|
const [openNote, setOpenNote] = useState(false);
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const [{ as, user }] = useGlobalState();
|
const [{ as, user }] = useGlobalState();
|
||||||
//const hPlanAPI = useHarvestPlanAPI();
|
const hPlanAPI = useHarvestPlanAPI();
|
||||||
const [planLoading, setPlanLoading] = useState(false);
|
const [planLoading, setPlanLoading] = useState(false);
|
||||||
const userAPI = useUserAPI();
|
const userAPI = useUserAPI();
|
||||||
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
|
|
@ -129,24 +129,24 @@ export default function FieldDrawer(props: Props) {
|
||||||
}
|
}
|
||||||
}, [as, user, userAPI, field]); //eslint-disable-line react-hooks/exhaustive-deps
|
}, [as, user, userAPI, field]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (field.key() !== "") {
|
if (field.key() !== "") {
|
||||||
// hPlanAPI
|
hPlanAPI
|
||||||
// .listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
|
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
|
||||||
// .then(resp => {
|
.then(resp => {
|
||||||
// if (resp.data.harvestPlan.length > 0) {
|
if (resp.data.harvestPlan.length > 0) {
|
||||||
// let plan = resp.data.harvestPlan[0];
|
let plan = resp.data.harvestPlan[0];
|
||||||
// setHPlan(HarvestPlan.any(plan));
|
setHPlan(HarvestPlan.any(plan));
|
||||||
// } else {
|
} else {
|
||||||
// setHPlan(HarvestPlan.create());
|
setHPlan(HarvestPlan.create());
|
||||||
// }
|
}
|
||||||
// setPlanLoading(false);
|
setPlanLoading(false);
|
||||||
// })
|
})
|
||||||
// .catch(err => {
|
.catch(err => {
|
||||||
// //openSnack("Failed to load plan");
|
//openSnack("Failed to load plan");
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// }, [field, as, hPlanAPI]);
|
}, [field, as, hPlanAPI]);
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
|
|
@ -206,7 +206,7 @@ export default function FieldDrawer(props: Props) {
|
||||||
let taskLoadKeys: string[] = [];
|
let taskLoadKeys: string[] = [];
|
||||||
if (!planLoading) {
|
if (!planLoading) {
|
||||||
field.key() !== "" && taskLoadKeys.push(field.key());
|
field.key() !== "" && taskLoadKeys.push(field.key());
|
||||||
//hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
|
hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|
@ -255,7 +255,7 @@ export default function FieldDrawer(props: Props) {
|
||||||
<Box padding={3}>
|
<Box padding={3}>
|
||||||
<Divider style={{ padding: 2 }} />
|
<Divider style={{ padding: 2 }} />
|
||||||
</Box>
|
</Box>
|
||||||
{/* <HarvestPlanDisplay
|
<HarvestPlanDisplay
|
||||||
plan={hPlan}
|
plan={hPlan}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
planField={field}
|
planField={field}
|
||||||
|
|
@ -266,7 +266,7 @@ export default function FieldDrawer(props: Props) {
|
||||||
setHPlan(updatedPlan);
|
setHPlan(updatedPlan);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/> */}
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
<TabPanelMine value={value} index={1}>
|
<TabPanelMine value={value} index={1}>
|
||||||
|
|
@ -311,14 +311,14 @@ export default function FieldDrawer(props: Props) {
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
// objectActions={
|
objectActions={
|
||||||
// field.settings.fieldGeoData &&
|
field.settings.fieldGeoData &&
|
||||||
// field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE ? (
|
field.settings.fieldGeoData.origin === pond.DataOrigin.DATA_ORIGIN_ADAPTIVE ? (
|
||||||
// <FieldActions field={field} permissions={permissions} refreshCallback={() => {}} />
|
<FieldActions field={field} permissions={permissions} refreshCallback={() => {}} />
|
||||||
// ) : (
|
) : (
|
||||||
// undefined
|
undefined
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
/>
|
/>
|
||||||
{noteDrawer()}
|
{noteDrawer()}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Box } from "@mui/material";
|
||||||
import DisplayDrawer from "common/DisplayDrawer";
|
import DisplayDrawer from "common/DisplayDrawer";
|
||||||
import MapMarkerSettings from "maps/MapMarkerSettings";
|
import MapMarkerSettings from "maps/MapMarkerSettings";
|
||||||
import { GrainBag as BagModel } from "models/GrainBag";
|
import { GrainBag as BagModel } from "models/GrainBag";
|
||||||
//import GrainBag from "pages/grainBag";
|
import GrainBag from "pages/grainBag";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGrainBagAPI, useSnackbar } from "providers";
|
import { useGrainBagAPI, useSnackbar } from "providers";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
@ -86,8 +86,8 @@ export default function GrainBagDrawer(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawerBody = () => {
|
const drawerBody = () => {
|
||||||
//return <Box>{bag.key() !== "" && <GrainBag bagKey={bag.key()} mobileView />}</Box>;
|
return <Box>{bag.key() !== "" && <GrainBag bagKey={bag.key()} mobileView />}</Box>;
|
||||||
return <Box></Box>
|
//return <Box></Box>
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ import {
|
||||||
import AgMapTools from "../mapObjectTools/agMapTools";
|
import AgMapTools from "../mapObjectTools/agMapTools";
|
||||||
import FieldDrawer from "../mapDrawers/FieldDrawer";
|
import FieldDrawer from "../mapDrawers/FieldDrawer";
|
||||||
import { coordDistance, shapeFromCoords } from "models/GeometryMapping";
|
import { coordDistance, shapeFromCoords } from "models/GeometryMapping";
|
||||||
//import FieldSettings from "field/FieldSettings";
|
import FieldSettings from "field/FieldSettings";
|
||||||
import GrainBagSettings from "grainBag/grainBagSettings";
|
import GrainBagSettings from "grainBag/grainBagSettings";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { Result } from "@mapbox/mapbox-gl-geocoder";
|
import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
|
|
@ -1327,7 +1327,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
getOptionLabel={(option: BagModel) => option.name()}
|
getOptionLabel={(option: BagModel) => option.name()}
|
||||||
fullWidth
|
fullWidth
|
||||||
onChange={(e, val) => {
|
onChange={(e, val) => {
|
||||||
val && setSelectKey(val.key);
|
val && setSelectKey(val.key());
|
||||||
}}
|
}}
|
||||||
renderInput={params => (
|
renderInput={params => (
|
||||||
<TextField {...params} label="Bags (leave blank to create a new one)" />
|
<TextField {...params} label="Bags (leave blank to create a new one)" />
|
||||||
|
|
@ -1518,9 +1518,6 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
addNewShape={(coords, type) => {
|
addNewShape={(coords, type) => {
|
||||||
console.log("controller add shape")
|
|
||||||
console.log(coords)
|
|
||||||
console.log(type)
|
|
||||||
if (type === "Polygon") {
|
if (type === "Polygon") {
|
||||||
//if it is a polygon it is a field (for now until we add more objects that use polygons)
|
//if it is a polygon it is a field (for now until we add more objects that use polygons)
|
||||||
let newShapes: pond.Shape[] = [];
|
let newShapes: pond.Shape[] = [];
|
||||||
|
|
@ -1842,7 +1839,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
longitude={newMarkerLong}
|
longitude={newMarkerLong}
|
||||||
latitude={newMarkerLat}
|
latitude={newMarkerLat}
|
||||||
/>
|
/>
|
||||||
{/* <FieldSettings
|
<FieldSettings
|
||||||
selectedField={fieldsRef.current.get(objectKey)}
|
selectedField={fieldsRef.current.get(objectKey)}
|
||||||
updateFields={(key, settings) => {
|
updateFields={(key, settings) => {
|
||||||
let data = clone(geoData);
|
let data = clone(geoData);
|
||||||
|
|
@ -1880,7 +1877,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
setOpenFieldSettings(false);
|
setOpenFieldSettings(false);
|
||||||
}}
|
}}
|
||||||
borders={fieldBorders}
|
borders={fieldBorders}
|
||||||
/> */}
|
/>
|
||||||
<GrainBagSettings
|
<GrainBagSettings
|
||||||
open={openBagSettings}
|
open={openBagSettings}
|
||||||
coordinates={bagCoordinates.current}
|
coordinates={bagCoordinates.current}
|
||||||
|
|
|
||||||
160
src/models/HarvestPlan.ts
Normal file
160
src/models/HarvestPlan.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
|
export class HarvestPlan {
|
||||||
|
public settings: pond.HarvestPlanSettings = pond.HarvestPlanSettings.create();
|
||||||
|
public status: pond.HarvestPlanStatus = pond.HarvestPlanStatus.create();
|
||||||
|
public permissions: pond.Permission[] = [];
|
||||||
|
|
||||||
|
public static create(pb?: pond.HarvestPlan): HarvestPlan {
|
||||||
|
let my = new HarvestPlan();
|
||||||
|
if (pb) {
|
||||||
|
my.settings = pond.HarvestPlanSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
||||||
|
my.status = pond.HarvestPlanStatus.fromObject(cloneDeep(or(pb.status, {})));
|
||||||
|
my.permissions = pb.planPermissions;
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static clone(other?: HarvestPlan): HarvestPlan {
|
||||||
|
if (other) {
|
||||||
|
return HarvestPlan.create(
|
||||||
|
pond.HarvestPlan.fromObject({
|
||||||
|
settings: cloneDeep(other.settings),
|
||||||
|
status: cloneDeep(other.status)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return HarvestPlan.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static any(data: any): HarvestPlan {
|
||||||
|
return HarvestPlan.create(pond.HarvestPlan.fromObject(cloneDeep(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public key(): string {
|
||||||
|
return this.settings.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public totalMaterialCost(): number {
|
||||||
|
return (
|
||||||
|
this.settings.seedMaterials +
|
||||||
|
this.settings.preSeedMaterials +
|
||||||
|
this.settings.postSeedMaterials +
|
||||||
|
this.settings.harvestMaterials +
|
||||||
|
this.settings.fallMaterials
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public totalEquipmentCost(): number {
|
||||||
|
return (
|
||||||
|
this.settings.seedEquipment +
|
||||||
|
this.settings.preSeedEquipment +
|
||||||
|
this.settings.postSeedEquipment +
|
||||||
|
this.settings.harvestEquipment +
|
||||||
|
this.settings.fallEquipment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public field(): string {
|
||||||
|
return this.settings.field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createDate(): number {
|
||||||
|
return this.settings.createDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public harvestYear(): number {
|
||||||
|
return this.settings.harvestYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public cropType(): pond.Grain {
|
||||||
|
return this.settings.cropType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public customCropType(): string {
|
||||||
|
return this.settings.customGrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public grainType(): string {
|
||||||
|
return this.settings.grainType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public yieldTarget(): number {
|
||||||
|
return this.settings.yieldTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public actualYield(): number {
|
||||||
|
return this.settings.actualYield;
|
||||||
|
}
|
||||||
|
|
||||||
|
//NOTE all cost are set as per acre
|
||||||
|
public seedMaterialsCost(): number {
|
||||||
|
return this.settings.seedMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public seedEquipmentCost(): number {
|
||||||
|
return this.settings.seedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public seedPerAcre(): number {
|
||||||
|
return this.settings.seedMaterials + this.settings.seedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public preSeedMaterialsCost(): number {
|
||||||
|
return this.settings.preSeedMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public preSeedEquipmentCost(): number {
|
||||||
|
return this.settings.preSeedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public preSeedPerAcre(): number {
|
||||||
|
return this.settings.preSeedMaterials + this.settings.preSeedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public postSeedMaterialCost(): number {
|
||||||
|
return this.settings.postSeedMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public postSeedEquipmentCost(): number {
|
||||||
|
return this.settings.postSeedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public postSeedPerAcre(): number {
|
||||||
|
return this.settings.postSeedMaterials + this.settings.postSeedEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public harvestMaterialCost(): number {
|
||||||
|
return this.settings.harvestMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public harvestEquipmentCost(): number {
|
||||||
|
return this.settings.harvestEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public harvestPerAcre(): number {
|
||||||
|
return this.settings.harvestMaterials + this.settings.harvestEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fallMaterialCost(): number {
|
||||||
|
return this.settings.fallMaterials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fallEquipmentCost(): number {
|
||||||
|
return this.settings.fallEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fallPerAcre(): number {
|
||||||
|
return this.settings.fallMaterials + this.settings.fallEquipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bushelPrice(): number {
|
||||||
|
return this.settings.bushelPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public taskIds(): string[] {
|
||||||
|
return this.settings.taskIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ export * from "./user";
|
||||||
export * from "./Field";
|
export * from "./Field";
|
||||||
// export * from "./Team";
|
// export * from "./Team";
|
||||||
export * from "./team";
|
export * from "./team";
|
||||||
// export * from "./HarvestPlan";
|
export * from "./HarvestPlan";
|
||||||
// export * from "./HarvestYear";
|
// export * from "./HarvestYear";
|
||||||
export * from "./Note";
|
export * from "./Note";
|
||||||
// export * from "./FieldMapping";
|
// export * from "./FieldMapping";
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { getWhitelabel } from "services/whiteLabel";
|
import { getWhitelabel } from "services/whiteLabel";
|
||||||
import Ventilation from "pages/VentEditor";
|
import Ventilation from "pages/VentEditor";
|
||||||
import FieldMap from "pages/FieldMap";
|
import FieldMap from "pages/FieldMap";
|
||||||
|
import GrainBag from "pages/grainBag";
|
||||||
|
|
||||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||||
const DevicePage = lazy(() => import("pages/Device"));
|
const DevicePage = lazy(() => import("pages/Device"));
|
||||||
|
|
@ -222,6 +223,7 @@ export default function Router(props: Props) {
|
||||||
<Route path="users" element={<Users/>} />
|
<Route path="users" element={<Users/>} />
|
||||||
<Route path="visualFarm" element={<FieldMap />} />
|
<Route path="visualFarm" element={<FieldMap />} />
|
||||||
<Route path="/logout" element={<Logout />} />
|
<Route path="/logout" element={<Logout />} />
|
||||||
|
<Route path="grainbags/:bagID" element={<GrainBag />} />
|
||||||
{/*
|
{/*
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<Route path="*" element={<RelativeRoutes/>} />
|
<Route path="*" element={<RelativeRoutes/>} />
|
||||||
|
|
|
||||||
133
src/pages/grainBag.tsx
Normal file
133
src/pages/grainBag.tsx
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { Box, Card, Grid2 as Grid, Typography } from "@mui/material";
|
||||||
|
import ObjectControls from "common/ObjectControls";
|
||||||
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||||
|
import TimeBar from "common/time/TimeBar";
|
||||||
|
import GrainBagActions from "grainBag/grainBagActions";
|
||||||
|
import GrainBagInventoryGraph from "grainBag/grainBagInventoryGraph";
|
||||||
|
import GrainBagVisualizer from "grainBag/grainBagVisualizer";
|
||||||
|
import { Scope } from "models";
|
||||||
|
import { GrainBag as IGrainBag } from "models/GrainBag";
|
||||||
|
import { Moment } from "moment";
|
||||||
|
//import { MatchParams } from "navigation/Routes";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useGrainBagAPI, useSnackbar, useUserAPI } from "providers";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
//import { useRouteMatch } from "react-router";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
bagKey?: string;
|
||||||
|
mobileView?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GrainBag(props: Props) {
|
||||||
|
const { bagKey, mobileView } = props;
|
||||||
|
//const match = useRouteMatch<MatchParams>();
|
||||||
|
//const bagID = bagKey ?? match.params.bagID;
|
||||||
|
const bagID = bagKey ?? useParams<{ bagID: string }>()?.bagID ?? "";
|
||||||
|
|
||||||
|
const grainBagAPI = useGrainBagAPI();
|
||||||
|
const [{ user, as }] = useGlobalState();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
|
const [grainBag, setGrainBag] = useState<IGrainBag>(IGrainBag.create());
|
||||||
|
const defaultDateRange = GetDefaultDateRange();
|
||||||
|
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
||||||
|
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
grainBagAPI
|
||||||
|
.getGrainBag(bagID)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.data.grainBag) {
|
||||||
|
setGrainBag(IGrainBag.create(resp.data.grainBag));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("There was a problem loading the Grain Bag");
|
||||||
|
});
|
||||||
|
}, [bagID, grainBagAPI, openSnack]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let key = bagID;
|
||||||
|
let kind = "grainbag";
|
||||||
|
if (as) {
|
||||||
|
key = as;
|
||||||
|
kind = "team";
|
||||||
|
}
|
||||||
|
console.log
|
||||||
|
userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
}, [as, bagID, userAPI, user]);
|
||||||
|
|
||||||
|
const updateDateRange = (newStartDate: any, newEndDate: any) => {
|
||||||
|
let range = GetDefaultDateRange();
|
||||||
|
range.start = newStartDate;
|
||||||
|
range.end = newEndDate;
|
||||||
|
setStartDate(newStartDate);
|
||||||
|
setEndDate(newEndDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<ObjectControls
|
||||||
|
actions={
|
||||||
|
<GrainBagActions
|
||||||
|
grainBag={grainBag}
|
||||||
|
refreshCallback={updatedBag => {
|
||||||
|
if (updatedBag) {
|
||||||
|
setGrainBag(updatedBag);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
permissions={permissions}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Card style={{ padding: 15, margin: 15 }}>
|
||||||
|
<Grid container width={"100%"} direction="row" spacing={3} style={{ padding: 5 }}>
|
||||||
|
<Grid size={{
|
||||||
|
xs: 12,
|
||||||
|
sm: 12,
|
||||||
|
md: mobileView ? 12 : 6,
|
||||||
|
lg: mobileView ? 12 : 4
|
||||||
|
}}>
|
||||||
|
<Box
|
||||||
|
paddingLeft={1}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="left"
|
||||||
|
marginBottom={2}>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 25 }}>{grainBag.name()}</Typography>
|
||||||
|
</Box>
|
||||||
|
<GrainBagVisualizer grainBag={grainBag} />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{
|
||||||
|
xs: 12,
|
||||||
|
sm: 12,
|
||||||
|
md: 12,
|
||||||
|
lg: mobileView ? 12 : 8
|
||||||
|
}}>
|
||||||
|
<Box
|
||||||
|
paddingLeft={1}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="left"
|
||||||
|
marginBottom={2}>
|
||||||
|
<TimeBar startDate={startDate} endDate={endDate} updateDateRange={updateDateRange} />
|
||||||
|
</Box>
|
||||||
|
<GrainBagInventoryGraph
|
||||||
|
grainBag={grainBag}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
customHeight={500}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,13 @@ import React from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type?: "light" | "dark";
|
type?: "light" | "dark";
|
||||||
|
height?: number | string
|
||||||
|
width?: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HomeIcon(props: Props) {
|
export default function HomeIcon(props: Props) {
|
||||||
const themeType = useThemeType();
|
const themeType = useThemeType();
|
||||||
const { type } = props;
|
const { type, height, width } = props;
|
||||||
|
|
||||||
const src = () => {
|
const src = () => {
|
||||||
if (type) {
|
if (type) {
|
||||||
|
|
@ -20,5 +22,5 @@ export default function HomeIcon(props: Props) {
|
||||||
return themeType === "light" ? HomeIconDark : HomeIconLight;
|
return themeType === "light" ? HomeIconDark : HomeIconLight;
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ImgIcon alt="fields" src={src()} />;
|
return <ImgIcon alt="fields" src={src()} iconHeight={height} iconWidth={width} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export {
|
||||||
useFieldMarkerAPI,
|
useFieldMarkerAPI,
|
||||||
useFirmwareAPI,
|
useFirmwareAPI,
|
||||||
useGroupAPI,
|
useGroupAPI,
|
||||||
// useHarvestPlanAPI,
|
useHarvestPlanAPI,
|
||||||
// useHarvestYearAPI,
|
// useHarvestYearAPI,
|
||||||
useHomeMarkerAPI,
|
useHomeMarkerAPI,
|
||||||
useInteractionsAPI,
|
useInteractionsAPI,
|
||||||
|
|
|
||||||
|
|
@ -127,19 +127,20 @@ export default function GrainBagProvider(props: PropsWithChildren<Props>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const listHistory = (id: string, limit: number, offset: number, start?: string, end?: string) => {
|
const listHistory = (id: string, limit: number, offset: number, start?: string, end?: string) => {
|
||||||
return get<pond.ListGrainBagHistoryResponse>(
|
let url = pondURL(
|
||||||
pondURL(
|
"/grainbags/" +
|
||||||
"/grainbags/" +
|
id +
|
||||||
id +
|
"/history?limit=" +
|
||||||
"/history?limit=" +
|
limit +
|
||||||
limit +
|
"&offset=" +
|
||||||
"&offset=" +
|
offset +
|
||||||
offset +
|
(start && "&start=" + start) +
|
||||||
(start && "&start=" + start) +
|
(end && "&end=" + end)
|
||||||
(end && "&end=" + end) +
|
)
|
||||||
(as && "&as=" + as)
|
if (as) {
|
||||||
)
|
url = url + "?as=" + as
|
||||||
);
|
}
|
||||||
|
return get<pond.ListGrainBagHistoryResponse>(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
130
src/providers/pond/harvestPlanAPI.tsx
Normal file
130
src/providers/pond/harvestPlanAPI.tsx
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState } from "providers/StateContainer";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { or } from "utils";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
|
||||||
|
export interface IHarvestPlanAPIContext {
|
||||||
|
addHarvestPlan: (harvestPlan: pond.HarvestPlanSettings) => Promise<any>;
|
||||||
|
getHarvestPlan: (harvestPlanId: string) => Promise<any>;
|
||||||
|
listHarvestPlans: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => Promise<AxiosResponse<pond.ListHarvestPlansResponse>>;
|
||||||
|
listHistory: (
|
||||||
|
id: string,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
) => Promise<AxiosResponse<pond.ListHarvestPlanHistoryResponse>>;
|
||||||
|
removeHarvestPlan: (
|
||||||
|
harvestPlanId: string
|
||||||
|
) => Promise<AxiosResponse<pond.RemoveHarvestPlanResponse>>;
|
||||||
|
updateHarvestPlan: (
|
||||||
|
key: string,
|
||||||
|
harvestPlan: pond.HarvestPlanSettings,
|
||||||
|
asRoot?: true
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateHarvestPlanResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HarvestPlanAPIContext = createContext<IHarvestPlanAPIContext>(
|
||||||
|
{} as IHarvestPlanAPIContext
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function HarvestPlanProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, del, post, put } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
const addHarvestPlan = (harvestPlan: pond.HarvestPlanSettings) => {
|
||||||
|
if (as) return post(pondURL("/harvestPlans?as=" + as), harvestPlan);
|
||||||
|
return post(pondURL("/harvestPlans"), harvestPlan);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHarvestPlan = (harvestPlanId: string) => {
|
||||||
|
if (as) return get(pondURL("/harvestPlans/" + harvestPlanId + "?as=" + as));
|
||||||
|
return get(pondURL("/harvestPlans/" + harvestPlanId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeHarvestPlan = (key: string) => {
|
||||||
|
if (as)
|
||||||
|
return del<pond.RemoveHarvestPlanResponse>(pondURL("/harvestPlans/" + key + "?as=" + as));
|
||||||
|
return del<pond.RemoveHarvestPlanResponse>(pondURL("/harvestPlans/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listHarvestPlans = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
as?: string,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => {
|
||||||
|
return get<pond.ListHarvestPlansResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/harvestPlans" +
|
||||||
|
"?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + or(order, "asc")) +
|
||||||
|
("&by=" + or(orderBy, "key")) +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(as ? "&as=" + as : "")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listHistory = (id: string, limit: number, offset: number) => {
|
||||||
|
return get<pond.ListHarvestPlanHistoryResponse>(
|
||||||
|
pondURL("/harvestPlans/" + id + "/history?limit=" + limit + "&offset=" + offset)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateHarvestPlan = (
|
||||||
|
key: string,
|
||||||
|
harvestPlan: pond.HarvestPlanSettings,
|
||||||
|
asRoot?: boolean
|
||||||
|
) => {
|
||||||
|
if (as)
|
||||||
|
return put<pond.UpdateHarvestPlanResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/harvestPlans/" +
|
||||||
|
key +
|
||||||
|
(as ? "?as=" + as : "") +
|
||||||
|
(asRoot ? "&asRoot=" + asRoot.toString() : "")
|
||||||
|
),
|
||||||
|
harvestPlan
|
||||||
|
);
|
||||||
|
return put<pond.UpdateHarvestPlanResponse>(
|
||||||
|
pondURL("/harvestPlans/" + key + (asRoot ? "?asRoot=" + asRoot.toString() : "")),
|
||||||
|
harvestPlan
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HarvestPlanAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
addHarvestPlan,
|
||||||
|
getHarvestPlan,
|
||||||
|
removeHarvestPlan,
|
||||||
|
listHarvestPlans,
|
||||||
|
listHistory,
|
||||||
|
updateHarvestPlan
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</HarvestPlanAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHarvestPlanAPI = () => useContext(HarvestPlanAPIContext);
|
||||||
|
|
@ -23,6 +23,7 @@ import FileControllerProvider, { useFileControllerAPI } from "./fileControllerAP
|
||||||
import MineProvider, { useMineAPI } from "./mineAPI";
|
import MineProvider, { useMineAPI } from "./mineAPI";
|
||||||
import FieldMarkerProvider, {useFieldMarkerAPI} from "./fieldMarkerAPI";
|
import FieldMarkerProvider, {useFieldMarkerAPI} from "./fieldMarkerAPI";
|
||||||
import HomeMarkerProvider, {useHomeMarkerAPI} from "./homeMarkerAPI";
|
import HomeMarkerProvider, {useHomeMarkerAPI} from "./homeMarkerAPI";
|
||||||
|
import HarvestPlanProvider, {useHarvestPlanAPI} from "./harvestPlanAPI";
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
|
|
@ -62,7 +63,9 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<MineProvider>
|
<MineProvider>
|
||||||
<FieldMarkerProvider>
|
<FieldMarkerProvider>
|
||||||
<HomeMarkerProvider>
|
<HomeMarkerProvider>
|
||||||
{children}
|
<HarvestPlanProvider>
|
||||||
|
{children}
|
||||||
|
</HarvestPlanProvider>
|
||||||
</HomeMarkerProvider>
|
</HomeMarkerProvider>
|
||||||
</FieldMarkerProvider>
|
</FieldMarkerProvider>
|
||||||
</MineProvider>
|
</MineProvider>
|
||||||
|
|
@ -111,5 +114,6 @@ export {
|
||||||
useFileControllerAPI,
|
useFileControllerAPI,
|
||||||
useMineAPI,
|
useMineAPI,
|
||||||
useFieldMarkerAPI,
|
useFieldMarkerAPI,
|
||||||
useHomeMarkerAPI
|
useHomeMarkerAPI,
|
||||||
|
useHarvestPlanAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
const openWeather = new OpenWeatherMap({
|
const openWeather = new OpenWeatherMap({
|
||||||
//apiKey: process.env.REACT_APP_OPEN_WEATHERMAP ? process.env.REACT_APP_OPEN_WEATHERMAP : ""
|
//apiKey: process.env.REACT_APP_OPEN_WEATHERMAP ? process.env.REACT_APP_OPEN_WEATHERMAP : ""
|
||||||
apiKey: import.meta.env.REACT_APP_OPEN_WEATHERMAP ? import.meta.env.REACT_APP_OPEN_WEATHERMAP : ""
|
apiKey: import.meta.env.VITE_OPEN_WEATHERMAP ? import.meta.env.VITE_OPEN_WEATHERMAP : ""
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue