2684 lines
101 KiB
TypeScript
2684 lines
101 KiB
TypeScript
import {
|
|
Accordion,
|
|
AccordionDetails,
|
|
AccordionSummary,
|
|
AppBar,
|
|
Box,
|
|
Button,
|
|
Checkbox,
|
|
Collapse,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogContentText,
|
|
DialogTitle,
|
|
FormControl,
|
|
FormControlLabel,
|
|
FormHelperText,
|
|
FormLabel,
|
|
Grid2 as Grid,
|
|
IconButton,
|
|
InputAdornment,
|
|
MobileStepper,
|
|
Radio,
|
|
RadioGroup,
|
|
Step,
|
|
StepLabel,
|
|
Stepper,
|
|
Switch,
|
|
Tab,
|
|
Tabs,
|
|
TextField,
|
|
Theme,
|
|
Toolbar,
|
|
Tooltip,
|
|
Typography,
|
|
useTheme
|
|
} from "@mui/material";
|
|
import { Close, ExpandMore, Info, KeyboardArrowLeft, KeyboardArrowRight } from "@mui/icons-material";
|
|
// import { getBinModel } from "common/DataImports/BinCables/BinCableImporter";
|
|
//import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
|
import DeleteButton from "common/DeleteButton";
|
|
import OutlinedBox from "common/OutlinedBox";
|
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
|
import SearchSelect, { Option } from "common/SearchSelect";
|
|
import {
|
|
ConeVolume,
|
|
CylinderVolume,
|
|
RadiansToDegrees,
|
|
TriangleOppositeLength
|
|
} from "common/TrigFunctions";
|
|
import FanPicker from "fans/fanPicker";
|
|
import { GrainOptions, ToGrainOption } from "grain";
|
|
import GrainDescriber from "grain/GrainDescriber";
|
|
import GrainTransaction from "grain/GrainTransaction";
|
|
import { GetGrainUseOptions, ToGrainUseOption } from "grain/GrainUse";
|
|
import { useMobile, useSnackbar } from "hooks";
|
|
import { clone, cloneDeep } from "lodash";
|
|
import { Bin } from "models";
|
|
import moment from "moment";
|
|
import { GetBinShapeDescribers } from "pbHelpers/Bin";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
// import { useHistory } from "react-router";
|
|
import { or } from "utils";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { useNavigate } from "react-router-dom";
|
|
import BinSelector from "./BinSelector";
|
|
import CustomGrainSelector from "grain/CustomGrainSelector";
|
|
// import BinSelector from "./BinSelector";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => {
|
|
return ({
|
|
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"
|
|
}
|
|
})
|
|
});
|
|
|
|
type Mode = "add" | "update" | "remove";
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onClose: (refresh?: boolean) => void;
|
|
mode?: Mode;
|
|
bin?: Bin;
|
|
canEdit: boolean;
|
|
openedBinYard?: string;
|
|
userID: string;
|
|
coords?: { longitude: number; latitude: number };
|
|
binYards?: pond.BinYardSettings[];
|
|
}
|
|
|
|
interface BinStep {
|
|
label: string;
|
|
}
|
|
|
|
const getSteps = (): BinStep[] => {
|
|
return [{ label: "General" }, { label: "Fan" }, { label: "Inventory" }, { label: "Thresholds" }];
|
|
};
|
|
|
|
interface BinFormExtension {
|
|
capacity: string;
|
|
height: string;
|
|
topConeHeight: string;
|
|
sidewallHeight: string;
|
|
hopperHeight: string;
|
|
diameter: string;
|
|
grainBushels: string;
|
|
grainWeight: string;
|
|
grainType: Option | null;
|
|
grainUse: Option | null;
|
|
grainSubtype: string;
|
|
customTypeName: string;
|
|
highTemp: number;
|
|
lowTemp: number;
|
|
tempTarget: number;
|
|
}
|
|
|
|
export default function BinSettings(props: Props) {
|
|
const classes = useStyles();
|
|
//the conversion constant to convert cubic feet to bushels
|
|
const conversionConstantFT = 0.7786;
|
|
const isMobile = useMobile();
|
|
const theme = useTheme();
|
|
// const history = useHistory();
|
|
const navigate = useNavigate();
|
|
const binAPI = useBinAPI();
|
|
const binYardAPI = useBinYardAPI();
|
|
const { openSnack } = useSnackbar();
|
|
const { open, bin, onClose, mode, canEdit, openedBinYard, userID, coords } = props;
|
|
const steps = getSteps();
|
|
const [activeStep, setActiveStep] = useState(0);
|
|
const [form, setForm] = useState<pond.BinSettings>(pond.BinSettings.create());
|
|
const [formExtension, setFormExtension] = useState<BinFormExtension>({
|
|
capacity: "",
|
|
height: "",
|
|
topConeHeight: "",
|
|
sidewallHeight: "",
|
|
hopperHeight: "",
|
|
diameter: "",
|
|
grainBushels: "",
|
|
grainWeight: "",
|
|
grainType: null,
|
|
grainUse: null,
|
|
grainSubtype: "",
|
|
customTypeName: "",
|
|
highTemp: 20,
|
|
lowTemp: 10,
|
|
tempTarget: 15
|
|
});
|
|
const [initialized, setInitialized] = useState(false);
|
|
const [removeIsOpen, setRemoveIsOpen] = useState(false);
|
|
const [binYardOptions, setBinYardOptions] = useState<Option[] | null>(null);
|
|
const [selectedBinYard, setSelectedBinYard] = useState<Option | null>(null);
|
|
const grainOptions = GrainOptions();
|
|
const grainUseOptions = GetGrainUseOptions();
|
|
const [inputCapacity, setInputCapacity] = useState<string>("");
|
|
const [{ user, as }] = useGlobalState();
|
|
const [grainDiff, setGrainDiff] = useState(0);
|
|
const [isCustomInventory, setIsCustomInventory] = useState<boolean>(false);
|
|
const [grainUpdate, setGrainUpdate] = useState(false);
|
|
const [inMoistureString, setInMoistureString] = useState("0");
|
|
const [tMoistureString, setTMoistureString] = useState("0");
|
|
const [moistureTargetDeviation, setMoistureTargetDeviation] = useState("0");
|
|
const [autoFillThreshold, setAutoFillThreshold] = useState(5);
|
|
const [lidarDropDistance, setLidarDropDistance] = useState(0);
|
|
const [validInitial, setValidInitial] = useState(true);
|
|
const [validTarget, setValidTarget] = useState(true);
|
|
const [validDeviation, setValidDeviation] = useState(true);
|
|
const [highTempC, setHighTempC] = useState(20);
|
|
const [lowTempC, setLowTempC] = useState(10);
|
|
const [isCustomBin, setIsCustomBin] = useState(false);
|
|
const [binModelOps, setBinModelOptions] = useState<Option[]>([]);
|
|
const [selectedBinModel, setSelectedBinModel] = useState<Option | null>(null);
|
|
const [bushelConversion, setBushelConversion] = useState(0) //the number used to convert the bushels to either tonne or ton based on user preference
|
|
const [modelID, setModelID] = useState(0);
|
|
const [autoTopNode, setAutoTopNode] = useState(false);
|
|
const [inventoryControl, setInventoryControl] = useState<pond.BinInventoryControl>(
|
|
pond.BinInventoryControl.BIN_INVENTORY_CONTROL_UNKNOWN
|
|
);
|
|
const [storageType, setStorageType] = useState<pond.BinStorage>(
|
|
pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
|
|
);
|
|
//libracart stuff
|
|
const libracartAPI = useLibraCartProxyAPI()
|
|
const [lcDestination, setlcDestination] = useState<Option | null>()
|
|
const [lcDestinationOptions, setlcDestinationOptions] = useState<Option[]>([])
|
|
const [customGrain, setCustomGrain] = useState<pond.GrainSettings>()
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
if (bin && bin.settings.specs) {
|
|
setIsCustomBin(true);
|
|
setModelID(bin.settings.specs.modelId);
|
|
}
|
|
let initForm = pond.BinSettings.create(
|
|
bin
|
|
? bin.settings
|
|
: {
|
|
...{
|
|
specs: pond.BinSpecs.create({ shape: pond.BinShape.BIN_SHAPE_FLAT_BOTTOM }),
|
|
inventory: pond.BinInventory.create({ grainBushels: 0, targetTemperature: 15, autoThreshold: 5 }),
|
|
highTemp: 20,
|
|
lowTemp: 10
|
|
}
|
|
}
|
|
);
|
|
if (mode === "add") {
|
|
initForm.yardKey = or(openedBinYard, "");
|
|
}
|
|
|
|
if (initForm.inventory) {
|
|
setInventoryControl(initForm.inventory.inventoryControl);
|
|
//make sure bushels per ton/ne is set so bins that don't have it in the settings yet can still do it before they get updated properly
|
|
const grain = GrainDescriber(
|
|
initForm.inventory.grainType
|
|
)
|
|
|
|
if (!initForm.inventory.bushelsPerTonne) {
|
|
initForm.inventory.bushelsPerTonne = grain.bushelsPerTonne;
|
|
}
|
|
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
setBushelConversion(initForm.inventory.bushelsPerTonne)
|
|
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
setBushelConversion(initForm.inventory.bushelsPerTonne * 0.907)
|
|
}
|
|
if (initForm.inventory.customGrain){
|
|
setCustomGrain(initForm.inventory.customGrain)
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne)
|
|
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne * 0.907)
|
|
}
|
|
}
|
|
//if the target temp is not set (older bins) make it the midpoint of the high and low temps assuming they are set otherwise make it 15
|
|
if (!initForm.inventory.targetTemperature || initForm.inventory.targetTemperature) {
|
|
if (initForm.highTemp && initForm.lowTemp) {
|
|
initForm.inventory.targetTemperature = (initForm.lowTemp + initForm.highTemp) / 2;
|
|
} else {
|
|
initForm.inventory.targetTemperature = 15;
|
|
}
|
|
}
|
|
}
|
|
|
|
let h = "";
|
|
let d = "";
|
|
let topconeHeight = "";
|
|
let sidewallHeight = "";
|
|
let hopperHeight = "";
|
|
if (initForm.specs) {
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
h = (initForm.specs.heightCm * 0.0328).toFixed(2);
|
|
d = (initForm.specs.diameterCm * 0.0328).toFixed(2);
|
|
if (initForm.specs.advancedDimensions) {
|
|
topconeHeight = (initForm.specs.advancedDimensions.topConeHeight * 0.0328).toFixed(2);
|
|
sidewallHeight = (initForm.specs.advancedDimensions.sidewallHeight * 0.0328).toFixed(2);
|
|
hopperHeight = (initForm.specs.advancedDimensions.hopperHeight * 0.0328).toFixed(2);
|
|
}
|
|
} else {
|
|
h = (initForm.specs.heightCm / 100).toFixed(2);
|
|
d = (initForm.specs.diameterCm / 100).toFixed(2);
|
|
if (initForm.specs.advancedDimensions) {
|
|
topconeHeight = (initForm.specs.advancedDimensions.topConeHeight / 100).toFixed(2);
|
|
sidewallHeight = (initForm.specs.advancedDimensions.sidewallHeight / 100).toFixed(2);
|
|
hopperHeight = (initForm.specs.advancedDimensions.hopperHeight / 100).toFixed(2);
|
|
}
|
|
}
|
|
}
|
|
let high = initForm.highTemp ?? 20;
|
|
let low = initForm.lowTemp ?? 10;
|
|
let target = initForm.inventory?.targetTemperature ?? 15;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
high = parseFloat((high * 1.8 + 32).toFixed(2));
|
|
low = parseFloat((low * 1.8 + 32).toFixed(2));
|
|
target = parseFloat((target * 1.8 + 32).toFixed(2));
|
|
}
|
|
|
|
let gb =
|
|
initForm.storage === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
|
? initForm.inventory?.grainBushels
|
|
? initForm.inventory.grainBushels * 35.239
|
|
: 0
|
|
: initForm.inventory?.grainBushels ?? 0;
|
|
let weight = ""
|
|
if(initForm.inventory){
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
weight = (gb/initForm.inventory.bushelsPerTonne).toFixed(2)
|
|
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
weight = (gb/(initForm.inventory.bushelsPerTonne * 0.907)).toFixed(2)
|
|
}
|
|
if(initForm.inventory.customGrain){
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
weight = (gb/initForm.inventory.customGrain.bushelsPerTonne).toFixed(2)
|
|
}else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
weight = (gb/(initForm.inventory.customGrain.bushelsPerTonne * 0.907)).toFixed(2)
|
|
}
|
|
}
|
|
}
|
|
|
|
setForm(initForm);
|
|
setHighTempC(initForm.highTemp ?? 20);
|
|
setLowTempC(initForm.lowTemp ?? 10);
|
|
setFormExtension({
|
|
capacity: initForm.specs?.bushelCapacity.toString() ?? "",
|
|
height: h,
|
|
diameter: d,
|
|
grainBushels: gb.toFixed(2),
|
|
grainWeight: weight,
|
|
// grainTons: initForm.inventory?.bushelsPerTon
|
|
// ? (gb / initForm.inventory.bushelsPerTon).toFixed(2)
|
|
// : "",
|
|
grainType: initForm.inventory?.grainType
|
|
? ToGrainOption(initForm.inventory.grainType)
|
|
: null,
|
|
grainUse: initForm.inventory?.grainUse
|
|
? ToGrainUseOption(initForm.inventory.grainUse)
|
|
: null,
|
|
grainSubtype: initForm.inventory?.grainSubtype ?? "",
|
|
customTypeName: initForm.inventory?.customTypeName ?? "",
|
|
highTemp: high,
|
|
lowTemp: low,
|
|
tempTarget: target,
|
|
hopperHeight: hopperHeight,
|
|
topConeHeight: topconeHeight,
|
|
sidewallHeight: sidewallHeight
|
|
});
|
|
setInMoistureString(initForm.inventory?.initialMoisture.toString() ?? "0");
|
|
setTMoistureString(initForm.inventory?.targetMoisture.toString() ?? "0");
|
|
setMoistureTargetDeviation(initForm.inventory?.moistureTargetDeviation.toString() ?? "0");
|
|
setAutoFillThreshold(initForm.inventory?.autoThreshold ?? 5)
|
|
let dropDistance = initForm.inventory?.lidarDropCm ?? 0
|
|
if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
|
dropDistance = Math.round((dropDistance/30.48)*100)/100
|
|
}
|
|
setLidarDropDistance(dropDistance)
|
|
setActiveStep(0);
|
|
if (bin) {
|
|
setAutoTopNode(bin.settings.autoGrainNode);
|
|
setIsCustomInventory(bin.storage() !== pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
|
|
}
|
|
setStorageType(initForm.storage);
|
|
setInitialized(true);
|
|
} else {
|
|
setInitialized(false);
|
|
}
|
|
}, [bin, open, mode, openedBinYard, user]);
|
|
|
|
useEffect(() => {
|
|
if (mode === "remove") {
|
|
setRemoveIsOpen(true);
|
|
}
|
|
}, [mode]);
|
|
|
|
const loadBinYards = useCallback(() => {
|
|
if (props.binYards) {
|
|
let yards: Option[] = props.binYards.map(yard => {
|
|
let option = { value: yard.key, label: yard.name } as Option;
|
|
if (yard.key === form.yardKey) {
|
|
setSelectedBinYard(option);
|
|
}
|
|
return option;
|
|
});
|
|
let y: Option[] = [];
|
|
y.push({ value: "", label: "No Yard" } as Option);
|
|
y = yards.concat(y);
|
|
setBinYardOptions(y);
|
|
} else {
|
|
binYardAPI
|
|
.listBinYards(350, 0, "desc", as ? as : userID, undefined, undefined, as)
|
|
.then(resp => {
|
|
let data = resp.data.yard ? resp.data.yard : [];
|
|
let yards: Option[] = data.map((yard: pond.BinYard) => {
|
|
let option = { value: yard.settings?.key, label: yard.settings?.name } as Option;
|
|
if (yard.settings?.key === form.yardKey) {
|
|
setSelectedBinYard(option);
|
|
}
|
|
return option;
|
|
});
|
|
let y: Option[] = [];
|
|
y.push({ value: "", label: "No Yard" } as Option);
|
|
y = yards.concat(y);
|
|
setBinYardOptions(y);
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
}, [binYardAPI, form.yardKey, userID, as, props.binYards]);
|
|
|
|
const loadLibraCartDestinations = useCallback(()=>{
|
|
if(inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART){
|
|
//load the destinations for the user/team they are viewing as
|
|
let options: Option[] = []
|
|
libracartAPI.listDestinations(0,0,undefined, as)
|
|
.then(resp=>{
|
|
//set the options for the search select
|
|
resp.data.destinations.forEach(d => {
|
|
let newOp: Option = {
|
|
label: d.name,
|
|
value: d.id
|
|
}
|
|
options.push(newOp)
|
|
//set the current option based on the key in the form
|
|
if (d.id === form.libracartDestinationKey){
|
|
setlcDestination(newOp)
|
|
}
|
|
})
|
|
setlcDestinationOptions(options)
|
|
}).catch(()=>{})
|
|
}
|
|
},[libracartAPI, inventoryControl, as, form.libracartDestinationKey])
|
|
|
|
useEffect(() => {
|
|
loadBinYards();
|
|
loadLibraCartDestinations();
|
|
}, [loadBinYards, loadLibraCartDestinations]);
|
|
|
|
useEffect(() => {
|
|
if (bin?.settings.specs?.bushelCapacity) {
|
|
setInputCapacity(bin.settings.specs.bushelCapacity.toFixed(0));
|
|
}
|
|
}, [bin]);
|
|
|
|
const close = (refresh?: boolean) => {
|
|
onClose(refresh);
|
|
};
|
|
|
|
const addBin = () => {
|
|
// If initial moisture is updated, so does the timestamp
|
|
if (form.inventory?.initialMoisture !== bin?.settings.inventory?.initialMoisture) {
|
|
if (form.inventory) form.inventory.initialTimestamp = moment().format();
|
|
}
|
|
if (form.inventory?.initialTimestamp === "") {
|
|
form.inventory.initialTimestamp = moment().format();
|
|
}
|
|
if (form.specs) {
|
|
form.specs.bushelCapacity = Number(inputCapacity);
|
|
if (form.specs.useAdvanced && form.specs.advancedDimensions) {
|
|
let adD = form.specs.advancedDimensions;
|
|
form.specs.heightCm = adD.hopperHeight + adD.sidewallHeight + adD.topConeHeight;
|
|
}
|
|
}
|
|
|
|
//if coordinates are passed in give the bin a location
|
|
if (coords) {
|
|
form.location = pond.Location.create({
|
|
longitude: coords.longitude,
|
|
latitude: coords.latitude
|
|
});
|
|
}
|
|
form.mode = pond.BinMode.BIN_MODE_STORAGE; //make sure bins are in storage mode when creating new bins
|
|
form.storage = storageType;
|
|
form.highTemp = highTempC;
|
|
form.lowTemp = lowTempC;
|
|
form.autoGrainNode = autoTopNode;
|
|
if (form.inventory) {
|
|
form.inventory.inventoryControl = inventoryControl
|
|
form.inventory.autoThreshold = autoFillThreshold
|
|
form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
|
|
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
|
|
form.inventory.customGrain = undefined
|
|
}else{
|
|
form.inventory.customGrain = customGrain
|
|
}
|
|
}
|
|
|
|
binAPI
|
|
.addBin(form, as)
|
|
.then(resp => {
|
|
openSnack("Successfully created bin");
|
|
if (!coords) {
|
|
let binPath: string = "/bins/" + resp.data.bin;
|
|
navigate(binPath);
|
|
}
|
|
close(true);
|
|
})
|
|
.catch((err: any) => {
|
|
openSnack("Failed to create bin");
|
|
close();
|
|
});
|
|
};
|
|
|
|
const updateBin = () => {
|
|
// If initial moisture is updated, so does the timestamp
|
|
if (form.inventory?.initialMoisture !== bin?.settings.inventory?.initialMoisture) {
|
|
if (form.inventory) form.inventory.initialTimestamp = moment().format();
|
|
}
|
|
if (form.inventory?.initialTimestamp === "") {
|
|
form.inventory.initialTimestamp = moment().format();
|
|
}
|
|
if (form.specs) {
|
|
form.specs.bushelCapacity = Number(inputCapacity);
|
|
if (form.specs.useAdvanced && form.specs.advancedDimensions) {
|
|
let adD = form.specs.advancedDimensions;
|
|
form.specs.heightCm = adD.hopperHeight + adD.sidewallHeight + adD.topConeHeight;
|
|
}
|
|
}
|
|
if (form.inventory?.grainBushels !== bin?.settings.inventory?.grainBushels) {
|
|
let current = bin?.settings.inventory?.grainBushels ?? 0;
|
|
let newVal = form.inventory?.grainBushels ?? 0;
|
|
setGrainDiff(newVal - current);
|
|
setGrainUpdate(true);
|
|
} else {
|
|
if (bin && bin.key()) {
|
|
form.storage = storageType;
|
|
form.highTemp = highTempC;
|
|
form.lowTemp = lowTempC;
|
|
form.autoGrainNode = autoTopNode;
|
|
if (form.inventory) {
|
|
form.inventory.inventoryControl = inventoryControl
|
|
form.inventory.autoThreshold = autoFillThreshold
|
|
//if the users preferences are in feet convert the distance to cm otherwise it was entered as cm so use that
|
|
form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
|
|
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
|
|
form.inventory.customGrain = undefined
|
|
}else{
|
|
form.inventory.customGrain = customGrain
|
|
}
|
|
}
|
|
binAPI
|
|
.updateBin(bin.key(), form, as)
|
|
.then(response => {
|
|
openSnack("Updated " + bin.name());
|
|
close(true);
|
|
})
|
|
.catch((err: any) => {
|
|
openSnack("Failed to update " + bin.name());
|
|
close();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const grainInventoryController = () => {
|
|
if (bin) {
|
|
let newBin = clone(bin);
|
|
newBin.settings = cloneDeep(form); //need to make sure to use all the changed settings
|
|
if (newBin.settings.inventory) {
|
|
newBin.settings.inventory.grainBushels = bin.settings.inventory?.grainBushels ?? 0; //but also need to use the old inventory amount so the diff is applied correctly
|
|
}
|
|
return (
|
|
<GrainTransaction
|
|
open={grainUpdate}
|
|
mainObject={newBin}
|
|
grainAdjustment={grainDiff}
|
|
close={() => {
|
|
setGrainUpdate(false);
|
|
setGrainDiff(0);
|
|
}}
|
|
callback={() => {
|
|
close(true);
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
};
|
|
|
|
const removeBin = () => {
|
|
if (bin && bin.key()) {
|
|
binAPI
|
|
.removeBin(bin.key(), as)
|
|
.then(response => {
|
|
openSnack(bin.name() + " was removed");
|
|
navigate("/bins");
|
|
})
|
|
.catch(err => {
|
|
openSnack("Failed to remove " + bin.name() + ".");
|
|
setRemoveIsOpen(false);
|
|
close();
|
|
});
|
|
}
|
|
};
|
|
|
|
const isFormValid = () => {
|
|
let valid = false;
|
|
if (
|
|
validInitial &&
|
|
validTarget &&
|
|
validDeviation &&
|
|
+formExtension.highTemp > +formExtension.lowTemp &&
|
|
+formExtension.lowTemp <= +formExtension.tempTarget &&
|
|
+formExtension.highTemp >= +formExtension.tempTarget
|
|
) {
|
|
valid = true;
|
|
}
|
|
return valid;
|
|
};
|
|
|
|
const stepComplete = (step: number) => {
|
|
switch (step) {
|
|
case 1:
|
|
let fan = form.fan;
|
|
let fanValid =
|
|
fan && fan.type !== pond.FanType.FAN_TYPE_UNKNOWN
|
|
? fan.size !== pond.FanHp.FAN_HP_UNKNOWN
|
|
: true;
|
|
return fanValid;
|
|
case 0:
|
|
let specs = form.specs;
|
|
let specsValid =
|
|
specs && Number(inputCapacity) > 0 && specs.diameterCm > 0 && specs.heightCm > 0;
|
|
return specsValid
|
|
default:
|
|
return true;
|
|
}
|
|
};
|
|
|
|
const stepNext = () => {
|
|
setActiveStep(activeStep + 1);
|
|
};
|
|
|
|
const stepBack = () => {
|
|
setActiveStep(activeStep - 1);
|
|
};
|
|
|
|
const updateForm = (
|
|
key: keyof pond.BinSettings,
|
|
value: pond.BinSettings[keyof pond.BinSettings]
|
|
) => {
|
|
setForm(pond.BinSettings.create({ ...form, [key]: value }));
|
|
};
|
|
|
|
const updateFormExtension = (
|
|
key: keyof BinFormExtension,
|
|
value: BinFormExtension[keyof BinFormExtension]
|
|
) => {
|
|
setFormExtension({ ...formExtension, [key]: value });
|
|
};
|
|
|
|
const titleText = () => {
|
|
return mode === "add" ? "Create Bin" : "Update " + (bin ? bin.name() : "Bin");
|
|
};
|
|
|
|
const stepper = () => {
|
|
if (mode === "add") {
|
|
return (
|
|
<Stepper
|
|
activeStep={activeStep}
|
|
alternativeLabel
|
|
classes={{
|
|
root: classes.stepper
|
|
}}>
|
|
{steps.map((s, i) => (
|
|
<Step
|
|
key={i}
|
|
onClick={() => {
|
|
const incompleteUpTo = (upTo: number): number => {
|
|
for (let j = 0; j < i; j++) {
|
|
if (!stepComplete(j)) {
|
|
return j;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
if (i <= activeStep || incompleteUpTo(i) === -1) {
|
|
setActiveStep(i);
|
|
} else {
|
|
openSnack("Step " + (incompleteUpTo(i) + 1).toString() + " is incomplete");
|
|
}
|
|
}}>
|
|
<StepLabel>{s.label}</StepLabel>
|
|
</Step>
|
|
))}
|
|
</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 }}>
|
|
{steps.map((s, i) => (
|
|
<Tab key={i} label={s.label} aria-label={s.label} />
|
|
))}
|
|
</Tabs>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const title = () => {
|
|
if (isMobile) {
|
|
return (
|
|
<AppBar position="relative">
|
|
<Toolbar>
|
|
<IconButton edge="start" color="inherit" onClick={() => close()} aria-label="close">
|
|
<Close />
|
|
</IconButton>
|
|
<Typography variant="h6" style={{ flex: 1 }}>
|
|
{titleText()}
|
|
</Typography>
|
|
{mode === "add" ? (
|
|
<Button
|
|
onClick={() => addBin()}
|
|
disabled={!isFormValid() || activeStep !== steps.length - 1}
|
|
aria-label="create bin"
|
|
color="inherit">
|
|
Create
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={() => updateBin()}
|
|
disabled={!canEdit || !isFormValid()}
|
|
aria-label="update bin">
|
|
Update
|
|
</Button>
|
|
)}
|
|
</Toolbar>
|
|
{stepper()}
|
|
</AppBar>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<DialogTitle>{titleText()}</DialogTitle>
|
|
{mode === "update" && (
|
|
<Tabs
|
|
value={activeStep}
|
|
onChange={(_, value) => setActiveStep(value)}
|
|
indicatorColor="primary"
|
|
textColor="primary"
|
|
variant="fullWidth"
|
|
aria-label="bin tabs"
|
|
classes={{ root: classes.tabSmall }}>
|
|
{steps.map((s, i) => (
|
|
<Tab className={classes.tabSmall} key={i} label={s.label} aria-label={s.label} />
|
|
))}
|
|
</Tabs>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const binYardForm = () => {
|
|
return (
|
|
<Box style={{ marginTop: theme.spacing(1) }}>
|
|
<Typography variant="subtitle2" align="center" color="primary" gutterBottom>
|
|
Bin Yard
|
|
</Typography>
|
|
|
|
<Box marginY={1}>
|
|
<SearchSelect
|
|
selected={selectedBinYard}
|
|
changeSelection={(option: Option | null) => {
|
|
let newForm = form;
|
|
newForm.yardKey = option?.value;
|
|
setForm(newForm);
|
|
setSelectedBinYard(option ? option : null);
|
|
}}
|
|
disabled={!canEdit}
|
|
options={binYardOptions ? binYardOptions : []}
|
|
label="Yard"
|
|
loading={false}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const inventoryControlLabel = (control: pond.BinInventoryControl) => {
|
|
switch(control){
|
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR:
|
|
return "Auto (lidar)"
|
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC:
|
|
return "Auto (cable)"
|
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR:
|
|
return "Hybrid (lidar)"
|
|
case pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART:
|
|
return "Auto (Libra Cart)"
|
|
default:
|
|
return "Manual"
|
|
}
|
|
};
|
|
|
|
|
|
const inventoryAmount = () => {
|
|
const grainWeight = formExtension.grainWeight;
|
|
const binQuantity = formExtension.grainBushels;
|
|
|
|
//as long as the storage type is not fertilizer it is some sort of grain, whether it is supported or custom is not important in this instance so we do not need to check for unknown
|
|
if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){
|
|
return (
|
|
<TextField
|
|
label="Amount"
|
|
value={grainWeight}
|
|
type="number"
|
|
onChange={event => {
|
|
let newWeight = event.target.value;
|
|
//get the bushel amount
|
|
if (!isNaN(+newWeight)) {
|
|
let newBushels = Math.round(+newWeight * bushelConversion * 100) / 100;
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainBushels: newBushels
|
|
})
|
|
);
|
|
//updateFormExtension("grainBushels", newBushels.toString())
|
|
updateFormExtension("grainWeight", newWeight);
|
|
}
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
disabled={!canEdit}
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">{user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}</InputAdornment>
|
|
}}
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
)
|
|
}else{
|
|
return (
|
|
<TextField
|
|
label="Amount"
|
|
value={binQuantity}
|
|
type="number"
|
|
onChange={event => {
|
|
let capacity =
|
|
bin?.settings.storage === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
|
? +inputCapacity * 35.239
|
|
: inputCapacity;
|
|
let value = event?.target.value;
|
|
let v =
|
|
value === ""
|
|
? 0
|
|
: isNaN(parseFloat(value))
|
|
? 0
|
|
: parseFloat(value) >= +capacity
|
|
? +capacity
|
|
: parseFloat(value) <= 0
|
|
? 0
|
|
: parseFloat(value);
|
|
if (bin?.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER && v) {
|
|
v = v / 35.239;
|
|
}
|
|
//setNewGrainQuantity(v);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainBushels: v
|
|
})
|
|
);
|
|
updateFormExtension("grainBushels", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
|
? "Litres"
|
|
: "Bushels"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
disabled={
|
|
!canEdit ||
|
|
inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC
|
|
}
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
|
|
const inventoryForm = () => {
|
|
const inventory = form.inventory;
|
|
const empty = inventory ? inventory.empty : false;
|
|
const binQuantity = formExtension.grainBushels;
|
|
const grainType = formExtension.grainType;
|
|
const grainSubtype = formExtension.grainSubtype;
|
|
const grainUse = formExtension.grainUse;
|
|
const customTypeName = formExtension.customTypeName;
|
|
return (
|
|
<Box>
|
|
<Typography variant="subtitle2" align="center" color="primary">
|
|
Bin Inventory
|
|
</Typography>
|
|
|
|
<Grid container justifyContent="space-between" alignItems="center">
|
|
<Grid>
|
|
<Grid container alignItems="center">
|
|
<Grid>Grain</Grid>
|
|
<Grid>
|
|
<Switch
|
|
color="default"
|
|
value={isCustomInventory}
|
|
checked={isCustomInventory}
|
|
onChange={(_, checked) => {
|
|
setIsCustomInventory(checked);
|
|
if (checked) {
|
|
setStorageType(pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainType: pond.Grain.GRAIN_CUSTOM,
|
|
bushelsPerTonne: 0,
|
|
})
|
|
);
|
|
updateFormExtension("grainType", null);
|
|
setBushelConversion(0)
|
|
} else {
|
|
setStorageType(pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
customTypeName: undefined
|
|
})
|
|
);
|
|
updateFormExtension("customTypeName", "");
|
|
}
|
|
}}
|
|
name="storage"
|
|
/>
|
|
</Grid>
|
|
<Grid>Custom</Grid>
|
|
</Grid>
|
|
</Grid>
|
|
<Grid>
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
checked={empty}
|
|
onChange={(_, checked) =>
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
empty: checked
|
|
})
|
|
)
|
|
}
|
|
name="empty"
|
|
/>
|
|
}
|
|
disabled={!canEdit}
|
|
label="Empty"
|
|
labelPlacement="start"
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
{isCustomInventory && (
|
|
<RadioGroup
|
|
row
|
|
value={storageType}
|
|
onChange={(_, value) => {
|
|
let storageType = parseFloat(value) as pond.BinStorage;
|
|
setStorageType(storageType);
|
|
if (storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
|
|
// formExtension.bushelsPerTonne = "";
|
|
setBushelConversion(0)
|
|
formExtension.grainBushels = (
|
|
parseFloat(formExtension.grainBushels) * 35.239
|
|
).toFixed(0);
|
|
} else {
|
|
formExtension.grainBushels = (
|
|
parseFloat(formExtension.grainBushels) / 35.239
|
|
).toFixed(0);
|
|
}
|
|
}}>
|
|
<FormControlLabel
|
|
value={pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN}
|
|
control={<Radio />}
|
|
label={"Grain"}
|
|
/>
|
|
<FormControlLabel
|
|
value={pond.BinStorage.BIN_STORAGE_FERTILIZER}
|
|
control={<Radio />}
|
|
label={"Fertilizer"}
|
|
/>
|
|
</RadioGroup>
|
|
)}
|
|
<Accordion className={classes.bottomSpacing}>
|
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
<Grid
|
|
width={"100%"}
|
|
container
|
|
direction="row"
|
|
alignContent="center"
|
|
alignItems="center"
|
|
justifyContent="space-between">
|
|
<Grid>
|
|
<Typography>Inventory Control</Typography>
|
|
</Grid>
|
|
<Grid>
|
|
<Typography>{inventoryControlLabel(inventoryControl)}</Typography>
|
|
</Grid>
|
|
</Grid>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
<RadioGroup
|
|
row
|
|
value={inventoryControl}
|
|
onChange={(_, value) => {
|
|
setInventoryControl(+value);
|
|
}}>
|
|
<FormControlLabel
|
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_MANUAL}
|
|
control={<Radio />}
|
|
label={"Manual"}
|
|
/>
|
|
<FormControlLabel
|
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC}
|
|
control={<Radio />}
|
|
label={"Auto (Cable)"}
|
|
/>
|
|
<FormControlLabel
|
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR}
|
|
control={<Radio />}
|
|
label={"Auto (Lidar)"}
|
|
/>
|
|
<FormControlLabel
|
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR}
|
|
control={<Radio />}
|
|
label={"Hybrid (Lidar)"}
|
|
/>
|
|
{user.hasFeature("libra-cart") &&
|
|
<FormControlLabel
|
|
value={pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART}
|
|
control={<Radio />}
|
|
label={"Auto (Libra Cart)"}
|
|
/>
|
|
}
|
|
</RadioGroup>
|
|
</AccordionDetails>
|
|
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC &&
|
|
!autoTopNode && (
|
|
<Box marginX={2} marginBottom={2}>
|
|
<Typography variant="caption">
|
|
Auto Top Node must be activated for automatic inventory control
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR && (
|
|
<Box marginX={2} marginBottom={2}>
|
|
<Typography variant="caption">
|
|
Hybrid control will submit a transaction that must be approved by the user before inventory is changed
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
{(inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC_LIDAR ||
|
|
inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR) &&
|
|
<Box width="100%" padding={2}>
|
|
<TextField
|
|
fullWidth
|
|
variant="outlined"
|
|
label="Auto Threshold Percent"
|
|
helperText="Fill percent threshold that must be crossed to affect inventory"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
%
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
type="number"
|
|
value={autoFillThreshold}
|
|
onChange={e => {
|
|
setAutoFillThreshold(+e.target.value)
|
|
}}
|
|
/>
|
|
<TextField
|
|
sx={{marginTop: 4}}
|
|
fullWidth
|
|
variant="outlined"
|
|
label="Drop Distance"
|
|
helperText="The distance between the lidar and the peak"
|
|
type="number"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "cm"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
value={lidarDropDistance}
|
|
onChange={e => {
|
|
setLidarDropDistance(+e.target.value)
|
|
}}
|
|
/>
|
|
</Box>
|
|
}
|
|
{inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_LIBRACART &&
|
|
<Box width="100%" padding={2}>
|
|
<SearchSelect
|
|
label="Libra Cart Destination"
|
|
selected={lcDestination}
|
|
changeSelection={option => {
|
|
let newForm = form;
|
|
newForm.libracartDestinationKey = option?.value;
|
|
setForm(newForm);
|
|
setlcDestination(option ? option : null);
|
|
}}
|
|
disabled={!canEdit}
|
|
options={lcDestinationOptions}
|
|
/>
|
|
<Typography variant="caption">
|
|
The linked bins inventory will be adjusted When the Libra Cart Destination data is synced every 6 hours
|
|
</Typography>
|
|
</Box>
|
|
}
|
|
</Accordion>
|
|
{empty ? (
|
|
<Box marginTop={3} display="flex" flexDirection="column" alignItems="center">
|
|
<Typography variant="subtitle1" color="textPrimary">
|
|
Bin is currently empty
|
|
</Typography>
|
|
<Typography variant="body1" color="textSecondary">
|
|
Inventory settings will be ignored
|
|
</Typography>
|
|
<Typography variant="body2" color="textSecondary">
|
|
We may ask for your inventory again in the future
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Box>
|
|
{/* <Typography variant="subtitle1" color="textSecondary" gutterBottom>
|
|
{isCustom ? "Custom" : "Grain"}
|
|
</Typography> */}
|
|
<Box>
|
|
{isCustomInventory ?
|
|
<React.Fragment>
|
|
{storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER ?
|
|
<React.Fragment>
|
|
<TextField
|
|
label="Type"
|
|
value={customTypeName}
|
|
type="text"
|
|
onChange={event => {
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
customTypeName: event.target.value
|
|
})
|
|
);
|
|
updateFormExtension("customTypeName", event.target.value);
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
</React.Fragment>
|
|
:
|
|
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
|
<CustomGrainSelector
|
|
initialGrain={customGrain}
|
|
onGrainSettingsChange={(newGrainSettings) => {
|
|
setCustomGrain(newGrainSettings)
|
|
let conversion = 0
|
|
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
conversion = newGrainSettings.bushelsPerTonne
|
|
}else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
conversion = newGrainSettings.bushelsPerTonne * 0.907
|
|
}
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
bushelsPerTonne: newGrainSettings.bushelsPerTonne,
|
|
})
|
|
);
|
|
setBushelConversion(conversion)
|
|
if(conversion !== 0 && !isNaN(+binQuantity)){
|
|
updateFormExtension("grainWeight", (+binQuantity/conversion).toFixed(2))
|
|
}
|
|
}}/>
|
|
</Box>
|
|
}
|
|
</React.Fragment>
|
|
:
|
|
<React.Fragment>
|
|
<Box className={classes.bottomSpacing}>
|
|
<SearchSelect
|
|
label="Type"
|
|
selected={grainType}
|
|
changeSelection={option => {
|
|
let newGrainType = option
|
|
? pond.Grain[option.value as keyof typeof pond.Grain]
|
|
: pond.Grain.GRAIN_INVALID;
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainType: newGrainType,
|
|
bushelsPerTonne: GrainDescriber(newGrainType).bushelsPerTonne,
|
|
})
|
|
);
|
|
let conversion = 0
|
|
if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
|
|
conversion = GrainDescriber(newGrainType).bushelsPerTonne
|
|
}else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
|
|
conversion = GrainDescriber(newGrainType).bushelsPerTon
|
|
}
|
|
setBushelConversion(conversion)
|
|
if(conversion !== 0 && !isNaN(+binQuantity)){
|
|
//updateFormExtension("grainWeight", (+binQuantity/conversion).toFixed(2))
|
|
let newWeight = (+binQuantity/conversion).toFixed(2)
|
|
formExtension.grainWeight = newWeight
|
|
}
|
|
updateFormExtension("grainType", option);
|
|
}}
|
|
group
|
|
disabled={!canEdit}
|
|
options={grainOptions}
|
|
/>
|
|
</Box>
|
|
<TextField
|
|
label="Grain Variant"
|
|
value={grainSubtype}
|
|
type="text"
|
|
onChange={event => {
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainSubtype: event.target.value
|
|
})
|
|
);
|
|
updateFormExtension("grainSubtype", event.target.value);
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
disabled={!grainType}
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
</React.Fragment>
|
|
}
|
|
{inventoryAmount()}
|
|
{/* {getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_BUSHELS ||
|
|
storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER ||
|
|
formExtension.bushelsPerTonne === "0" ||
|
|
formExtension.bushelsPerTonne === "" ? (
|
|
<TextField
|
|
label="Amount"
|
|
value={binQuantity}
|
|
type="number"
|
|
onChange={event => {
|
|
let capacity =
|
|
bin?.settings.storage === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
|
? +inputCapacity * 35.239
|
|
: inputCapacity;
|
|
let value = event?.target.value;
|
|
let v =
|
|
value === ""
|
|
? 0
|
|
: isNaN(parseFloat(value))
|
|
? 0
|
|
: parseFloat(value) >= +capacity
|
|
? +capacity
|
|
: parseFloat(value) <= 0
|
|
? 0
|
|
: parseFloat(value);
|
|
if (bin?.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER && v) {
|
|
v = v / 35.239;
|
|
}
|
|
//setNewGrainQuantity(v);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainBushels: v
|
|
})
|
|
);
|
|
updateFormExtension("grainBushels", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{storageType === pond.BinStorage.BIN_STORAGE_FERTILIZER
|
|
? "Litres"
|
|
: "Bushels"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
disabled={
|
|
!canEdit ||
|
|
inventoryControl === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC
|
|
}
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
) : (
|
|
<TextField
|
|
label="Amount"
|
|
value={grainWeight}
|
|
type="number"
|
|
onChange={event => {
|
|
let newWeight = event.target.value;
|
|
const bushelsPerTonne = +formExtension.bushelsPerTonne;
|
|
|
|
//get the bushel amount
|
|
if (!isNaN(+newWeight) && !isNaN(+bushelsPerTonne)) {
|
|
let newBushels = Math.round(+newWeight * +bushelsPerTonne * 100) / 100;
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainBushels: newBushels
|
|
})
|
|
);
|
|
//updateFormExtension("grainBushels", newBushels.toString())
|
|
updateFormExtension("grainWeight", newWeight);
|
|
}
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
disabled={!canEdit}
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">mT</InputAdornment>
|
|
}}
|
|
className={classes.bottomSpacing}
|
|
/>
|
|
)} */}
|
|
{!isCustomInventory && (
|
|
<SearchSelect
|
|
label="Use"
|
|
selected={grainUse}
|
|
changeSelection={option => {
|
|
let grainUse = option
|
|
? pond.GrainUse[option.value as keyof typeof pond.GrainUse]
|
|
: pond.GrainUse.GRAIN_USE_UNKNOWN;
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
grainUse: grainUse
|
|
})
|
|
);
|
|
updateFormExtension("grainUse", option);
|
|
}}
|
|
group
|
|
disabled={!canEdit}
|
|
options={grainUseOptions}
|
|
/>
|
|
)}
|
|
</Box>
|
|
|
|
{/*<Box marginTop={1}>
|
|
<DateTimePicker
|
|
|
|
disabled={!canEdit}
|
|
renderInput={props => <TextField {...props} helperText="" />}
|
|
value={time}
|
|
onChange={date =>
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
initialTimestamp: moment(date ?? undefined).format()
|
|
})
|
|
)
|
|
}
|
|
disableFuture
|
|
label="Start Time"
|
|
/>
|
|
</Box>*/}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const fanPicker = () => {
|
|
return (
|
|
<FanPicker
|
|
updateFan={(fanId: number) => {
|
|
if (fanId !== undefined) {
|
|
//set the fan id in the bin settings form
|
|
form.fanId = fanId;
|
|
//remove the old fan from the bin
|
|
form.fan = null;
|
|
}
|
|
}}
|
|
fanID={form.fanId}
|
|
useCardView
|
|
/>
|
|
);
|
|
};
|
|
|
|
const getBushels = (height: number, diameter: number): number => {
|
|
let d = diameter * 3.281;
|
|
let h = height * 3.281;
|
|
return 0.628 * (d * d) * h;
|
|
};
|
|
|
|
/**
|
|
* takes in the dimensions of the bin to calculate its bushel capacity
|
|
* @param diameter
|
|
* @param topConeHeight
|
|
* @param sidewallHeight
|
|
* @param hopperHeight
|
|
* @returns the bushel capacity of the bin with given dimensions
|
|
*/
|
|
const advancedGrainCalc = (
|
|
diameter: number,
|
|
topConeHeight: number,
|
|
sidewallHeight: number,
|
|
hopperHeight?: number
|
|
) => {
|
|
//if the units are in meters will need to convert to feet
|
|
let tcH = topConeHeight;
|
|
let sH = sidewallHeight;
|
|
let hH = hopperHeight ?? 0;
|
|
let d = diameter;
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
|
tcH = tcH * 3.281;
|
|
sH = sH * 3.281;
|
|
hH = hH * 3.281;
|
|
d = d * 3.281;
|
|
}
|
|
let topConeBushels = ConeVolume(d / 2, tcH) * conversionConstantFT;
|
|
let sidewallBushels = CylinderVolume(d / 2, sH) * conversionConstantFT;
|
|
let hopperBushels = ConeVolume(d / 2, hH ?? 0) * conversionConstantFT;
|
|
|
|
return Math.round(topConeBushels + sidewallBushels + hopperBushels);
|
|
};
|
|
|
|
const generalForm = () => {
|
|
const name = form?.name ?? "";
|
|
const shape = form.specs ? form.specs.shape.valueOf() : "";
|
|
const advanced = form.specs ? form.specs.useAdvanced : false;
|
|
const roofAngle =
|
|
form.specs && form.specs.advancedDimensions ? form.specs.advancedDimensions.roofAngle : 0;
|
|
const hopperAngle =
|
|
form.specs && form.specs.advancedDimensions ? form.specs.advancedDimensions.hopperAngle : 0;
|
|
|
|
let height = formExtension.height;
|
|
let topConeHeight = formExtension.topConeHeight;
|
|
let sidewallHeight = formExtension.sidewallHeight;
|
|
let hopperHeight = formExtension.hopperHeight;
|
|
let diameter = formExtension.diameter;
|
|
|
|
// if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
// diameterM = (Number(diameterM) * 3.28084).toFixed(2);
|
|
// heightM = (Number(heightM) * 3.28084).toFixed(2);
|
|
// }
|
|
return (
|
|
<Box>
|
|
<Box width={"100%"}>
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
value={isCustomBin}
|
|
checked={isCustomBin}
|
|
title="Build Custom Bin"
|
|
onClick={() => {
|
|
setIsCustomBin(!isCustomBin);
|
|
}}
|
|
/>
|
|
}
|
|
label="Build Custom Bin"
|
|
labelPlacement="start"
|
|
/>
|
|
</Box>
|
|
<Grid container direction="row" alignItems="center" alignContent="center">
|
|
<Grid >
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
value={autoTopNode}
|
|
checked={autoTopNode}
|
|
onChange={(_, checked) => {
|
|
setAutoTopNode(checked);
|
|
}}
|
|
/>
|
|
}
|
|
label={"Automatic Top Node"}
|
|
/>
|
|
</Grid>
|
|
<Grid >
|
|
<Tooltip
|
|
title={
|
|
<React.Fragment>
|
|
<Typography>
|
|
Will run every day using the Day/Night cycle of temperatures on each of the bins
|
|
cables to determine which nodes are in the grain as opposed to in the air and
|
|
update its interactions accordingly.
|
|
</Typography>
|
|
<Typography>
|
|
If a bin is determined to be empty it will remove the interactions on its
|
|
cables.
|
|
</Typography>
|
|
</React.Fragment>
|
|
}>
|
|
<Info />
|
|
</Tooltip>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<Typography variant="subtitle2" align="center" color="primary" gutterBottom>
|
|
Bin Information
|
|
</Typography>
|
|
<Typography variant="body1" align="center" gutterBottom>
|
|
{/* {modelID !== 0
|
|
? getBinModel(modelID)?.Manufacturer + " " + getBinModel(modelID)?.Model
|
|
: "Custom"} */}
|
|
</Typography>
|
|
<Box marginTop={1} marginBottom={1}>
|
|
<TextField
|
|
label="Name"
|
|
fullWidth
|
|
variant="outlined"
|
|
value={name}
|
|
disabled={!canEdit}
|
|
onChange={e => updateForm("name", e.target.value)}
|
|
/>
|
|
</Box>
|
|
{isCustomBin ? (
|
|
<Box>
|
|
<Box marginTop={1} alignContent="center" justifyContent="center">
|
|
<TextField
|
|
label="Height"
|
|
type="number"
|
|
required
|
|
value={height}
|
|
disabled={!canEdit || advanced}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let d = diameter;
|
|
let valueM = value;
|
|
let dM = diameter;
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
valueM = (Number(value) * 0.3048).toFixed(2);
|
|
dM = (Number(d) * 0.3048).toFixed(2);
|
|
}
|
|
setModelID(0);
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
heightCm:
|
|
valueM === ""
|
|
? undefined
|
|
: isNaN(parseFloat(valueM))
|
|
? 0
|
|
: parseFloat(valueM) * 100
|
|
})
|
|
);
|
|
updateFormExtension("height", value);
|
|
setInputCapacity(getBushels(Number(valueM), Number(dM)).toFixed(0));
|
|
//updateFormExtension("capacity", getBushels(Number(value), Number(d)).toFixed(0));
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
style={{ width: "59%" }}
|
|
variant="outlined"
|
|
/>
|
|
<FormControlLabel
|
|
style={{ width: "38%", paddingTop: 5 }}
|
|
control={
|
|
<Switch
|
|
disabled={!canEdit}
|
|
value={advanced}
|
|
checked={advanced}
|
|
title="Advanced Dimensions"
|
|
onChange={(_, checked) => {
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
useAdvanced: checked
|
|
})
|
|
);
|
|
if (checked) {
|
|
let ext = cloneDeep(formExtension);
|
|
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(topConeHeight)) ? 0 : parseFloat(topConeHeight);
|
|
let h = isNaN(parseFloat(hopperHeight)) ? 0 : parseFloat(hopperHeight);
|
|
//change the total height to use the section values
|
|
ext.height = (s + t + h).toFixed(2);
|
|
//update the capacity with those dimensions
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
setFormExtension(ext);
|
|
}
|
|
}}
|
|
/>
|
|
}
|
|
label="Advanced"
|
|
labelPlacement="start"
|
|
/>
|
|
</Box>
|
|
<Collapse in={advanced}>
|
|
<Box marginTop={1}>
|
|
<Grid container spacing={1}>
|
|
<Grid size={{ xs: 8 }}>
|
|
<TextField
|
|
label="Top Cone Height"
|
|
type="number"
|
|
value={topConeHeight}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueM = value;
|
|
setModelID(0);
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
valueM = (Number(value) * 0.3048).toFixed(2);
|
|
}
|
|
let coneCM =
|
|
valueM === ""
|
|
? undefined
|
|
: isNaN(parseFloat(valueM))
|
|
? 0
|
|
: parseFloat(valueM) * 100;
|
|
|
|
//Calculate the roof angle
|
|
let dCm = form.specs?.diameterCm ?? 0; //get the diameter
|
|
let angle = RadiansToDegrees(Math.atan((coneCM ?? 0) / (dCm / 2))); //('arcTan(opposite / adjacent))' and convert to degrees
|
|
angle = Math.round(angle * 100) / 100; // round to 2 digits
|
|
|
|
let ext = cloneDeep(formExtension);
|
|
//calculate the total height from the parts
|
|
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(value)) ? 0 : parseFloat(value);
|
|
let h = isNaN(parseFloat(hopperHeight)) ? 0 : parseFloat(hopperHeight);
|
|
|
|
//trigger the advanced calculation for capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
ext.topConeHeight = value; //set the new top cone heigh
|
|
ext.height = (s + t + h).toFixed(2); // set the new total height
|
|
|
|
//update the extensions with the new values
|
|
setFormExtension({ ...ext });
|
|
|
|
//need to get the total height in cm
|
|
let totalHeightCM = s + t + h; //the total height in the users units
|
|
if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
|
}else{
|
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
|
}
|
|
|
|
//update the form with the new specs
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
heightCm: Math.round(totalHeightCM),
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
topConeHeight: coneCM,
|
|
roofAngle: angle
|
|
})
|
|
})
|
|
);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
|
|
? "m"
|
|
: "ft"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
<Grid size={{ xs: 4 }}>
|
|
<TextField
|
|
label="Roof Angle"
|
|
type="number"
|
|
value={roofAngle}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event.target.value;
|
|
let angle =
|
|
value === ""
|
|
? undefined
|
|
: isNaN(parseFloat(value))
|
|
? 0
|
|
: parseFloat(value);
|
|
let d = form.specs?.diameterCm ?? 0; //get the diameter
|
|
//calculate the new cone height for the roof angle
|
|
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
|
|
let newConeHeight =
|
|
user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
|
|
? (coneCM / 30.48).toFixed(2)
|
|
: (coneCM / 100).toFixed(2);
|
|
|
|
let ext = cloneDeep(formExtension);
|
|
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(newConeHeight)) ? 0 : parseFloat(newConeHeight);
|
|
let h = isNaN(parseFloat(hopperHeight)) ? 0 : parseFloat(hopperHeight);
|
|
|
|
//trigger the advanced calculation for capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
ext.topConeHeight = newConeHeight;
|
|
ext.height = (s + t + h).toFixed(2)
|
|
|
|
setFormExtension(ext);
|
|
|
|
//need to get the total height in cm
|
|
let totalHeightCM = s + t + h; //the total height in the users units
|
|
if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
|
}else{
|
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
|
}
|
|
|
|
setModelID(0);
|
|
//update the form with the new specs
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
heightCm: Math.round(totalHeightCM),
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
roofAngle: angle ?? 0,
|
|
topConeHeight: coneCM
|
|
})
|
|
})
|
|
);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">°</InputAdornment>
|
|
}}
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
<Grid size={{ xs: 8 }}>
|
|
<TextField
|
|
label="Sidewall Height"
|
|
type="number"
|
|
value={sidewallHeight}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueM = value;
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
valueM = (Number(value) * 0.3048).toFixed(2);
|
|
}
|
|
let ext = cloneDeep(formExtension);
|
|
let s = isNaN(parseFloat(value)) ? 0 : parseFloat(value);
|
|
let t = isNaN(parseFloat(topConeHeight)) ? 0 : parseFloat(topConeHeight);
|
|
let h = isNaN(parseFloat(hopperHeight)) ? 0 : parseFloat(hopperHeight);
|
|
|
|
//trigger the advanced calculation for capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
ext.sidewallHeight = value;
|
|
ext.height = (s + t + h).toFixed(2);
|
|
|
|
setFormExtension({ ...ext });
|
|
|
|
setModelID(0);
|
|
|
|
//need to get the total height in cm
|
|
let totalHeightCM = s + t + h; //the total height in the users units
|
|
if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
|
|
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
|
|
}else{
|
|
totalHeightCM = totalHeightCM * 100 //convert from m to cm
|
|
}
|
|
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
heightCm: Math.round(totalHeightCM),
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
sidewallHeight:
|
|
valueM === ""
|
|
? undefined
|
|
: isNaN(parseFloat(valueM))
|
|
? 0
|
|
: parseFloat(valueM) * 100
|
|
})
|
|
})
|
|
);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
|
|
? "m"
|
|
: "ft"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
{shape === pond.BinShape.BIN_SHAPE_HOPPER_BOTTOM && (
|
|
<React.Fragment>
|
|
<Grid size={{ xs: 4 }}>
|
|
<TextField
|
|
label="Hopper Angle"
|
|
type="number"
|
|
value={hopperAngle}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event.target.value;
|
|
let angle =
|
|
value === ""
|
|
? undefined
|
|
: isNaN(parseFloat(value))
|
|
? 0
|
|
: parseFloat(value);
|
|
let d = form.specs?.diameterCm ?? 0; //get the diameter
|
|
//calculate the new cone height for the roof angle
|
|
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
|
|
|
|
let newHopperHeight =
|
|
user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
|
|
? (coneCM / 30.48).toFixed(2)
|
|
: (coneCM / 100).toFixed(2);
|
|
|
|
let ext = cloneDeep(formExtension);
|
|
let s = isNaN(parseFloat(sidewallHeight))
|
|
? 0
|
|
: parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(topConeHeight))
|
|
? 0
|
|
: parseFloat(topConeHeight);
|
|
let h = isNaN(parseFloat(newHopperHeight))
|
|
? 0
|
|
: parseFloat(newHopperHeight);
|
|
|
|
//trigger the advanced calculation for capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
ext.hopperHeight = newHopperHeight;
|
|
ext.height = (s + t + h).toFixed(2);
|
|
|
|
setFormExtension(ext);
|
|
//update the form with the new specs
|
|
setModelID(0);
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
hopperAngle: angle ?? 0,
|
|
hopperHeight: coneCM
|
|
})
|
|
})
|
|
);
|
|
//need to update the total height with the new height calculated from the angle
|
|
}}
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">°</InputAdornment>
|
|
}}
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
<Grid size={{ xs: 8 }}>
|
|
<TextField
|
|
label="Hopper Height"
|
|
type="number"
|
|
value={hopperHeight}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueM = value;
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
valueM = (Number(value) * 0.3048).toFixed(2);
|
|
}
|
|
|
|
let hopperHeightCM =
|
|
valueM === ""
|
|
? undefined
|
|
: isNaN(parseFloat(valueM))
|
|
? 0
|
|
: parseFloat(valueM) * 100;
|
|
|
|
//Calculate the hopper angle
|
|
let d = form.specs?.diameterCm ?? 0; //get the diameter
|
|
let angle = RadiansToDegrees(
|
|
Math.atan((hopperHeightCM ?? 0) / (d / 2))
|
|
); //('arcTan(opposite / adjacent))' and convert to degrees
|
|
angle = Math.round(angle * 100) / 100; // round to 2 digits
|
|
|
|
setModelID(0);
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
hopperHeight: hopperHeightCM,
|
|
hopperAngle: angle
|
|
})
|
|
})
|
|
);
|
|
let ext = cloneDeep(formExtension);
|
|
let s = isNaN(parseFloat(sidewallHeight))
|
|
? 0
|
|
: parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(topConeHeight))
|
|
? 0
|
|
: parseFloat(topConeHeight);
|
|
let h = isNaN(parseFloat(value)) ? 0 : parseFloat(value);
|
|
|
|
//trigger the advanced calculation for capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s, h);
|
|
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
ext.hopperHeight = value;
|
|
ext.height = (s + t + h).toFixed(2);
|
|
|
|
setFormExtension({ ...ext });
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
|
|
? "m"
|
|
: "ft"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
</React.Fragment>
|
|
)}
|
|
</Grid>
|
|
</Box>
|
|
</Collapse>
|
|
|
|
<Box marginTop={1}>
|
|
<TextField
|
|
label="Diameter"
|
|
type="number"
|
|
required
|
|
value={diameter}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let h = height;
|
|
let valueM = value;
|
|
let hM = height;
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
|
|
valueM = (Number(value) * 0.3048).toFixed(2);
|
|
hM = (Number(h) * 0.3048).toFixed(2);
|
|
}
|
|
|
|
let diameterCM =
|
|
valueM === ""
|
|
? undefined
|
|
: isNaN(parseFloat(valueM))
|
|
? 0
|
|
: parseFloat(valueM) * 100;
|
|
|
|
//adjust the angles using the new diameter
|
|
let d = diameterCM ?? 0; //get the diameter
|
|
//Calculate the hopper angle
|
|
let hopperAngle = RadiansToDegrees(
|
|
Math.atan((form.specs?.advancedDimensions?.hopperHeight ?? 0) / (d / 2))
|
|
); //('arcTan(opposite / adjacent))' and convert to degrees
|
|
hopperAngle = Math.round(hopperAngle * 100) / 100; // round to 2 digits
|
|
//Calculate the roof angle
|
|
let roofAngle = RadiansToDegrees(
|
|
Math.atan((form.specs?.advancedDimensions?.topConeHeight ?? 0) / (d / 2))
|
|
); //('arcTan(opposite / adjacent))' and convert to degrees
|
|
roofAngle = Math.round(roofAngle * 100) / 100; // round to 2 digits
|
|
|
|
setModelID(0);
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
modelId: 0,
|
|
diameterCm: diameterCM,
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
hopperAngle: hopperAngle,
|
|
roofAngle: roofAngle
|
|
})
|
|
})
|
|
);
|
|
|
|
let ext = cloneDeep(formExtension);
|
|
ext.diameter = value;
|
|
|
|
if (advanced) {
|
|
//advanced capacity calc using the 3 sections
|
|
} else {
|
|
//basic capcity calc using only height and diameter
|
|
setInputCapacity(getBushels(Number(hM), Number(valueM)).toFixed(0));
|
|
}
|
|
setFormExtension({ ...ext });
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
<Box marginTop={1}>
|
|
<TextField
|
|
label="Capacity"
|
|
type="number"
|
|
required
|
|
value={inputCapacity}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
value = isNaN(parseFloat(value)) ? "" : parseFloat(value) >= 0 ? value : "";
|
|
setInputCapacity(value);
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
bushelCapacity:
|
|
value === "" ? undefined : isNaN(parseFloat(value)) ? 0 : parseFloat(value)
|
|
})
|
|
);
|
|
updateFormExtension("capacity", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">bushels</InputAdornment>
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
|
|
<Box marginTop={2}>
|
|
<FormControl component="fieldset" variant="outlined" fullWidth disabled={!canEdit}>
|
|
{/* <OutlinedBox label={<FormLabel component="legend">Shape</FormLabel>} padding={1}> */}
|
|
<FormLabel component="legend">Shape</FormLabel>
|
|
<RadioGroup
|
|
aria-label="bin shape"
|
|
name="binShape"
|
|
value={shape}
|
|
onChange={(_, value) => {
|
|
//if user changed from hopper to flat bottom and they are using advanced dimensions
|
|
if (parseInt(value) === pond.BinShape.BIN_SHAPE_FLAT_BOTTOM && advanced) {
|
|
let ext = cloneDeep(formExtension);
|
|
//set the hopper measurements (height/angle) to be 0
|
|
ext.hopperHeight = "0";
|
|
|
|
//re-calc the total height for the extension, the settings will be done upon saving the bin
|
|
let s = isNaN(parseFloat(sidewallHeight)) ? 0 : parseFloat(sidewallHeight);
|
|
let t = isNaN(parseFloat(topConeHeight)) ? 0 : parseFloat(topConeHeight);
|
|
ext.height = (s + t).toFixed(2);
|
|
|
|
//re-calc the capacity
|
|
let diameter = isNaN(parseFloat(ext.diameter))
|
|
? 0
|
|
: parseFloat(ext.diameter);
|
|
let bushelCap = advancedGrainCalc(diameter, t, s);
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
|
|
//update extension with new values
|
|
setFormExtension(ext);
|
|
}
|
|
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
shape: parseInt(value),
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
...form.specs?.advancedDimensions,
|
|
hopperAngle: 0,
|
|
hopperHeight: 0
|
|
})
|
|
})
|
|
);
|
|
}}>
|
|
{GetBinShapeDescribers()
|
|
.filter(s => s.key !== pond.BinShape.BIN_SHAPE_UNKNOWN)
|
|
.map(s => (
|
|
<FormControlLabel
|
|
key={s.key}
|
|
value={s.key}
|
|
control={<Radio />}
|
|
label={s.label}
|
|
/>
|
|
))}
|
|
</RadioGroup>
|
|
{/* </OutlinedBox> */}
|
|
<FormHelperText>Aeration effectiveness varies with bin shape</FormHelperText>
|
|
</FormControl>
|
|
</Box>
|
|
</Box>
|
|
) : (
|
|
<Box>
|
|
<BinSelector
|
|
vertical
|
|
optionsChanged={options => {
|
|
let ops: Option[] = [];
|
|
options.forEach(o => {
|
|
ops.push({
|
|
label: o.Manufacturer + " - " + o.Model + " - " + o.Bottom,
|
|
value: o
|
|
});
|
|
});
|
|
setBinModelOptions(ops);
|
|
}}
|
|
/>
|
|
<Box marginTop={1}>
|
|
<SearchSelect
|
|
options={binModelOps}
|
|
selected={selectedBinModel}
|
|
label="Select Bin Model"
|
|
changeSelection={selected => {
|
|
if (selected) {
|
|
setSelectedBinModel(selected);
|
|
let jsonBin = selected.value;
|
|
setModelID(jsonBin.ID);
|
|
let bushelCap = 0;
|
|
//need to set all the form (form is a bin settings object) data according to the bin that was selected
|
|
if (form.specs) {
|
|
//json bins lengths are in feet so for the data stored in the backend need to convert it to cm
|
|
|
|
let hopperHeightFt = TriangleOppositeLength(
|
|
jsonBin.Diameter / 2,
|
|
jsonBin.HopperAngle
|
|
);
|
|
|
|
// bushelCap = advancedGrainCalc(jsonBin.Diameter, jsonBin.Peak - jsonBin.Sidewall, jsonBin.Sidewall, hopperHeightFt)
|
|
// console.log(bushelCap)
|
|
|
|
let topConeBushels =
|
|
ConeVolume(jsonBin.Diameter / 2, jsonBin.Peak - jsonBin.Sidewall) *
|
|
conversionConstantFT;
|
|
let cylinderBushels =
|
|
CylinderVolume(jsonBin.Diameter / 2, jsonBin.Sidewall) *
|
|
conversionConstantFT;
|
|
let hopperBushels =
|
|
ConeVolume(jsonBin.Diameter / 2, hopperHeightFt) * conversionConstantFT;
|
|
bushelCap = topConeBushels + cylinderBushels + hopperBushels;
|
|
|
|
//set all the values that would be stored in the backend in bin settings
|
|
updateForm(
|
|
"specs",
|
|
pond.BinSpecs.create({
|
|
...form.specs,
|
|
bushelCapacity: Math.round(bushelCap),
|
|
heightCm:
|
|
(jsonBin.Peak +
|
|
TriangleOppositeLength(jsonBin.Diameter / 2, jsonBin.HopperAngle)) *
|
|
30.48,
|
|
diameterCm: jsonBin.Diameter * 30.48,
|
|
advancedDimensions: pond.BinAdvancedDimensions.create({
|
|
sidewallHeight: jsonBin.Sidewall * 30.48,
|
|
topConeHeight: (jsonBin.Peak - jsonBin.Sidewall) * 30.48,
|
|
hopperHeight: hopperHeightFt * 30.48,
|
|
roofAngle: jsonBin.RoofAngle,
|
|
hopperAngle: jsonBin.HopperAngle
|
|
}),
|
|
modelId: jsonBin.ID,
|
|
shape:
|
|
jsonBin.Bottom === "Hopper Bottom"
|
|
? pond.BinShape.BIN_SHAPE_HOPPER_BOTTOM
|
|
: pond.BinShape.BIN_SHAPE_FLAT_BOTTOM
|
|
})
|
|
);
|
|
}
|
|
|
|
//set the form extensions values that are displayed to the user
|
|
let hopperHeight = TriangleOppositeLength(
|
|
jsonBin.Diameter / 2,
|
|
jsonBin.HopperAngle
|
|
);
|
|
|
|
let ext = cloneDeep(formExtension);
|
|
ext.capacity = bushelCap.toFixed(0);
|
|
setInputCapacity(bushelCap.toFixed(0));
|
|
|
|
//the dimensions for the correct unit defined by user settings
|
|
let userHeight = jsonBin.Peak + hopperHeight;
|
|
let userTopCone = jsonBin.Peak - jsonBin.Sidewall;
|
|
let userSidewall = jsonBin.Sidewall;
|
|
let userHopperHeight = hopperHeight;
|
|
let userDiameter = jsonBin.Diameter;
|
|
//since the jsone data is in feet just check if we need to make it in meters
|
|
if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
|
userHeight = userHeight / 3.281;
|
|
userDiameter = userDiameter / 3.281;
|
|
userTopCone = userTopCone / 3.281;
|
|
userSidewall = userSidewall / 3.281;
|
|
userHopperHeight = userHopperHeight / 3.281;
|
|
}
|
|
ext.height = userHeight.toFixed(2);
|
|
ext.diameter = userDiameter.toFixed(2);
|
|
ext.topConeHeight = userTopCone.toFixed(2);
|
|
ext.sidewallHeight = userSidewall.toFixed(2);
|
|
ext.hopperHeight = userHopperHeight.toFixed(2);
|
|
setFormExtension({ ...ext });
|
|
}
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
{binYardForm()}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const targetForm = () => {
|
|
let highTemp = formExtension.highTemp;
|
|
let lowTemp = formExtension.lowTemp;
|
|
let tempTarget = formExtension.tempTarget;
|
|
return (
|
|
<Box>
|
|
<Typography variant="subtitle2" align="center" color="primary" gutterBottom>
|
|
Grain Targets
|
|
</Typography>
|
|
<Typography variant="body1" align="center" gutterBottom>
|
|
Temperature
|
|
</Typography>
|
|
<Box marginTop={1}>
|
|
<TextField
|
|
label="Temperature Target"
|
|
type="number"
|
|
required
|
|
error={
|
|
+formExtension.lowTemp > +formExtension.tempTarget ||
|
|
+formExtension.highTemp < +formExtension.tempTarget
|
|
}
|
|
helperText={
|
|
+formExtension.lowTemp >= +formExtension.highTemp
|
|
? "Target must be between the lower and upper limits"
|
|
: ""
|
|
}
|
|
value={tempTarget}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueC = value;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
|
|
}
|
|
|
|
if (form.inventory) {
|
|
form.inventory.targetTemperature = +valueC;
|
|
}
|
|
updateFormExtension("tempTarget", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
? "F"
|
|
: "C"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
<Box marginTop={1}>
|
|
<Grid container spacing={1}>
|
|
<Grid size={{ xs: 6 }}>
|
|
<TextField
|
|
label="Lower Limit"
|
|
type="number"
|
|
required
|
|
error={+formExtension.lowTemp >= +formExtension.highTemp}
|
|
helperText={
|
|
+formExtension.lowTemp >= +formExtension.highTemp
|
|
? "Lower Limit must be less than the Upper Limit"
|
|
: ""
|
|
}
|
|
value={lowTemp}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueC = value;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
|
|
}
|
|
setLowTempC(+valueC);
|
|
updateFormExtension("lowTemp", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
? "F"
|
|
: "C"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
<Grid size={{ xs: 6 }}>
|
|
<TextField
|
|
label="Upper Limit"
|
|
type="number"
|
|
required
|
|
error={+formExtension.highTemp <= +formExtension.lowTemp}
|
|
helperText={
|
|
+formExtension.highTemp <= +formExtension.lowTemp
|
|
? "Upper Limit must be greater than the Lower Limit"
|
|
: ""
|
|
}
|
|
value={highTemp}
|
|
disabled={!canEdit}
|
|
onChange={event => {
|
|
let value = event?.target.value;
|
|
let valueC = value;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
|
|
}
|
|
setHighTempC(+valueC);
|
|
updateFormExtension("highTemp", value);
|
|
}}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
? "F"
|
|
: "C"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
fullWidth
|
|
variant="outlined"
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
<Box marginTop={1}>
|
|
<Typography variant="body1" align="center" gutterBottom>
|
|
Moisture
|
|
</Typography>
|
|
{isCustomInventory && (
|
|
<Typography variant="caption">You are using a custom Grain type, the drying estimate may not be accurate</Typography>
|
|
)}
|
|
<Box marginTop={1}>
|
|
<TextField
|
|
label="Target EMC"
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">% EMC</InputAdornment>
|
|
}}
|
|
fullWidth
|
|
value={tMoistureString}
|
|
disabled={!canEdit}
|
|
variant="outlined"
|
|
className={classes.bottomSpacing}
|
|
error={isNaN(+tMoistureString) && tMoistureString !== ""}
|
|
onChange={event => {
|
|
setTMoistureString(event.target.value);
|
|
if (!isNaN(+event.target.value)) {
|
|
setValidTarget(true);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
targetMoisture: +event.target.value
|
|
})
|
|
);
|
|
} else {
|
|
setValidTarget(false);
|
|
}
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
<Grid container spacing={1}>
|
|
<Grid size={{ xs: 6 }}>
|
|
<TextField
|
|
label="Initial"
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">% EMC</InputAdornment>
|
|
}}
|
|
fullWidth
|
|
value={inMoistureString}
|
|
variant="outlined"
|
|
disabled={!canEdit}
|
|
className={classes.bottomSpacing}
|
|
error={isNaN(+inMoistureString) && inMoistureString !== ""}
|
|
onChange={event => {
|
|
setInMoistureString(event.target.value);
|
|
if (!isNaN(+event.target.value)) {
|
|
setValidInitial(true);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
initialMoisture: +event.target.value
|
|
})
|
|
);
|
|
} else {
|
|
setValidInitial(false);
|
|
}
|
|
}}
|
|
/>
|
|
</Grid>
|
|
<Grid size={{ xs: 6 }} >
|
|
<TextField
|
|
label="Acceptable Deviation"
|
|
fullWidth
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">% EMC</InputAdornment>
|
|
}}
|
|
value={moistureTargetDeviation}
|
|
variant="outlined"
|
|
disabled={!canEdit}
|
|
className={classes.bottomSpacing}
|
|
error={isNaN(+moistureTargetDeviation) && moistureTargetDeviation !== ""}
|
|
onChange={event => {
|
|
setMoistureTargetDeviation(event.target.value);
|
|
if (!isNaN(+event.target.value)) {
|
|
setValidDeviation(true);
|
|
updateForm(
|
|
"inventory",
|
|
pond.BinInventory.create({
|
|
...form.inventory,
|
|
moistureTargetDeviation: +event.target.value
|
|
})
|
|
);
|
|
} else {
|
|
setValidDeviation(false);
|
|
}
|
|
}}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const stepContent = (step: number) => {
|
|
switch (step) {
|
|
case 1:
|
|
return fanPicker();
|
|
case 2:
|
|
return inventoryForm();
|
|
case 3:
|
|
return targetForm();
|
|
default:
|
|
return generalForm();
|
|
}
|
|
};
|
|
|
|
const content = () => {
|
|
return (
|
|
<DialogContent style={{ padding: 0 }}>
|
|
<Box paddingX={2} paddingY={1}>
|
|
{!isMobile && mode === "add" && stepper()}
|
|
{stepContent(activeStep)}
|
|
</Box>
|
|
</DialogContent>
|
|
);
|
|
};
|
|
|
|
const nextButton = () => {
|
|
if (activeStep < 3) {
|
|
return (
|
|
<Button
|
|
size="small"
|
|
aria-label="next"
|
|
onClick={stepNext}
|
|
disabled={activeStep === steps.length - 1 || !stepComplete(activeStep)}>
|
|
Next
|
|
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
|
|
</Button>
|
|
);
|
|
}
|
|
return (
|
|
<Button size="small" aria-label="next" onClick={addBin}>
|
|
Create
|
|
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
const actions = () => {
|
|
if (mode === "add" && isMobile) {
|
|
return (
|
|
<MobileStepper
|
|
variant="dots"
|
|
steps={steps.length}
|
|
position="static"
|
|
activeStep={activeStep}
|
|
nextButton={nextButton()}
|
|
backButton={
|
|
<Button size="small" aria-label="back" onClick={stepBack} disabled={activeStep === 0}>
|
|
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
|
|
Back
|
|
</Button>
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<DialogActions>
|
|
<Grid container justifyContent="space-between" direction="row" width="100%">
|
|
<Grid size={{ xs: 5 }}>
|
|
{mode === "add" ? (
|
|
<Button
|
|
disabled={activeStep === 0}
|
|
onClick={stepBack}
|
|
color="primary"
|
|
aria-label="Back">
|
|
Back
|
|
</Button>
|
|
) : (
|
|
canEdit && <DeleteButton onClick={() => setRemoveIsOpen(true)}>Delete</DeleteButton>
|
|
)}
|
|
</Grid>
|
|
<Grid size={{ xs: 7 }} container justifyContent="flex-end">
|
|
{mode === "add" ? (
|
|
<React.Fragment>
|
|
{!isMobile && (
|
|
<Button onClick={() => close()} color="primary" aria-label="Cancel">
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
|
|
{activeStep === steps.length - 1 ? (
|
|
<Button
|
|
color="primary"
|
|
onClick={addBin}
|
|
disabled={!isFormValid()}
|
|
aria-label="Submit">
|
|
Submit
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
color="primary"
|
|
onClick={stepNext}
|
|
disabled={activeStep === steps.length - 1 || !stepComplete(activeStep)}
|
|
aria-label="Next">
|
|
Next
|
|
</Button>
|
|
)}
|
|
</React.Fragment>
|
|
) : (
|
|
!isMobile && (
|
|
<React.Fragment>
|
|
<Button onClick={() => close()} color="primary" aria-label="Cancel">
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={updateBin}
|
|
color="primary"
|
|
disabled={!canEdit || !isFormValid()}
|
|
aria-label="Submit">
|
|
Submit
|
|
</Button>
|
|
</React.Fragment>
|
|
)
|
|
)}
|
|
</Grid>
|
|
</Grid>
|
|
</DialogActions>
|
|
);
|
|
};
|
|
|
|
const removeConfirmation = () => {
|
|
return (
|
|
<ResponsiveDialog
|
|
fullScreen={false}
|
|
open={removeIsOpen}
|
|
onClose={() => setRemoveIsOpen(false)}>
|
|
<DialogTitle>Delete {bin?.name() ?? "Bin"}?</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText>
|
|
Anyone that has access to {bin?.name() ?? "this bin"} will no longer be able to see it.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setRemoveIsOpen(false)} color="primary">
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={removeBin} color="primary">
|
|
Delete
|
|
</Button>
|
|
</DialogActions>
|
|
</ResponsiveDialog>
|
|
);
|
|
};
|
|
|
|
if ((!open || !initialized) && !removeIsOpen) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<ResponsiveDialog
|
|
maxWidth={"xs"}
|
|
fullWidth
|
|
fullScreen={isMobile}
|
|
open={open}
|
|
scroll="paper"
|
|
onClose={() => {
|
|
onClose();
|
|
setSelectedBinYard(null);
|
|
}}>
|
|
{title()}
|
|
{content()}
|
|
{actions()}
|
|
{removeConfirmation()}
|
|
{grainInventoryController()}
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|