frontend/src/grain/GrainInventory.tsx

385 lines
12 KiB
TypeScript

import ResponsiveDialog from "common/ResponsiveDialog";
import { Bin, Field, HarvestYear } from "models";
import React, { useCallback } from "react";
import { useFieldAPI, useBinAPI, useHarvestYearAPI, useGlobalState } from "providers";
import {
Box,
Button,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
FormControlLabel,
MenuItem,
Radio,
RadioGroup,
TextField
} from "@mui/material";
import { useState } from "react";
import { useEffect } from "react";
import { pond } from "protobuf-ts/pond";
import { useSnackbar } from "hooks";
import moment from "moment";
import GrainDescriber from "./GrainDescriber";
interface Props {
activeBinSettings: pond.BinSettings;
newGrainInventory: number;
dialogControl: boolean;
onClose(cancel?: boolean): void;
afterUpdate?(secondBin?: Bin): void;
}
//this component is going to be deprecated by grain transaction
export default function GrainInventory(props: Props) {
const { activeBinSettings, newGrainInventory, dialogControl, onClose, afterUpdate } = props;
const binAPI = useBinAPI();
const fieldAPI = useFieldAPI();
const hYearAPI = useHarvestYearAPI();
const [{ as }] = useGlobalState();
const [binIndex, setBinIndex] = useState(0);
const [fieldIndex, setFieldIndex] = useState<number>(-1);
const [yearIndex, setYearIndex] = useState(0);
const [bins, setBins] = useState<Bin[]>([]);
const [fields, setFields] = useState<Field[]>([]);
const [hYears, setHYears] = useState<Map<number, HarvestYear>>(new Map<number, HarvestYear>());
const [currentBushels, setCurrentBushels] = useState(0);
const { openSnack } = useSnackbar();
const [increaseMode, setIncreaseMode] = useState<"correction" | "purchased" | "grown">(
"correction"
);
const [decreaseMode, setDecreaseMode] = useState<"correction" | "sold" | "moved">("correction");
useEffect(() => {
if (activeBinSettings.inventory && dialogControl) {
setCurrentBushels(activeBinSettings.inventory.grainBushels);
}
}, [activeBinSettings, dialogControl]);
const loadBins = useCallback(() => {
if (!dialogControl) return;
binAPI
.listBins(50, 0, "asc", "name", undefined, as)
.then(resp => {
setBins(resp.data.bins.map(b => Bin.any(b)));
})
.catch();
}, [binAPI, as, dialogControl]);
const loadHYears = useCallback(() => {
if (fields.length > 0 && fieldIndex > -1) {
hYearAPI.listHarvestYears(50, 0, "asc", "year", fields[fieldIndex].fieldName()).then(resp => {
let tempMap = new Map<number, HarvestYear>();
resp.data.harvestYear.forEach(hy => {
let tempHY = HarvestYear.any(hy);
tempMap.set(tempHY.year(), tempHY);
});
setHYears(tempMap);
});
}
}, [hYearAPI, fields, fieldIndex]);
useEffect(() => {
loadHYears();
}, [loadHYears]);
useEffect(() => {
loadBins();
fieldAPI
.listFields(500, 0, "asc", "fieldName", undefined, as)
.then(resp => {
setFields(resp.data.fields.map(f => Field.any(f)));
})
.catch();
}, [loadBins, fieldAPI, as]);
const grainName = (bin: Bin) => {
return bin.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
? GrainDescriber(bin.grain()).name
: bin.customType();
};
const dialogContent = () => {
if (newGrainInventory < currentBushels) {
return (
<Box>
<FormControl>
<RadioGroup value={decreaseMode} name="radio-buttons-group">
<FormControlLabel
value="correction"
control={<Radio />}
label="Correction"
onChange={() => setDecreaseMode("correction")}
/>
{/* Doesnt Actually Do Anything yet */}
{/* <FormControlLabel
value="sold"
control={<Radio />}
label="Sold"
onChange={() => setDecreaseMode("sold")}
/> */}
<FormControlLabel
value="moved"
control={<Radio />}
label="Moved"
onChange={() => setDecreaseMode("moved")}
/>
</RadioGroup>
</FormControl>
{decreaseMode === "moved" && (
<TextField
id="bins"
fullWidth
margin="normal"
select
label="Bin grain moved to"
value={binIndex}
onChange={e => setBinIndex(+e.target.value)}>
{bins.map((option, i) => (
<MenuItem key={i} value={i} disabled={option.key() === activeBinSettings.key}>
{option.name()} - {grainName(option)}
</MenuItem>
))}
</TextField>
)}
</Box>
);
} else {
return (
<Box>
<FormControl>
<RadioGroup value={increaseMode} name="radio-buttons-group">
<FormControlLabel
value="correction"
control={<Radio />}
label="Correction"
onChange={() => setIncreaseMode("correction")}
/>
{/* Doesnt Actually Do Anything yet */}
{/* <FormControlLabel
value="purchased"
control={<Radio />}
label="Purchased"
onChange={() => setIncreaseMode("purchased")}
/> */}
<FormControlLabel
value="grown"
control={<Radio />}
label="Grown"
onChange={() => setIncreaseMode("grown")}
/>
</RadioGroup>
</FormControl>
{increaseMode === "grown" && (
<Box>
<TextField
id="fields"
fullWidth
margin="normal"
select
label="Field"
helperText="Select the field that provided the grain"
value={fieldIndex}
onChange={e => setFieldIndex(+e.target.value)}>
<MenuItem key="noField" value={-1}>
No Field Selected
</MenuItem>
{fields.map((option, i) => (
<MenuItem key={i} value={i}>
{option.fieldName()}
</MenuItem>
))}
</TextField>
<TextField
id="harvestYear"
fullWidth
margin="normal"
select
label="Harvest Year"
helperText="Select which Year to track"
value={yearIndex}
onChange={e => setYearIndex(+e.target.value)}
disabled={fieldIndex < 0}>
<MenuItem key={0} value={0}>
New Harvest Year
</MenuItem>
{Array.from(hYears.values()).length > 0 &&
Array.from(hYears.values()).map(option => (
<MenuItem key={option.year()} value={option.year()}>
{option.year()}
</MenuItem>
))}
</TextField>
</Box>
)}
</Box>
);
}
};
const increaseInv = () => {
let bin = activeBinSettings;
let binInv = pond.BinInventory.fromObject({
...bin.inventory
});
if (binInv.grainBushels === 0 && bin.mode === pond.BinMode.BIN_MODE_NONE) {
bin.mode = pond.BinMode.BIN_MODE_STORAGE;
}
binInv.grainBushels = newGrainInventory;
if (fields.length > 0 && fieldIndex > -1) {
if (fields[fieldIndex].crop() !== activeBinSettings.inventory?.grainType) {
openSnack("Grain type of this bin does not match the Field");
return;
} else if (yearIndex === 0 && hYears.has(moment().year())) {
openSnack("Current year already exists for this field");
return;
}
binInv.grainSubtype = fields[fieldIndex].subtype();
}
bin.inventory = binInv;
binAPI
.updateBin(activeBinSettings.key, bin)
.then(() => {
if (increaseMode === "grown") {
if (yearIndex === 0) {
let hy = HarvestYear.create();
hy.settings.field = fields[fieldIndex].fieldName();
hy.settings.grain = fields[fieldIndex].crop();
hy.settings.year = moment().year();
hy.settings.bushels = newGrainInventory - currentBushels;
hYearAPI.addHarvestYear(hy.settings).then(resp => {
loadHYears();
});
} else {
let hy = hYears.get(yearIndex);
if (hy) {
hy.settings.bushels = hy.totalBushels() + (newGrainInventory - currentBushels);
hYearAPI.updateHarvestYear(hy.key(), hy.settings).then(resp => {
loadHYears();
});
}
}
} else if (increaseMode === "purchased") {
}
})
.catch(err => {
openSnack("Failed to update grain inventory");
})
.finally(() => {
if (afterUpdate) {
loadBins();
afterUpdate();
}
closeDialog();
});
};
const decreaseInv = () => {
let binFrom = activeBinSettings;
let binTo: Bin;
let binFromInv = pond.BinInventory.fromObject({
...binFrom.inventory
});
if (decreaseMode === "moved") {
//TODO-CS: re-think the logic for moving between bins since the addition of custom types,
//possibly remove restrictions and just have a confirmation for the user to change the destination bins grain type
binTo = bins[binIndex];
if (binTo.settings.inventory && binTo.settings.specs) {
let binToInv = binTo.settings.inventory;
//check to make sure the grain types of both bins are the same
if (binFromInv.grainType !== binToInv.grainType) {
if (
binToInv.grainType === pond.Grain.GRAIN_NONE ||
binToInv.grainType === pond.Grain.GRAIN_INVALID ||
binToInv.grainBushels < 1
) {
binTo.settings.inventory.grainType = binFromInv.grainType;
} else {
openSnack("Grain types of both bins must match");
return;
}
}
//check if the other bin has enough space to move the grain
let space = binTo.settings.specs.bushelCapacity - binTo.settings.inventory.grainBushels;
if (space < currentBushels - newGrainInventory) {
openSnack("Bin does not have enough space");
return;
}
binToInv.grainBushels = binToInv.grainBushels + (currentBushels - newGrainInventory);
}
}
binFromInv.grainBushels = newGrainInventory;
binFrom.inventory = binFromInv;
binAPI
.updateBin(activeBinSettings.key, binFrom)
.then(() => {
//update inventory of the bin that the grain was moved to if it was a decrease
if (decreaseMode === "moved") {
binAPI
.updateBin(bins[binIndex].key(), binTo.settings)
.then(() => {
openSnack("Grain inventory updated");
})
.catch(err => {
openSnack("Failed to update grain inventory");
})
.finally(() => {
if (afterUpdate) {
afterUpdate(binTo);
}
});
}
})
.catch(err => {
openSnack("Failed to update grain inventory");
})
.finally(() => {
if (decreaseMode !== "moved" && afterUpdate) {
afterUpdate();
}
closeDialog();
});
};
const setNewInventory = () => {
if (currentBushels > newGrainInventory) {
decreaseInv();
} else {
increaseInv();
}
};
const closeDialog = (useCallback?: boolean) => {
onClose(useCallback);
};
const disableButton = () => {
if (increaseMode === "grown") {
if (fields.length === 0 || fieldIndex < 0) {
return true;
}
}
return false;
};
return (
<ResponsiveDialog open={dialogControl} onClose={() => closeDialog(true)}>
<DialogTitle>Grain Adjustment for {activeBinSettings.name}</DialogTitle>
<DialogContent>{dialogContent()}</DialogContent>
<DialogActions>
<Button onClick={() => closeDialog(true)} color="primary">
Cancel
</Button>
<Button onClick={setNewInventory} color="primary" disabled={disableButton()}>
Confirm
</Button>
</DialogActions>
</ResponsiveDialog>
);
}