frontend/src/grainBag/grainBagSettings.tsx

468 lines
16 KiB
TypeScript

import {
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
InputAdornment,
Switch,
TextField,
Theme,
useTheme
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
import GrainTransaction from "grain/GrainTransaction";
import { clone } from "lodash";
import { GrainBag } from "models/GrainBag";
import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
const useStyles = makeStyles((theme: Theme) => {
return ({
bottomSpacing: {
marginBottom: theme.spacing(1)
}
})
});
interface Props {
open: boolean;
close: (updatedBag?: GrainBag) => void;
coordinates?: { start: pond.Coordinates; end: pond.Coordinates };
grainBag?: GrainBag;
}
export default function GrainBagSettings(props: Props) {
const { open, close, grainBag, coordinates } = props;
const classes = useStyles();
const grainBagAPI = useGrainBagAPI();
const [bagName, setBagName] = useState("");
const [bagDiameterM, setBagDiameterM] = useState(0);
const [bagLengthM, setBagLengthM] = useState(0);
const [bagDiameterDisplay, setBagDiameterDisplay] = useState(0);
const [bagLengthDispla, setBagLengthDisplay] = useState(0);
const [{ user, as }] = useGlobalState();
const [isCustom, setIsCustom] = useState<boolean>(false);
const [customType, setCustomType] = useState("");
const [supportedGrainType, setSupportedGrainType] = useState(pond.Grain.GRAIN_INVALID);
const [selectedGrainOp, setSelectedGrainOp] = useState<Option | null>(null);
const grainOptions = GrainOptions();
const [grainSubtype, setGrainSubtype] = useState("");
const [grainCapacity, setGrainCapacity] = useState(0);
const [grainFill, setGrainFill] = useState("");
const [grainBushels, setGrainBushels] = useState(0);
const [fillDate, setFillDate] = useState(moment().format("YYYY-MM-DD"));
const [initialMoisture, setInitialMoisture] = useState(0);
// const history = useHistory();
const navigate = useNavigate()
const { openSnack } = useSnackbar();
const theme = useTheme();
const [grainUpdate, setGrainUpdate] = useState(false);
const [grainDiff, setGrainDiff] = useState(0);
const [bushelConversion, setBushelConversion] = useState(0);
useEffect(() => {
if (grainBag) {
//if a grainbag is passed in set all the state values
setBagDiameterM(grainBag.settings.diameter);
setBagDiameterDisplay(
user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET
? grainBag.settings.diameter * 3.281
: grainBag.settings.diameter
);
setBagLengthM(grainBag.settings.length);
setBagLengthDisplay(
user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET
? grainBag.settings.length * 3.281
: grainBag.settings.length
);
let grainVal = grainBag.bushels();
if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
grainVal = grainBag.bushels() / grainBag.bushelsPerTonne();
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
}
setGrainFill(grainVal.toFixed(2));
setBagName(grainBag.name());
setIsCustom(grainBag.isCustom());
setSupportedGrainType(grainBag.settings.supportedGrain);
setSelectedGrainOp(ToGrainOption(grainBag.settings.supportedGrain));
setCustomType(grainBag.settings.customGrain);
setGrainSubtype(grainBag.settings.grainSubtype);
setGrainCapacity(grainBag.capacity());
setGrainBushels(grainBag.bushels());
setFillDate(grainBag.settings.fillDate);
setInitialMoisture(grainBag.settings.initialMoisture);
}
}, [grainBag, user]);
const removeGrainBag = () => {
if (!grainBag) return;
grainBagAPI
.removeGrainBag(grainBag.key(), as)
.then(resp => {
openSnack("Removed Grain Bag");
close();
navigate("/bins");
})
.catch(err => {
openSnack("Failed to remove Grain Bag");
});
};
const submit = () => {
//if a bag was passed in do an update
let tonneConversion = bushelConversion
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
tonneConversion = bushelConversion / 0.907
}
if (grainBag) {
grainBag.title = bagName;
grainBag.settings.initialMoisture = initialMoisture;
grainBag.settings.diameter = bagDiameterM;
grainBag.settings.length = bagLengthM;
grainBag.settings.customGrain = customType;
grainBag.settings.supportedGrain = supportedGrainType;
grainBag.settings.bushelCapacity = grainCapacity;
grainBag.settings.grainSubtype = grainSubtype;
grainBag.settings.fillDate = fillDate;
grainBag.settings.bushelsPerTonne = tonneConversion;
grainBag.settings.storageType = isCustom
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
if (grainBag.bushels() !== grainBushels) {
//grainBag.settings.currentBushels = grainBushels;
setGrainDiff(grainBushels - grainBag.bushels());
setGrainUpdate(true);
} else {
grainBagAPI
.updateGrainBag(grainBag.key(), bagName, grainBag.settings, as)
.then(resp => {
openSnack("Grain Bag Updated");
let updatedBag = clone(grainBag);
close(updatedBag);
})
.catch(err => {
openSnack("Failed to update Grain Bag");
close();
});
}
} else {
//otherwise make a new one
let settings = pond.GrainBagSettings.create();
settings.initialMoisture = initialMoisture;
settings.diameter = bagDiameterM;
settings.length = bagLengthM;
settings.customGrain = customType;
settings.supportedGrain = supportedGrainType;
settings.bushelCapacity = grainCapacity;
settings.currentBushels = grainBushels;
settings.grainSubtype = grainSubtype;
settings.fillDate = fillDate;
settings.bushelsPerTonne = tonneConversion;
settings.storageType = isCustom
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
if (coordinates) {
settings.startLocation = coordinates.start;
settings.endLocation = coordinates.end;
}
grainBagAPI
.addGrainBag(bagName, settings, as)
.then(resp => {
openSnack("New grain bag added");
let newBag = GrainBag.create(
pond.GrainBag.fromObject({
title: bagName,
key: resp.data.key,
settings: settings
})
);
close(newBag);
})
.catch(() => {
openSnack("Failed to add new grain bag");
});
}
};
const grainBox = () => {
if (isCustom) {
return (
<React.Fragment>
<TextField
label="Grain"
value={customType}
type="text"
onChange={event => {
setCustomType(event.target.value);
}}
fullWidth
className={classes.bottomSpacing}
variant="outlined"
/>
<TextField
label="Bushels Per Tonne"
value={bushelConversion}
type="number"
onChange={event => {
setBushelConversion(+event.target.value);
}}
fullWidth
className={classes.bottomSpacing}
variant="outlined"
/>
</React.Fragment>
);
} else {
return (
<SearchSelect
style={{ marginBottom: theme.spacing(1) }}
label="Type"
selected={selectedGrainOp}
changeSelection={option => {
let newGrainType = option
? pond.Grain[option.value as keyof typeof pond.Grain]
: pond.Grain.GRAIN_INVALID;
setSupportedGrainType(newGrainType);
setBushelConversion(GrainDescriber(newGrainType).bushelsPerTonne);
setSelectedGrainOp(option);
}}
group
//disabled={!canEdit}
options={grainOptions}
/>
);
}
};
const formInvalid = () => {
let invalid = true;
if (bagName !== "" && grainCapacity > 0) {
invalid = false;
}
return invalid;
};
const grainUnitDisplay = () => {
if(bushelConversion > 1){
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
return "mT"
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
return "t"
}
}else{
return "bu"
}
}
return (
<React.Fragment>
<GrainTransaction
open={grainUpdate}
mainObject={grainBag ?? GrainBag.create()}
grainAdjustment={grainDiff}
close={() => {
setGrainUpdate(false);
setGrainDiff(0);
//setPendingGrainAmount(undefined);
//resetFillPercentage();
//setGrainDiff(0);
}}
callback={() => {
let updatedBag = clone(grainBag);
close(updatedBag);
}}
/>
<ResponsiveDialog
open={open}
onClose={() => {
close();
}}>
<DialogTitle>Grain Bag Settings</DialogTitle>
<DialogContent>
<TextField
label="Grain Bag Name"
fullWidth
variant="outlined"
value={bagName}
onChange={e => setBagName(e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Bag Diameter"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "m"}
</InputAdornment>
)
}}
fullWidth
variant="outlined"
type="number"
value={bagDiameterDisplay}
onChange={e => {
setBagDiameterDisplay(+e.target.value);
let inMeters = +e.target.value;
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
//if the users preferred unit is in feet that means the value that will be saved needs to be converted to meters
inMeters = inMeters / 3.281;
}
setBagDiameterM(inMeters);
}}
className={classes.bottomSpacing}
/>
<TextField
label="Bag Length"
fullWidth
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "m"}
</InputAdornment>
)
}}
type="number"
value={bagLengthDispla}
onChange={e => {
setBagLengthDisplay(+e.target.value);
let inMeters = +e.target.value;
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
//if the users preferred unit is in feet that means the value that will be saved needs to be converted to meters
inMeters = inMeters / 3.281;
}
setBagLengthM(inMeters);
}}
className={classes.bottomSpacing}
/>
<TextField
fullWidth
type="date"
label="Fill Date"
value={fillDate}
InputLabelProps={{ shrink: true }}
onChange={e => setFillDate(e.target.value)}
/>
<Grid container alignItems="center">
<Grid>Grain</Grid>
<Grid>
<Switch
color="default"
value={isCustom}
checked={isCustom}
onChange={(_, checked) => {
setIsCustom(checked);
if (checked) {
setSupportedGrainType(pond.Grain.GRAIN_CUSTOM);
} else {
setCustomType("");
}
}}
name="storage"
/>
</Grid>
<Grid>Custom</Grid>
</Grid>
{grainBox()}
<TextField
label="Grain Subtype"
fullWidth
variant="outlined"
value={grainSubtype}
onChange={e => setGrainSubtype(e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Initial Moisture"
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
value={initialMoisture}
onChange={e => setInitialMoisture(+e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Capacity"
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="end">bu</InputAdornment>
}}
value={grainCapacity}
onChange={e => setGrainCapacity(+e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label={"Current Inventory"}
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{grainUnitDisplay()}
</InputAdornment>
)
}}
value={grainFill}
onChange={e => {
let bushelVal = +e.target.value;
if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
//convert the number as a weight into the bushel value, no changing or conversions necessary
// since these are both user entered fields and should be the same unit (ton or tonne)
bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1);
}
if (bushelVal > grainCapacity) {
bushelVal = grainCapacity;
}
setGrainBushels(bushelVal);
setGrainFill(e.target.value);
}}
className={classes.bottomSpacing}
/>
</DialogContent>
<DialogActions>
<Grid container direction="row" justifyContent="space-between">
<Grid >
{grainBag && (
<Button
onClick={() => {
removeGrainBag();
}}
variant="contained"
style={{ background: "red" }}>
Delete
</Button>
)}
</Grid>
<Grid >
<Button
onClick={() => {
close();
}}>
Cancel
</Button>
<Button onClick={submit} disabled={formInvalid()} color="primary" variant="contained">
Confirm
</Button>
</Grid>
</Grid>
</DialogActions>
</ResponsiveDialog>
</React.Fragment>
);
}