frontend/src/contract/contractSettings.tsx

660 lines
21 KiB
TypeScript

import {
Box,
Button,
DialogActions,
DialogContent,
DialogTitle,
FormControlLabel,
Grid2 as Grid,
InputAdornment,
Step,
StepLabel,
Stepper,
Switch,
Tab,
Tabs,
TextField,
Typography
} from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect from "common/SearchSelect";
import GrainDescriber from "grain/GrainDescriber";
import { Contract } from "models/Contract";
import moment from "moment";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useTaskAPI, useContractAPI } from "providers";
import React, { useEffect, useState } from "react";
import { getGrainUnit } from "utils";
interface Props {
open: boolean;
close: () => void;
contract?: Contract;
//if we do other types of contracts (not grain) the default type should be an input prop
}
interface InputLabels {
commodityLabel: string;
sizeLabel: string;
deliveryLabel: string;
conversionLabel: string;
adornment: string;
}
export default function ContractSettings(props: Props) {
const { open, close, contract } = props;
const contractAPI = useContractAPI();
const [name, setName] = useState("");
const [contractID, setContractID] = useState(""); //id
const [holder, setHolder] = useState(""); //holder
const [deliveryStart, setDeliveryStart] = useState(moment().format("YYYY-MM-DD")); //deliverystart
const [deliveryEnd, setDeliveryEnd] = useState(moment().format("YYYY-MM-DD")); //deliveryend
const [contractType, setContractType] = useState<pond.ContractType>(1); //contracttype -- defaults to grain
//const [selectedContractType, setSelectedContractType] = useState<Option | null>(null);
const [customCommodity, setCustomCommodity] = useState(""); //customcommodity
const [contractSize, setContractSize] = useState("0"); //contractsize
const [deliveredAmount, setDeliveredAmount] = useState("0"); //deliveredamount
const [contractValue, setContractValue] = useState("0"); //contractvalue
const [commodity, setCommodity] = useState(0); //commodity
const [useCustom, setUseCustom] = useState(false);
const [currentTab, setCurrentTab] = useState(0);
const [contractDate, setContractDate] = useState(moment().format("YYYY-MM-DD"));
const [deleteWindow, setDeleteWindow] = useState(false);
const [createDeliveryTask, setCreateDeliveryTask] = useState(false);
const [conversionValue, setConversionValue] = useState("1");
const [labels, setLabels] = useState<InputLabels>({
sizeLabel: "Size",
commodityLabel: "Commodity",
conversionLabel: "Primary units per Secondary Unit",
deliveryLabel: "Delivered",
adornment: ""
});
const taskAPI = useTaskAPI();
const [{ user }] = useGlobalState();
const closeSettings = () => {
close();
};
useEffect(() => {
if (contract) {
setName(contract.name());
setContractID(contract.settings.contractId);
setHolder(contract.settings.contractHolder);
setDeliveryStart(contract.settings.deliveryWindow?.startDate ?? "");
setDeliveryEnd(contract.settings.deliveryWindow?.endDate ?? "");
setContractType(contract.settings.type);
setCustomCommodity(contract.settings.customCommodity);
setContractSize(contract.sizeInPreferredUnit().toFixed(2));
setDeliveredAmount(contract.deliveredInPreferredUnit().toFixed(2));
setContractValue(contract.settings.totalValue.toString());
setCommodity(contract.settings.commodity);
setContractDate(contract.settings.contractDate);
setConversionValue(
contract.settings.conversionValue > 0 ? contract.settings.conversionValue.toString() : "1"
);
setUseCustom(contract.isCustom());
}
}, [contract]);
useEffect(() => {
switch (contractType) {
case pond.ContractType.CONTRACT_TYPE_GRAIN:
setLabels({
sizeLabel: "Total Grain on Contract",
commodityLabel: "Select Grain Type",
conversionLabel: "Bushels Per Tonne",
deliveryLabel: "Grain Delivered",
adornment: getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu"
});
break;
default:
setLabels({
sizeLabel: "Size",
commodityLabel: "Commodity",
conversionLabel: "Primary units per Secondary Unit",
deliveryLabel: "Delivered",
adornment: ""
});
break;
}
}, [contractType]);
const createTask = (contractKey: string) => {
let task: pond.TaskSettings = pond.TaskSettings.create({
userId: user.id(),
title: name + " Delivery Window",
start: deliveryStart,
startTime: "00:00",
end: deliveryEnd,
endTime: "00:00",
objectKey: contractKey,
description: "The window for delivering grain for the " + name + " contract",
worker: user.id(),
complete: false,
colour: "22194D"
});
taskAPI
.addTask(task)
.then(resp => {
console.log("Task Created");
})
.catch(err => {
console.log("error creating task");
});
};
const submit = () => {
const conVal = isNaN(parseFloat(conversionValue)) ? 1 : parseFloat(conversionValue);
const deliveredVal = isNaN(parseFloat(deliveredAmount))
? 0
: Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal));
const sizeVal = isNaN(parseFloat(contractSize))
? 0
: Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal));
if (contract) {
let settings: pond.ContractSettings = contract.settings;
settings.commodity = commodity;
settings.contractHolder = holder;
settings.contractId = contractID;
settings.customCommodity = customCommodity;
settings.delivered = deliveredVal;
settings.deliveryWindow = pond.ContractDelivery.create({
startDate: deliveryStart,
endDate: deliveryEnd
});
settings.conversionValue = conVal;
settings.size = sizeVal;
settings.totalValue = isNaN(parseFloat(contractValue)) ? 0 : parseFloat(contractValue);
settings.type = contractType;
settings.contractDate = contractDate;
contractAPI
.updateContract(contract.key(), name, settings)
.then(resp => {
console.log("Updated Contract");
close();
})
.catch(err => {
console.log("Error updating contract");
});
} else {
let settings: pond.ContractSettings = pond.ContractSettings.create();
settings.commodity = commodity;
settings.contractHolder = holder;
settings.contractId = contractID;
settings.customCommodity = customCommodity;
//settings.commoditySubtype = variant;
settings.delivered = deliveredVal;
settings.deliveryWindow = pond.ContractDelivery.create({
startDate: deliveryStart,
endDate: deliveryEnd
});
settings.size = sizeVal;
settings.totalValue = isNaN(parseFloat(contractValue)) ? 0 : parseFloat(contractValue);
settings.type = contractType;
settings.conversionValue = conVal;
settings.contractDate = contractDate;
contractAPI
.addContract(name, settings)
.then(resp => {
if (createDeliveryTask) {
createTask(resp.data.key);
}
close();
})
.catch(err => {
console.log("Error adding contract");
});
}
};
const deleteContract = () => {
if (contract) {
contractAPI
.removeContract(contract.key())
.then(resp => {
console.log("contract deleted");
close();
})
.catch(err => {
console.log("failed to delete contract");
});
}
};
const deleteConfirmation = () => {
return (
<ResponsiveDialog
open={deleteWindow}
onClose={() => {
setDeleteWindow(false);
}}>
<DialogTitle>Delete Contract</DialogTitle>
<DialogContent>Are you sure you want to delete this contract</DialogContent>
<DialogActions>
<Button
variant="contained"
color="primary"
onClick={() => {
setDeleteWindow(false);
}}
style={{ marginRight: 10 }}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
style={{ backgroundColor: "red" }}
onClick={deleteContract}>
Delete
</Button>
</DialogActions>
</ResponsiveDialog>
);
};
const stepValid = () => {
let valid = true;
if (currentTab === 0) {
if (name === "" || holder === "" || contractID === "") {
valid = false;
}
} else if (currentTab === 1) {
if (contractType === pond.ContractType.CONTRACT_TYPE_UNKNOWN) {
valid = false;
} else if (
commodity === 0 ||
(commodity === Contract.customValue(contractType) &&
(customCommodity === "" || conversionValue === ""))
) {
valid = false;
}
}
return valid;
};
const general = () => {
return (
<React.Fragment>
<Typography style={{ fontSize: 13, margin: 20 }}>
General information about your contract such as: name, reference number, contract holder
and delivery window will help organize your contracts for easy reference and deliveries.
All fields marked with * are required but all fields are recommended
</Typography>
<TextField
fullWidth
margin="dense"
value={name}
InputLabelProps={{ shrink: true }}
onChange={e => {
setName(e.target.value);
}}
label={"*Contract Name"}
/>
<TextField
fullWidth
margin="dense"
type="date"
label="Contract Signing Date"
value={contractDate}
InputLabelProps={{ shrink: true }}
onChange={e => setContractDate(e.target.value)}
/>
<TextField
fullWidth
margin="dense"
value={contractID}
InputLabelProps={{ shrink: true }}
onChange={e => {
setContractID(e.target.value);
}}
label={"*Reference #"}
/>
<TextField
fullWidth
margin="dense"
value={holder}
InputLabelProps={{ shrink: true }}
onChange={e => {
setHolder(e.target.value);
}}
label={"*Contract Holder"}
/>
<Box border={"2px solid grey"} borderRadius={5} padding={2} marginTop={2} marginBottom={2}>
<Typography>Delivery Window</Typography>
<TextField
style={{ width: "45%" }}
margin="dense"
type="date"
label="Start"
value={deliveryStart}
InputLabelProps={{ shrink: true }}
onChange={e => setDeliveryStart(e.target.value)}
/>
<TextField
style={{ width: "45%", marginLeft: "10%" }}
margin="dense"
type="date"
label="End"
value={deliveryEnd}
InputLabelProps={{ shrink: true }}
onChange={e => setDeliveryEnd(e.target.value)}
/>
</Box>
{!contract && (
<FormControlLabel
control={
<Switch
checked={createDeliveryTask}
onChange={(_, checked) => {
setCreateDeliveryTask(checked);
}}
name="Create Task For Delivery Window"
/>
}
//disabled={!canEdit}
label="Create Task For Delivery Window"
labelPlacement="end"
/>
)}
<TextField
fullWidth
margin="dense"
type="number"
value={contractValue}
InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>
}}
error={isNaN(parseFloat(contractValue))}
helperText={isNaN(parseFloat(contractValue)) ? "Must be a valid number" : ""}
InputLabelProps={{ shrink: true }}
onChange={e => {
setContractValue(e.target.value);
}}
label={"Contract Value"}
/>
</React.Fragment>
);
};
const details = () => {
return (
<React.Fragment>
<Box>
<Typography
style={{ fontSize: 13, paddingLeft: 5, paddingRight: 5, margin: 20, marginBottom: 0 }}>
Select Grain Type, Total Grain on Contract (bu), and Grain Delivered. These settings can
be changed at anytime. If your grain type is not listed use the Custom toggle.
</Typography>
<Typography
style={{
fontSize: 13,
padding: 5,
border: "1px solid grey",
borderRadius: 5,
margin: 20
}}>
Note: When delivering grain on contract from a field or a bin the Grain Types must match
</Typography>
</Box>
<Grid
container
direction="row"
alignContent="center"
alignItems="center"
style={{ marginTop: 10 }}>
<Grid size={8}>
<SearchSelect
label="Contract Type*"
selected={Contract.toTypeOption(contractType)}
changeSelection={option => {
if (option) {
setContractType(option.value);
}
}}
options={Contract.typeOptions()}
/>
</Grid>
<Grid size={4}>
<FormControlLabel
control={
<Switch
checked={useCustom}
onChange={(_, checked) => {
setUseCustom(checked);
setCustomCommodity("");
if (checked) {
setCommodity(Contract.customValue(contractType));
} else {
setCommodity(0);
}
}}
name="Custom"
/>
}
//disabled={!canEdit}
label="Custom"
labelPlacement="start"
/>
</Grid>
</Grid>
{!useCustom ? (
<SearchSelect
style={{ marginTop: 10 }}
label={labels.commodityLabel + "*"}
selected={Contract.toCommodityOption(contractType, commodity)}
changeSelection={option => {
if (option) {
let c = 0;
let conversion = 1;
if (contractType) {
switch (contractType) {
case pond.ContractType.CONTRACT_TYPE_GRAIN:
c = pond.Grain[option.value as keyof typeof pond.Grain];
conversion = GrainDescriber(c).bushelsPerTonne;
break;
default:
c = option.value;
}
}
setCommodity(c);
setConversionValue(conversion.toFixed(2));
}
}}
options={Contract.commodityOptionsForType(contractType)}
/>
) : (
<React.Fragment>
<TextField
fullWidth
margin="dense"
label="Custom*"
value={customCommodity}
InputLabelProps={{ shrink: true }}
onChange={e => setCustomCommodity(e.target.value)}
/>
<TextField
fullWidth
margin="dense"
type="number"
label={labels.conversionLabel}
value={conversionValue}
InputLabelProps={{ shrink: true }}
error={isNaN(parseFloat(conversionValue))}
helperText={isNaN(parseFloat(conversionValue)) ? "Must be a valid number" : ""}
onChange={e => setConversionValue(e.target.value)}
/>
</React.Fragment>
)}
{/* <TextField
fullWidth
margin="dense"
type="variant"
label="Variant"
value={variant}
InputLabelProps={{ shrink: true }}
onChange={e => setVariant(e.target.value)}
/> */}
<TextField
fullWidth
margin="dense"
type="number"
InputProps={{
endAdornment: <InputAdornment position="end">{labels.adornment}</InputAdornment>
}}
value={contractSize}
error={isNaN(parseFloat(contractSize))}
helperText={isNaN(parseFloat(contractSize)) ? "Must be a valid number" : ""}
InputLabelProps={{ shrink: true }}
onChange={e => {
setContractSize(e.target.value);
}}
label={labels.sizeLabel}
/>
<TextField
fullWidth
margin="dense"
type="number"
InputProps={{
endAdornment: <InputAdornment position="end">{labels.adornment}</InputAdornment>
}}
value={deliveredAmount}
error={isNaN(parseFloat(deliveredAmount))}
helperText={isNaN(parseFloat(deliveredAmount)) ? "Must be a valid number" : ""}
InputLabelProps={{ shrink: true }}
onChange={e => {
setDeliveredAmount(e.target.value);
}}
label={labels.deliveryLabel}
/>
</React.Fragment>
);
};
const stepper = () => {
if (contract === undefined) {
return (
<Stepper activeStep={currentTab}>
<Step key={"general"}>
<StepLabel>General</StepLabel>
</Step>
<Step key={"details"}>
<StepLabel>Commodity</StepLabel>
</Step>
</Stepper>
);
}
return (
<Box display="flex" justifyContent="center" width="100%">
<Tabs
value={currentTab}
onChange={(_, value) => setCurrentTab(value)}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
aria-label="bin tabs"
//classes={{ root: classes.tabs }}
>
<Tab label="General" />
<Tab label="Commodity" />
</Tabs>
</Box>
);
};
const stepperContent = (step: number) => {
switch (step) {
case 1:
return details();
default:
return general();
}
};
return (
<React.Fragment>
<ResponsiveDialog open={open} onClose={closeSettings}>
<DialogTitle>Contract Settings</DialogTitle>
<DialogContent>
{/* <Tabs
value={currentTab}
onChange={(_, value) => setCurrentTab(value)}>
<Tab label="General" />
<Tab label="Commodity" />
</Tabs>
<TabPanelMine value={currentTab} index={0}>
{general()}
</TabPanelMine>
<TabPanelMine value={currentTab} index={1}>
{details()}
</TabPanelMine> */}
{stepper()}
{stepperContent(currentTab)}
</DialogContent>
<DialogActions>
<Grid container direction="row-reverse" justifyContent="space-between">
<Grid>
<Button
variant="contained"
color="primary"
onClick={close}
style={{ marginRight: 10 }}>
Cancel
</Button>
{!contract && currentTab > 0 && (
<Button
variant="contained"
color="primary"
onClick={() => {
setCurrentTab(currentTab - 1);
}}
style={{ marginRight: 10 }}>
Back
</Button>
)}
{(contract || currentTab === 1) && (
<Button
variant="contained"
color="primary"
onClick={submit}
style={{ marginRight: 10 }}
disabled={!stepValid()}>
Submit
</Button>
)}
{!contract && currentTab < 1 && (
<Button
variant="contained"
color="primary"
onClick={() => {
setCurrentTab(currentTab + 1);
}}
style={{ marginRight: 10 }}
disabled={!stepValid()}>
Next
</Button>
)}
</Grid>
{contract && (
<Grid>
<Button
variant="contained"
style={{ backgroundColor: "red" }}
onClick={() => {
setDeleteWindow(true);
}}>
Delete
</Button>
</Grid>
)}
</Grid>
</DialogActions>
</ResponsiveDialog>
{deleteConfirmation()}
</React.Fragment>
);
}