added the contract and contracts and all supporting files
This commit is contained in:
parent
07ccb42905
commit
832bb0f7e5
18 changed files with 2898 additions and 17 deletions
41
src/common/CustomProgressBar.tsx
Normal file
41
src/common/CustomProgressBar.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Box, colors } from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
colour: string;
|
||||||
|
completed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
container: {
|
||||||
|
height: 20,
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: "#e0e0de",
|
||||||
|
borderRadius: 50
|
||||||
|
},
|
||||||
|
filler: {
|
||||||
|
height: "100%",
|
||||||
|
// width: `${completed}%`,
|
||||||
|
// backgroundColor: bgcolor,
|
||||||
|
borderRadius: "inherit",
|
||||||
|
textAlign: "right"
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
padding: 5,
|
||||||
|
color: colors.grey[900],
|
||||||
|
fontWeight: "bold"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function CustomProgressBar(props: Props) {
|
||||||
|
const { colour, completed } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<Box className={classes.container}>
|
||||||
|
<Box className={classes.filler} style={{ width: completed + "%", backgroundColor: colour }}>
|
||||||
|
<span className={classes.label}>{completed}%</span>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
src/common/ImageViewer.tsx
Normal file
52
src/common/ImageViewer.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useFileControllerAPI } from "providers";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
image: pond.FileReference;
|
||||||
|
height?: number | string;
|
||||||
|
width?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageViewer(props: Props) {
|
||||||
|
const { image, height, width } = props;
|
||||||
|
const fileController = useFileControllerAPI();
|
||||||
|
// const [contentType, setContentType] = useState("")
|
||||||
|
const [imageData, setImageData] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//check local storage for the image
|
||||||
|
let imageURL = localStorage.getItem(image.uuid);
|
||||||
|
if (imageURL !== null) {
|
||||||
|
setImageData(imageURL);
|
||||||
|
} else {
|
||||||
|
//if its not there retrieve it
|
||||||
|
fileController
|
||||||
|
.retrieveFile(image.spacesPath)
|
||||||
|
.then(resp => {
|
||||||
|
let url = URL.createObjectURL(resp.data);
|
||||||
|
setImageData(url);
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
//store the file url in base64 in local storage so it doesn't need to be loaded everytime
|
||||||
|
localStorage.setItem(image.uuid, reader.result as string);
|
||||||
|
});
|
||||||
|
reader.readAsDataURL(resp.data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
//console.log("There were errors retrieving the file");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [image, fileController]);
|
||||||
|
|
||||||
|
//get the file from digital ocean by using our backend as a proxy
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<a href={imageData} download={image.fileName}>
|
||||||
|
<img src={imageData} alt={image.fileName} style={{ maxHeight: height, maxWidth: width }} />
|
||||||
|
</a>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
107
src/contract/constractActions.tsx
Normal file
107
src/contract/constractActions.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { Box, Card, Typography, useTheme } from "@mui/material";
|
||||||
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contract: Contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractVisualizer(props: Props) {
|
||||||
|
const { contract } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ height: "100%", padding: 10 }}>
|
||||||
|
<Box height={"15%"}>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 20 }}>
|
||||||
|
{GrainDescriber(contract.grain()).name}
|
||||||
|
</Typography>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||||
|
{contract.deliveredInPreferredUnit().toFixed(2)}/
|
||||||
|
{contract.sizeInPreferredUnit().toFixed(2)} {contract.unit}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<ResponsiveContainer height={"85%"}>
|
||||||
|
<PieChart>
|
||||||
|
{/* <Legend
|
||||||
|
verticalAlign="bottom"
|
||||||
|
content={() => (
|
||||||
|
<Box textAlign="center" marginY={0.5}>
|
||||||
|
<Chip label={contract.commodityLabel()} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
<Pie
|
||||||
|
outerRadius={"100%"}
|
||||||
|
innerRadius="70%"
|
||||||
|
stroke="transparent"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.delivered,
|
||||||
|
fill: contract.colour
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.size - contract.settings.delivered
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
dataKey="value"
|
||||||
|
cx="50%"
|
||||||
|
cy="50%">
|
||||||
|
<Label
|
||||||
|
position="center"
|
||||||
|
fill={theme.palette.text.secondary}
|
||||||
|
fontSize={"2rem"}
|
||||||
|
fontWeight={650}
|
||||||
|
content={props => {
|
||||||
|
const { cx, cy } = props.viewBox as any;
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<Text
|
||||||
|
x={cx}
|
||||||
|
y={cy}
|
||||||
|
dy={-15}
|
||||||
|
textAnchor="middle"
|
||||||
|
verticalAnchor="middle"
|
||||||
|
fill={props.fill}
|
||||||
|
fontWeight={props.fontWeight}
|
||||||
|
fontSize={props.fontSize}>
|
||||||
|
{((contract.settings.delivered / contract.settings.size) * 100).toFixed(1) +
|
||||||
|
"%"}
|
||||||
|
</Text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
position="center"
|
||||||
|
fill={theme.palette.text.secondary}
|
||||||
|
fontSize={"1.5rem"}
|
||||||
|
fontWeight={650}
|
||||||
|
content={props => {
|
||||||
|
const { cx, cy } = props.viewBox as any;
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<Text
|
||||||
|
x={cx}
|
||||||
|
y={cy}
|
||||||
|
dy={15}
|
||||||
|
textAnchor="middle"
|
||||||
|
verticalAnchor="middle"
|
||||||
|
fill={props.fill}
|
||||||
|
fontWeight={props.fontWeight}
|
||||||
|
fontSize={props.fontSize}>
|
||||||
|
Delivered
|
||||||
|
</Text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
219
src/contract/contractActions.tsx
Normal file
219
src/contract/contractActions.tsx
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
lighten,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
Theme,
|
||||||
|
Tooltip
|
||||||
|
} from "@mui/material";
|
||||||
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
|
import MoreIcon from "@mui/icons-material/MoreVert";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
|
||||||
|
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||||
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { Scope } from "models";
|
||||||
|
import ObjectTeams from "teams/ObjectTeams";
|
||||||
|
import RemoveSelfFromObject from "user/RemoveSelfFromObject";
|
||||||
|
import ShareObject from "user/ShareObject";
|
||||||
|
import { blue } from "@mui/material/colors";
|
||||||
|
import RemoveSelfIcon from "@mui/icons-material/ExitToApp";
|
||||||
|
import { Share } from "@mui/icons-material";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import ContractSettings from "./contractSettings";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
shareIcon: {
|
||||||
|
color: blue["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeIcon: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
},
|
||||||
|
blueIcon: {
|
||||||
|
color: blue["500"],
|
||||||
|
"&:hover": {
|
||||||
|
color: blue["600"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
menuButton: {
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
color: lighten(theme.palette.background.default, 0.2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
interface OpenState {
|
||||||
|
users: boolean;
|
||||||
|
teams: boolean;
|
||||||
|
settings: boolean;
|
||||||
|
removeSelf: boolean;
|
||||||
|
share: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contract: Contract;
|
||||||
|
refreshCallback: () => void;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractActions(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { contract, refreshCallback, permissions } = props;
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
const [openState, setOpenState] = useState<OpenState>({
|
||||||
|
users: false,
|
||||||
|
teams: false,
|
||||||
|
settings: false,
|
||||||
|
removeSelf: false,
|
||||||
|
share: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupMenu = () => {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={() => setAnchorEl(null)}
|
||||||
|
keepMounted
|
||||||
|
disableAutoFocusItem>
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_SHARE) && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, share: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
className={classes.menuButton}
|
||||||
|
dense>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Share className={classes.blueIcon} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText secondary="Share" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_USERS) && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, users: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
className={classes.menuButton}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectUsersIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Users" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_USERS) && (
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, teams: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
className={classes.menuButton}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ObjectTeamsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Teams" />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
dense
|
||||||
|
onClick={() => {
|
||||||
|
setOpenState({ ...openState, removeSelf: true });
|
||||||
|
setAnchorEl(null);
|
||||||
|
}}
|
||||||
|
className={classes.menuButton}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<RemoveSelfIcon className={classes.red} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Leave" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
const key = contract.key();
|
||||||
|
const label = contract.name();
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ContractSettings
|
||||||
|
contract={contract}
|
||||||
|
open={openState.settings}
|
||||||
|
close={() => {
|
||||||
|
setOpenState({ ...openState, settings: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ShareObject
|
||||||
|
scope={{ kind: "contract", key: key } as Scope}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.share}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
|
||||||
|
/>
|
||||||
|
<ObjectUsers
|
||||||
|
scope={{ kind: "contract", key: key } as Scope}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.users}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<ObjectTeams
|
||||||
|
scope={{ kind: "contract", key: key } as Scope}
|
||||||
|
label={label}
|
||||||
|
permissions={permissions}
|
||||||
|
isDialogOpen={openState.teams}
|
||||||
|
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
|
||||||
|
refreshCallback={refreshCallback}
|
||||||
|
/>
|
||||||
|
<RemoveSelfFromObject
|
||||||
|
scope={{ kind: "contract", key: key } as Scope}
|
||||||
|
path={"contracts"}
|
||||||
|
label={label}
|
||||||
|
isDialogOpen={openState.removeSelf}
|
||||||
|
closeDialogCallback={() => {
|
||||||
|
setOpenState({ ...openState, removeSelf: false });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
|
<Tooltip title="Settings">
|
||||||
|
<IconButton onClick={() => setOpenState({ ...openState, settings: true })}>
|
||||||
|
<SettingsIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
aria-owns={anchorEl ? "groupMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget)}>
|
||||||
|
<MoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
{dialogs()}
|
||||||
|
{groupMenu()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
509
src/contract/contractList.tsx
Normal file
509
src/contract/contractList.tsx
Normal file
|
|
@ -0,0 +1,509 @@
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Box,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Theme,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
DialogTitle,
|
||||||
|
useTheme,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Divider,
|
||||||
|
Grid2 as Grid,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
colors,
|
||||||
|
lighten
|
||||||
|
} from "@mui/material";
|
||||||
|
import { ExpandMore } from "@mui/icons-material";
|
||||||
|
import CustomProgressBar from "common/CustomProgressBar";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import GrainTransaction from "grain/GrainTransaction";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useContractAPI } from "providers/pond/contractAPI";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
// import { useHistory } from "react-router";
|
||||||
|
import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
|
||||||
|
import { stringToMaterialColour } from "utils";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contracts: Contract[];
|
||||||
|
updateList: (newContractList: Contract[]) => void;
|
||||||
|
contractPermissions: Map<string, pond.Permission[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
contractRow: {
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: lighten(theme.palette.background.default, 0.25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function ContractsList(props: Props) {
|
||||||
|
const { contracts, contractPermissions, updateList } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
//will need map for contracts that have supported grain types
|
||||||
|
const [supportedTypes, setSupportedTypes] = useState<Map<pond.Grain, Contract[]>>(new Map());
|
||||||
|
//will need need map for custom contracts
|
||||||
|
const [customTypes, setCustomTypes] = useState<Map<string, Contract[]>>(new Map());
|
||||||
|
const [openGrainTransactionDialog, setOpenGrainTransactionDialog] = useState(false);
|
||||||
|
const [currentContract, setCurrentContract] = useState<Contract>();
|
||||||
|
// const history = useHistory();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [deleteWindow, setDeleteWindow] = useState(false);
|
||||||
|
const contractAPI = useContractAPI();
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const [contractsDisplay, setContractsDisplay] = useState("open");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//loop through contracts setting them into the appropriate map at the appropriate location
|
||||||
|
let supportedTypes: Map<pond.Grain, Contract[]> = new Map();
|
||||||
|
let customTypes: Map<string, Contract[]> = new Map();
|
||||||
|
|
||||||
|
contracts.forEach(con => {
|
||||||
|
let contract: Contract | undefined;
|
||||||
|
if (contractsDisplay === "open" && con.settings.delivered < con.settings.size) contract = con;
|
||||||
|
if (contractsDisplay === "closed" && con.settings.delivered >= con.settings.size)
|
||||||
|
contract = con;
|
||||||
|
|
||||||
|
if (contract) {
|
||||||
|
if (!contract.isCustom()) {
|
||||||
|
let mapItem = supportedTypes.get(contract.settings.commodity);
|
||||||
|
if (mapItem) {
|
||||||
|
mapItem.push(contract);
|
||||||
|
} else {
|
||||||
|
supportedTypes.set(contract.settings.commodity, [contract]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mapItem = customTypes.get(con.settings.customCommodity);
|
||||||
|
if (mapItem) {
|
||||||
|
mapItem.push(contract);
|
||||||
|
} else {
|
||||||
|
customTypes.set(contract.settings.customCommodity, [contract]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSupportedTypes(supportedTypes);
|
||||||
|
setCustomTypes(customTypes);
|
||||||
|
}, [contracts, contractsDisplay]);
|
||||||
|
|
||||||
|
const goToContract = (contract: Contract) => {
|
||||||
|
let path = "/contracts/" + contract.key();
|
||||||
|
navigate(path, {state: { contract: contract }})
|
||||||
|
// history.replace(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteContract = () => {
|
||||||
|
if (currentContract) {
|
||||||
|
contractAPI
|
||||||
|
.removeContract(currentContract.key())
|
||||||
|
.then(resp => {
|
||||||
|
let c = contracts;
|
||||||
|
contracts.splice(c.indexOf(currentContract), 1);
|
||||||
|
updateList(c);
|
||||||
|
})
|
||||||
|
.catch(err => {});
|
||||||
|
}
|
||||||
|
setDeleteWindow(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayTable = (contracts: Contract[]) => {
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Name</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Contract Date</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Delivery Due</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Size</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Delivered</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Progress</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Remaining</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography style={{ fontSize: 15, fontWeight: 650 }}>Actions</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{contracts.map(contract => {
|
||||||
|
const fillPercent =
|
||||||
|
Math.round((contract.settings.delivered / contract.settings.size) * 100 * 10) / 10;
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={contract.key()}
|
||||||
|
className={classes.contractRow}
|
||||||
|
onClick={() => {
|
||||||
|
goToContract(contract);
|
||||||
|
}}>
|
||||||
|
<TableCell>{contract.name()}</TableCell>
|
||||||
|
<TableCell>{contract.settings.contractDate}</TableCell>
|
||||||
|
<TableCell>{contract.settings.deliveryWindow?.endDate}</TableCell>
|
||||||
|
<TableCell>{contract.sizeInPreferredUnit() + " " + contract.unit}</TableCell>
|
||||||
|
<TableCell>{contract.deliveredInPreferredUnit() + " " + contract.unit}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{/* <progress value={contract.settings.delivered} max={contract.settings.size} style={{height: 10}}/> */}
|
||||||
|
<CustomProgressBar colour={colors.blue[500]} completed={fillPercent} />
|
||||||
|
{/* {((contract.settings.delivered/contract.settings.size)*100).toFixed(1) + "%"} */}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{contract.sizeInPreferredUnit() -
|
||||||
|
contract.deliveredInPreferredUnit() +
|
||||||
|
" " +
|
||||||
|
contract.unit}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
!contractPermissions
|
||||||
|
.get(contract.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setOpenGrainTransactionDialog(true);
|
||||||
|
setCurrentContract(contract);
|
||||||
|
}}>
|
||||||
|
Deliver
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
backgroundColor: contractPermissions
|
||||||
|
.get(contract.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
? "red"
|
||||||
|
: "",
|
||||||
|
marginLeft: 5
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
!contractPermissions
|
||||||
|
.get(contract.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setDeleteWindow(true);
|
||||||
|
setCurrentContract(contract);
|
||||||
|
}}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const circleGraph = (contract: Contract) => {
|
||||||
|
return (
|
||||||
|
<ResponsiveContainer>
|
||||||
|
<PieChart>
|
||||||
|
{/* <Legend
|
||||||
|
verticalAlign="bottom"
|
||||||
|
content={() => (
|
||||||
|
<Box textAlign="center" marginY={0.5}>
|
||||||
|
<Chip label={contract.commodityLabel()} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
<Pie
|
||||||
|
outerRadius={"100%"}
|
||||||
|
innerRadius="70%"
|
||||||
|
stroke="transparent"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.delivered,
|
||||||
|
fill: contract.colour
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.size - contract.settings.delivered
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
dataKey="value"
|
||||||
|
cx="50%"
|
||||||
|
cy="50%">
|
||||||
|
<Label
|
||||||
|
position="center"
|
||||||
|
fill={theme.palette.text.secondary}
|
||||||
|
fontSize={"0.75rem"}
|
||||||
|
fontWeight={650}
|
||||||
|
content={props => {
|
||||||
|
const { cx, cy } = props.viewBox as any;
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<Text
|
||||||
|
x={cx}
|
||||||
|
y={cy}
|
||||||
|
textAnchor="middle"
|
||||||
|
verticalAnchor="middle"
|
||||||
|
fill={props.fill}
|
||||||
|
fontWeight={props.fontWeight}
|
||||||
|
fontSize={props.fontSize}>
|
||||||
|
{((contract.settings.delivered / contract.settings.size) * 100).toFixed(1) +
|
||||||
|
"%"}
|
||||||
|
</Text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mobileView = (contracts: Contract[]) => {
|
||||||
|
return (
|
||||||
|
<List style={{ width: "100%" }}>
|
||||||
|
{contracts.map((c, i) => {
|
||||||
|
const remaining = c.settings.size - c.settings.delivered;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={c.key()}>
|
||||||
|
<ListItem
|
||||||
|
style={{ paddingLeft: 0, paddingRight: 0 }}
|
||||||
|
className={classes.contractRow}
|
||||||
|
onClick={() => {
|
||||||
|
goToContract(c);
|
||||||
|
}}>
|
||||||
|
<Grid container direction="row" spacing={2}>
|
||||||
|
<Grid size={6}>
|
||||||
|
<Typography noWrap style={{ fontWeight: 650 }}>
|
||||||
|
{c.name()}
|
||||||
|
</Typography>
|
||||||
|
<Typography>{c.settings.contractDate}</Typography>
|
||||||
|
<Typography>
|
||||||
|
{remaining > 0 ? remaining + " " + c.unit + " remaining" : "Completed"}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={6}>
|
||||||
|
{circleGraph(c)}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={6}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
!contractPermissions
|
||||||
|
.get(c.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setOpenGrainTransactionDialog(true);
|
||||||
|
setCurrentContract(c);
|
||||||
|
}}>
|
||||||
|
Deliver
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={6}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
backgroundColor: contractPermissions
|
||||||
|
.get(c.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
? "red"
|
||||||
|
: "",
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
!contractPermissions
|
||||||
|
.get(c.key())
|
||||||
|
?.includes(pond.Permission.PERMISSION_WRITE)
|
||||||
|
}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setDeleteWindow(true);
|
||||||
|
setCurrentContract(c);
|
||||||
|
}}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ListItem>
|
||||||
|
{i !== contracts.length - 1 && <Divider />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportedGrains = () => {
|
||||||
|
let elements: JSX.Element[] = [];
|
||||||
|
supportedTypes.forEach((contracts, key) => {
|
||||||
|
elements.push(
|
||||||
|
<Accordion key={key} style={{ borderRadius: 10, marginBottom: 10 }} defaultExpanded>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMore />}
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 10,
|
||||||
|
borderTopRightRadius: 10,
|
||||||
|
backgroundColor: GrainDescriber(key).colour
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
style={{
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 650,
|
||||||
|
color: theme.palette.getContrastText(GrainDescriber(key).colour)
|
||||||
|
}}>
|
||||||
|
{GrainDescriber(key).name} - ({contracts.length} Contract
|
||||||
|
{contracts.length > 1 ? "s" : ""})
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{isMobile ? mobileView(contracts) : displayTable(contracts)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
const customGrains = () => {
|
||||||
|
let elements: JSX.Element[] = [];
|
||||||
|
customTypes.forEach((contracts, key) => {
|
||||||
|
elements.push(
|
||||||
|
<Accordion key={key} style={{ borderRadius: 10, marginBottom: 10 }} defaultExpanded>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMore />}
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 10,
|
||||||
|
borderTopRightRadius: 10,
|
||||||
|
backgroundColor: stringToMaterialColour(key)
|
||||||
|
}}>
|
||||||
|
<Typography style={{ fontSize: 20, fontWeight: 650 }}>
|
||||||
|
{key} - ({contracts.length} Contract{contracts.length > 1 ? "s" : ""})
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{isMobile ? mobileView(contracts) : displayTable(contracts)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return elements;
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Box padding={2} paddingTop={0}>
|
||||||
|
{/* <RadioGroup
|
||||||
|
row
|
||||||
|
value={contractsDisplay}
|
||||||
|
onChange={(_, value) => {
|
||||||
|
setContractsDisplay(value)
|
||||||
|
}}>
|
||||||
|
<FormControlLabel
|
||||||
|
value={"open"}
|
||||||
|
control={<Radio />}
|
||||||
|
label={"Open"}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value={"closed"}
|
||||||
|
control={<Radio />}
|
||||||
|
label={"Closed"}
|
||||||
|
/>
|
||||||
|
</RadioGroup> */}
|
||||||
|
<Tabs
|
||||||
|
value={contractsDisplay}
|
||||||
|
onChange={(_, value) => setContractsDisplay(value)}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
variant={isMobile ? "fullWidth" : "standard"}
|
||||||
|
style={{ paddingTop: 0 }}
|
||||||
|
aria-label="statusTabs">
|
||||||
|
<Tab label={"Open"} value={"open"} />
|
||||||
|
<Tab label={"Closed"} value={"closed"} />
|
||||||
|
</Tabs>
|
||||||
|
{supportedGrains()}
|
||||||
|
{customGrains()}
|
||||||
|
</Box>
|
||||||
|
{currentContract && (
|
||||||
|
<GrainTransaction
|
||||||
|
open={openGrainTransactionDialog}
|
||||||
|
mainObject={currentContract}
|
||||||
|
restrictMatching
|
||||||
|
asDestination
|
||||||
|
allowAttachmentUploads
|
||||||
|
close={() => {
|
||||||
|
setOpenGrainTransactionDialog(false);
|
||||||
|
}}
|
||||||
|
callback={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{deleteConfirmation()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
660
src/contract/contractSettings.tsx
Normal file
660
src/contract/contractSettings.tsx
Normal file
|
|
@ -0,0 +1,660 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
77
src/contract/contractTransactionFile.tsx
Normal file
77
src/contract/contractTransactionFile.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import ImageViewer from "common/ImageViewer";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useFileControllerAPI } from "providers";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface FileReferences {
|
||||||
|
images: pond.FileReference[];
|
||||||
|
other: pond.FileReference[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contractKey: string;
|
||||||
|
transactionKey: string;
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractTransactionFile(props: Props) {
|
||||||
|
const { contractKey, transactionKey, open, onClose } = props;
|
||||||
|
const fileController = useFileControllerAPI();
|
||||||
|
const [fileRefs, setFileRefs] = useState<FileReferences>({
|
||||||
|
images: [],
|
||||||
|
other: []
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//retrieve the files for the passed in uuids
|
||||||
|
if (transactionKey) {
|
||||||
|
fileController
|
||||||
|
.listFileRefs(transactionKey, [contractKey, transactionKey], ["contract", "transaction"])
|
||||||
|
.then(resp => {
|
||||||
|
let fileRefs: FileReferences = {
|
||||||
|
images: [],
|
||||||
|
other: []
|
||||||
|
};
|
||||||
|
resp.data.fileRefs.forEach(fileRef => {
|
||||||
|
if (fileRef.metadata && fileRef.metadata.contentType.split("/")[0] === "image") {
|
||||||
|
fileRefs.images.push(fileRef);
|
||||||
|
} else {
|
||||||
|
fileRefs.other.push(fileRef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setFileRefs(fileRefs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [contractKey, transactionKey, fileController]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>Attached Files</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography>Attached Images</Typography>
|
||||||
|
{fileRefs.images.map(file => (
|
||||||
|
<Card key={file.uuid} style={{ width: "100%" }}>
|
||||||
|
<ImageViewer image={file} width={"100%"} />
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={close}>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/contract/contractTransactionGraph.tsx
Normal file
73
src/contract/contractTransactionGraph.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { Box, Card, Typography } from "@mui/material";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import { Transaction } from "models/Transaction";
|
||||||
|
import BarGraph, { BarData } from "charts/BarGraph";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import moment, { Moment } from "moment";
|
||||||
|
import { getGrainUnit } from "utils";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contract: Contract;
|
||||||
|
transactions: Transaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractTransactionGraph(props: Props) {
|
||||||
|
const { contract, transactions } = props;
|
||||||
|
const [data, setData] = useState<BarData[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let d: BarData[] = [];
|
||||||
|
transactions.forEach(t => {
|
||||||
|
let time: Moment = moment(t.transaction.timestamp);
|
||||||
|
let value = 0;
|
||||||
|
//this seems confusing but the transaction model contains the transaction as defined in the pond and then need to get the specific transaction data
|
||||||
|
//which is defined in the pond as a oneof for different types of transactions ie: grainTransaction
|
||||||
|
let transaction = t.transaction.transaction;
|
||||||
|
if (transaction?.grainTransaction) {
|
||||||
|
value = transaction.grainTransaction.bushels;
|
||||||
|
if (
|
||||||
|
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
|
||||||
|
transaction.grainTransaction.bushelsPerTonne > 1
|
||||||
|
) {
|
||||||
|
value = value / transaction.grainTransaction.bushelsPerTonne;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.push({
|
||||||
|
timestamp: time.valueOf(),
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setData(d);
|
||||||
|
}, [transactions]);
|
||||||
|
|
||||||
|
const daysRemaining = () => {
|
||||||
|
let days = "Delivery Window Closed";
|
||||||
|
const endDate = contract.settings.deliveryWindow?.endDate;
|
||||||
|
|
||||||
|
if (endDate && moment(endDate).isAfter(moment.now())) {
|
||||||
|
days = "To be completed " + moment(endDate).fromNow();
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ height: "100%", padding: 10 }}>
|
||||||
|
<Box height={"10%"}>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 20 }}>
|
||||||
|
Deliveries ({daysRemaining()})
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<BarGraph
|
||||||
|
data={data}
|
||||||
|
barColour={contract.colour}
|
||||||
|
useGradient
|
||||||
|
yMin={0}
|
||||||
|
customHeight={"90%"}
|
||||||
|
labels
|
||||||
|
labelSize={20}
|
||||||
|
labelColour="white"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
391
src/contract/contractTransactionTable.tsx
Normal file
391
src/contract/contractTransactionTable.tsx
Normal file
|
|
@ -0,0 +1,391 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Grid2 as Grid,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { AttachFile, Person, Visibility } from "@mui/icons-material";
|
||||||
|
import FileUploader from "common/FileUploads/FileUploader";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
// import { getTableIcons } from "common/ResponsiveTable";
|
||||||
|
// import MaterialTable, { Column } from "material-table";
|
||||||
|
import { Bin } from "models";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import { GrainBag } from "models/GrainBag";
|
||||||
|
import { Transaction } from "models/Transaction";
|
||||||
|
import moment from "moment";
|
||||||
|
import ObjectDescriber from "objects/ObjectDescriber";
|
||||||
|
import DeleteIcon from "products/AgIcons/Delete";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useBinAPI, useGrainBagAPI, useSnackbar, useTransactionAPI, useUserAPI } from "providers";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import TransactionDataDisplay from "transactions/transactionDataDisplay";
|
||||||
|
import ContractTransactionFile from "./contractTransactionFile";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contract: Contract;
|
||||||
|
permissions: pond.Permission[];
|
||||||
|
transactions: Transaction[];
|
||||||
|
objectNames: Map<string, string>;
|
||||||
|
transactionRevokedCallback: (transactionKey: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
userAvatar: {
|
||||||
|
width: 36,
|
||||||
|
height: 36
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function ContractTransactionTable(props: Props) {
|
||||||
|
const { transactions, objectNames, contract, permissions, transactionRevokedCallback } = props;
|
||||||
|
const [tableData, setTableData] = useState<Transaction[]>([]);
|
||||||
|
const [displayData, setDisplayData] = useState<Transaction[]>([]);
|
||||||
|
const [tablePage, setTablePage] = useState(0)
|
||||||
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const [userProfiles, setUserProfiles] = useState<Map<string, pond.UserProfile>>(new Map());
|
||||||
|
const [openRevokeDialog, setOpenRevokeDialog] = useState(false);
|
||||||
|
const classes = useStyles();
|
||||||
|
const [activeTransaction, setActiveTransaction] = useState<Transaction>(Transaction.create());
|
||||||
|
const transactionAPI = useTransactionAPI();
|
||||||
|
const binAPI = useBinAPI();
|
||||||
|
const bagAPI = useGrainBagAPI();
|
||||||
|
const [transactionSource, setTransactionSource] = useState<Bin | GrainBag | undefined>(undefined);
|
||||||
|
const [openFileViewer, setOpenFileViewer] = useState(false);
|
||||||
|
const [openAttachmentDialog, setOpenAttachmentDialog] = useState(false);
|
||||||
|
const [selectedTransaction, setSelectedTransaction] = useState<Transaction>(Transaction.create());
|
||||||
|
const [attachmentList, setAttachmentList] = useState<string[]>([]);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const { openSnack } = useSnackbar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let users: string[] = [];
|
||||||
|
transactions.forEach(transaction => {
|
||||||
|
if (
|
||||||
|
!users.includes(transaction.transaction.userId) &&
|
||||||
|
transaction.transaction.userId !== "unknown"
|
||||||
|
) {
|
||||||
|
users.push(transaction.transaction.userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTableData(transactions);
|
||||||
|
userAPI.getProfiles(users).then(resp => {
|
||||||
|
let userMap: Map<string, pond.UserProfile> = new Map<string, pond.UserProfile>();
|
||||||
|
resp.forEach(profile => {
|
||||||
|
userMap.set(profile.id, profile);
|
||||||
|
});
|
||||||
|
setUserProfiles(userMap);
|
||||||
|
});
|
||||||
|
}, [transactions, userAPI]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let clone: Transaction[] = cloneDeep(tableData)
|
||||||
|
let rows = clone.splice(tablePage * pageSize, pageSize)
|
||||||
|
setDisplayData(rows)
|
||||||
|
},[tablePage, pageSize, tableData])
|
||||||
|
|
||||||
|
const getTransactionSource = (transaction: Transaction) => {
|
||||||
|
setTransactionSource(undefined);
|
||||||
|
switch (transaction.transaction.fromObject) {
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_BIN:
|
||||||
|
binAPI.getBin(transaction.transaction.fromKey).then(resp => {
|
||||||
|
let bin = Bin.create(resp.data);
|
||||||
|
setTransactionSource(bin);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
|
||||||
|
bagAPI.getGrainBag(transaction.transaction.fromKey).then(resp => {
|
||||||
|
if (resp.data.grainBag) {
|
||||||
|
let bag = GrainBag.create(resp.data.grainBag);
|
||||||
|
setTransactionSource(bag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canRevoke = () => {
|
||||||
|
let valid = true;
|
||||||
|
if (transactionSource) {
|
||||||
|
const amount = activeTransaction.transaction.transaction?.grainTransaction?.bushels ?? 0;
|
||||||
|
switch (activeTransaction.transaction.fromObject) {
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_BIN:
|
||||||
|
const bin = transactionSource as Bin;
|
||||||
|
const inv = bin.settings.inventory;
|
||||||
|
const specs = bin.settings.specs;
|
||||||
|
if (inv && specs) {
|
||||||
|
if (inv.grainBushels + amount > specs.bushelCapacity) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
|
||||||
|
const bag = transactionSource as GrainBag;
|
||||||
|
if (bag.bushels() + amount > bag.capacity()) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeTransaction = () => {
|
||||||
|
//use the transaction api to revoke the transaction
|
||||||
|
//--moved all the functionality for adjusting the objects involved to the backend so this can be handled with a single call--
|
||||||
|
transactionAPI
|
||||||
|
.revokeTransaction(activeTransaction.transaction.key)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Transaction revoked and inventory moved back");
|
||||||
|
transactionRevokedCallback(activeTransaction.transaction.key);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
openSnack("There was was a problem revoking the transaction");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={openRevokeDialog}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenRevokeDialog(false);
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Revoke Transaction</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
Revoking this transaction will return the grain from the contract to its origin.
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpenRevokeDialog(false);
|
||||||
|
}}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
revokeTransaction();
|
||||||
|
setOpenRevokeDialog(false);
|
||||||
|
}}
|
||||||
|
disabled={!canRevoke()}>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayUser = (userID: string) => {
|
||||||
|
let profile = userProfiles.get(userID);
|
||||||
|
if (profile) {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" alignItems="center" spacing={2}>
|
||||||
|
<Grid>
|
||||||
|
{profile.avatar ? (
|
||||||
|
<Avatar alt={profile.name} src={profile.avatar} className={classes.userAvatar}>
|
||||||
|
{!profile.avatar && profile.name}
|
||||||
|
</Avatar>
|
||||||
|
) : (
|
||||||
|
<Avatar alt={profile.name} className={classes.userAvatar}>
|
||||||
|
<Person />
|
||||||
|
</Avatar>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>{profile.name}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" alignItems="center" spacing={2}>
|
||||||
|
<Grid>
|
||||||
|
<Avatar alt={"unknownUser"} className={classes.userAvatar}>
|
||||||
|
<Person />
|
||||||
|
</Avatar>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>Unknown</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachmentDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={openAttachmentDialog}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenAttachmentDialog(false);
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Select Files to Upload</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<FileUploader
|
||||||
|
keys={[contract.key(), selectedTransaction.transaction.key]}
|
||||||
|
types={["contract", "transaction"]}
|
||||||
|
toAttach={{
|
||||||
|
key: selectedTransaction.transaction.key,
|
||||||
|
type: pond.ObjectType.OBJECT_TYPE_TRANSACTION
|
||||||
|
}}
|
||||||
|
uploadStart={() => {
|
||||||
|
//disable close button
|
||||||
|
setUploading(true);
|
||||||
|
}}
|
||||||
|
uploadEnd={fileID => {
|
||||||
|
let aL = attachmentList;
|
||||||
|
//update the transaction with the new fileID
|
||||||
|
setUploading(false);
|
||||||
|
if (fileID) {
|
||||||
|
aL.push(fileID);
|
||||||
|
}
|
||||||
|
setAttachmentList(aL);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={uploading}
|
||||||
|
onClick={() => {
|
||||||
|
setOpenAttachmentDialog(false);
|
||||||
|
}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
{/* <Button
|
||||||
|
disabled={uploading}
|
||||||
|
onClick={() => {
|
||||||
|
updateTransaction();
|
||||||
|
}}>
|
||||||
|
Attach Files
|
||||||
|
</Button> */}
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: Column<Transaction>[] = [
|
||||||
|
{
|
||||||
|
title: "Date",
|
||||||
|
render: row => {
|
||||||
|
return <Typography>{moment(row.transaction.timestamp).format("YYYY-MM-DD")}</Typography>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "User",
|
||||||
|
render: row => {
|
||||||
|
return displayUser(row.transaction.userId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Source",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography>{ObjectDescriber(row.transaction.fromObject).name}</Typography>
|
||||||
|
<Typography>
|
||||||
|
{objectNames ? objectNames.get(row.transaction.fromKey) : row.transaction.fromKey}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Transaction",
|
||||||
|
render: row => {
|
||||||
|
return <TransactionDataDisplay transaction={row} />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//this column should be hidden from users that dont have edit permissions to the contract
|
||||||
|
{
|
||||||
|
title: "Revoke Transaction",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
style={{ backgroundColor: "red" }}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTransaction(row);
|
||||||
|
getTransactionSource(row);
|
||||||
|
setOpenRevokeDialog(true);
|
||||||
|
}}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Attachments",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" spacing={2}>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||||
|
//style={{ backgroundColor: "red" }}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTransaction(row);
|
||||||
|
setOpenFileViewer(true);
|
||||||
|
}}>
|
||||||
|
<Visibility />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTransaction(row);
|
||||||
|
setOpenAttachmentDialog(true);
|
||||||
|
}}>
|
||||||
|
<AttachFile />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Card>
|
||||||
|
<ResponsiveTable<Transaction>
|
||||||
|
page={tablePage}
|
||||||
|
pageSize={pageSize}
|
||||||
|
rows={displayData}
|
||||||
|
columns={columns}
|
||||||
|
total={tableData.length}
|
||||||
|
handleRowsPerPageChange={()=>{}}
|
||||||
|
setPage={()=>{}}
|
||||||
|
/>
|
||||||
|
{/* <MaterialTable
|
||||||
|
title={"Deliveries"}
|
||||||
|
columns={columns}
|
||||||
|
data={tableData}
|
||||||
|
icons={getTableIcons()}
|
||||||
|
/> */}
|
||||||
|
</Card>
|
||||||
|
<ContractTransactionFile
|
||||||
|
contractKey={contract.key()}
|
||||||
|
transactionKey={selectedTransaction.transaction.key}
|
||||||
|
open={openFileViewer}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenFileViewer(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{revokeDialog()}
|
||||||
|
{attachmentDialog()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
107
src/contract/contractVisualizer.tsx
Normal file
107
src/contract/contractVisualizer.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { Box, Card, Typography, useTheme } from "@mui/material";
|
||||||
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
contract: Contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContractVisualizer(props: Props) {
|
||||||
|
const { contract } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{ height: "100%", padding: 10 }}>
|
||||||
|
<Box height={"15%"}>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 20 }}>
|
||||||
|
{GrainDescriber(contract.grain()).name}
|
||||||
|
</Typography>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
|
||||||
|
{contract.deliveredInPreferredUnit().toFixed(2)}/
|
||||||
|
{contract.sizeInPreferredUnit().toFixed(2)} {contract.unit}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<ResponsiveContainer height={"85%"}>
|
||||||
|
<PieChart>
|
||||||
|
{/* <Legend
|
||||||
|
verticalAlign="bottom"
|
||||||
|
content={() => (
|
||||||
|
<Box textAlign="center" marginY={0.5}>
|
||||||
|
<Chip label={contract.commodityLabel()} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/> */}
|
||||||
|
<Pie
|
||||||
|
outerRadius={"100%"}
|
||||||
|
innerRadius="70%"
|
||||||
|
stroke="transparent"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.delivered,
|
||||||
|
fill: contract.colour
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
value: contract.settings.size - contract.settings.delivered
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
dataKey="value"
|
||||||
|
cx="50%"
|
||||||
|
cy="50%">
|
||||||
|
<Label
|
||||||
|
position="center"
|
||||||
|
fill={theme.palette.text.secondary}
|
||||||
|
fontSize={"2rem"}
|
||||||
|
fontWeight={650}
|
||||||
|
content={props => {
|
||||||
|
const { cx, cy } = props.viewBox as any;
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<Text
|
||||||
|
x={cx}
|
||||||
|
y={cy}
|
||||||
|
dy={-15}
|
||||||
|
textAnchor="middle"
|
||||||
|
verticalAnchor="middle"
|
||||||
|
fill={props.fill}
|
||||||
|
fontWeight={props.fontWeight}
|
||||||
|
fontSize={props.fontSize}>
|
||||||
|
{((contract.settings.delivered / contract.settings.size) * 100).toFixed(1) +
|
||||||
|
"%"}
|
||||||
|
</Text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
position="center"
|
||||||
|
fill={theme.palette.text.secondary}
|
||||||
|
fontSize={"1.5rem"}
|
||||||
|
fontWeight={650}
|
||||||
|
content={props => {
|
||||||
|
const { cx, cy } = props.viewBox as any;
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<Text
|
||||||
|
x={cx}
|
||||||
|
y={cy}
|
||||||
|
dy={15}
|
||||||
|
textAnchor="middle"
|
||||||
|
verticalAnchor="middle"
|
||||||
|
fill={props.fill}
|
||||||
|
fontWeight={props.fontWeight}
|
||||||
|
fontSize={props.fontSize}>
|
||||||
|
Delivered
|
||||||
|
</Text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,8 @@ const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
||||||
const Logs = lazy(() => import("pages/Logs"))
|
const Logs = lazy(() => import("pages/Logs"))
|
||||||
const Firmware = lazy(() => import("pages/Firmware"));
|
const Firmware = lazy(() => import("pages/Firmware"));
|
||||||
const Nfc = lazy(() => import("pages/Nfc"));
|
const Nfc = lazy(() => import("pages/Nfc"));
|
||||||
|
const Contracts = lazy(() => import("pages/Contracts"));
|
||||||
|
const Contract = lazy(() => import("pages/Contract"));
|
||||||
|
|
||||||
export const appendToUrl = (appendage: number | string) => {
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
|
@ -317,6 +319,8 @@ export default function Router() {
|
||||||
<Route path="firmware" element={<Firmware />} />
|
<Route path="firmware" element={<Firmware />} />
|
||||||
)}
|
)}
|
||||||
<Route path="nfc" element={<Nfc />} />
|
<Route path="nfc" element={<Nfc />} />
|
||||||
|
<Route path="contracts" element={<Contracts />} />
|
||||||
|
<Route path="contracts/:contractKey" element={<Contract />} />
|
||||||
|
|
||||||
{/* Map pages */}
|
{/* Map pages */}
|
||||||
<Route path="visualFarm" element={<FieldMap />} />
|
<Route path="visualFarm" element={<FieldMap />} />
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import CableIcon from "products/Bindapt/CableIcon";
|
||||||
import FieldListIcon from "products/AgIcons/FieldList";
|
import FieldListIcon from "products/AgIcons/FieldList";
|
||||||
import MarketplaceIcon from "products/CommonIcons/marketplaceIcon";
|
import MarketplaceIcon from "products/CommonIcons/marketplaceIcon";
|
||||||
import DataDuckIcon from "products/Bindapt/DataDuckIcon";
|
import DataDuckIcon from "products/Bindapt/DataDuckIcon";
|
||||||
|
import ContractsIcon from "products/CommonIcons/contractIcon";
|
||||||
|
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
|
@ -328,6 +329,7 @@ export default function SideNavigator(props: Props) {
|
||||||
{open && <ListItemText primary="Tasks" />}
|
{open && <ListItemText primary="Tasks" />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{(isAg || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Transactions" placement="right">
|
<Tooltip title="Transactions" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-transactions"
|
id="tour-transactions"
|
||||||
|
|
@ -340,6 +342,21 @@ export default function SideNavigator(props: Props) {
|
||||||
{open && <ListItemText primary="Transactions" />}
|
{open && <ListItemText primary="Transactions" />}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{(isAg || user.hasFeature("admin")) && (
|
||||||
|
<Tooltip title="Contracts" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-contracts"
|
||||||
|
onClick={() => goTo("/contracts")}
|
||||||
|
classes={getClasses("/contracts")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ContractsIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Contracts" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
|
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
|
||||||
<Tooltip title="Cable Estimator" placement="right">
|
<Tooltip title="Cable Estimator" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
|
|
|
||||||
326
src/pages/Contract.tsx
Normal file
326
src/pages/Contract.tsx
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Drawer,
|
||||||
|
Grid2 as Grid,
|
||||||
|
IconButton,
|
||||||
|
LinearProgress,
|
||||||
|
Theme,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import ContractTransactionGraph from "contract/contractTransactionGraph";
|
||||||
|
import ContractVisualizer from "contract/contractVisualizer";
|
||||||
|
import { useMobile, useThemeType } from "hooks";
|
||||||
|
// import { MatchParams } from "navigation/Routes";
|
||||||
|
import { useContractAPI } from "providers/pond/contractAPI";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
// import { Redirect, useRouteMatch } from "react-router";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
import { Contract as IContract } from "models/Contract";
|
||||||
|
import ObjectControls from "common/ObjectControls";
|
||||||
|
import ContractActions from "contract/contractActions";
|
||||||
|
import { useGlobalState, useUserAPI } from "providers";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { Scope } from "models";
|
||||||
|
import { Transaction } from "models/Transaction";
|
||||||
|
import ContractTransactionTable from "contract/contractTransactionTable";
|
||||||
|
import GrainTransaction from "grain/GrainTransaction";
|
||||||
|
import NotesIcon from "@mui/icons-material/Notes";
|
||||||
|
import Chat from "chat/Chat";
|
||||||
|
import ContractsIcon from "products/CommonIcons/contractIcon";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanelMine(props: TabPanelProps) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
{...other}>
|
||||||
|
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
return ({
|
||||||
|
inactiveButton: {
|
||||||
|
color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
width: theme.spacing(5),
|
||||||
|
height: theme.spacing(5),
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: theme.palette.divider
|
||||||
|
},
|
||||||
|
activeButton: {
|
||||||
|
color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
width: theme.spacing(5),
|
||||||
|
height: theme.spacing(5),
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: theme.palette.divider
|
||||||
|
},
|
||||||
|
drawerPaper: {
|
||||||
|
height: "100%",
|
||||||
|
width: "30%"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Contract() {
|
||||||
|
// const match = useRouteMatch<MatchParams>();
|
||||||
|
// const contractKey = match.params.contractKey;
|
||||||
|
const contractKey = useParams<{ contractKey: string }>()?.contractKey ?? "";
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
const contractAPI = useContractAPI();
|
||||||
|
const isMobile = useMobile();
|
||||||
|
const [contract, setContract] = useState<IContract>(IContract.create());
|
||||||
|
const [validTransactions, setValidTransactions] = useState<Map<string, Transaction>>(new Map());
|
||||||
|
//have to use this as the array passed to the bar graph, for whatever reason getting the array from the values of the map causes the labels to not appear
|
||||||
|
const [transArray, setTransArray] = useState<Transaction[]>([]);
|
||||||
|
const [openGrainTransactionDialog, setOpenGrainTransactionDialog] = useState(false);
|
||||||
|
const [displayTab, setDisplayTab] = useState(0);
|
||||||
|
const [openNoteDrawer, setOpenNoteDrawer] = useState(false);
|
||||||
|
//const [revokedTransactions, setRevokedTransactions] = useState<Transaction[]>([]);
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const [{ user, as }] = useGlobalState();
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
|
const [objectMap, setObjectMap] = useState<Map<string, string>>(new Map());
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
// const [invalid, setInvalid] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let key = contractKey;
|
||||||
|
let kind = "contract";
|
||||||
|
if (as) {
|
||||||
|
key = as;
|
||||||
|
kind = "team";
|
||||||
|
}
|
||||||
|
userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
}, [as, contractKey, userAPI, user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
setLoading(true);
|
||||||
|
contractAPI
|
||||||
|
.getContractPageData(contractKey)
|
||||||
|
.then(resp => {
|
||||||
|
setContract(IContract.any(resp.data.contract));
|
||||||
|
//setValidTransactions(resp.data.transactions.map(t => Transaction.create(t)));
|
||||||
|
let valid: Map<string, Transaction> = new Map();
|
||||||
|
let tArray: Transaction[] = [];
|
||||||
|
resp.data.transactions.forEach(transaction => {
|
||||||
|
let t = Transaction.create(transaction);
|
||||||
|
if (
|
||||||
|
t.transaction.state === pond.TransactionState.TRANSACTION_STATE_VALID ||
|
||||||
|
t.transaction.state === pond.TransactionState.TRANSACTION_STATE_CORRECTION
|
||||||
|
) {
|
||||||
|
valid.set(t.transaction.key, t);
|
||||||
|
tArray.push(t);
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// revoked.push(t);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
setValidTransactions(valid);
|
||||||
|
setTransArray(tArray);
|
||||||
|
//setRevokedTransactions(revoked);
|
||||||
|
let tempMap: Map<string, string> = new Map();
|
||||||
|
Object.keys(resp.data.nameMap).forEach(key => {
|
||||||
|
tempMap.set(key, resp.data.nameMap[key]);
|
||||||
|
});
|
||||||
|
setObjectMap(tempMap);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
//setInvalid(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [contractKey, contractAPI]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const contractDisplay = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isMobile ? (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
alignContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
direction="row"
|
||||||
|
style={{ marginBottom: 15 }}>
|
||||||
|
<Grid size={9}>
|
||||||
|
<Typography style={{ paddingRight: 20, fontSize: 30, fontWeight: 650 }} noWrap>
|
||||||
|
{contract.name()}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={3}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
onClick={e => {
|
||||||
|
setOpenGrainTransactionDialog(true);
|
||||||
|
}}>
|
||||||
|
+ Add Delivery
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
) : (
|
||||||
|
<Box display="flex" flexDirection="row" marginBottom={2}>
|
||||||
|
<Typography style={{ paddingRight: 20, fontSize: 30, fontWeight: 650 }} noWrap>
|
||||||
|
{contract.name()}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
onClick={e => {
|
||||||
|
setOpenGrainTransactionDialog(true);
|
||||||
|
}}>
|
||||||
|
+ Add Delivery
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Grid container spacing={2} direction={"row"}>
|
||||||
|
<Grid size={isMobile ? 12 : 4} style={{ height: isMobile ? 300 : 400 }}>
|
||||||
|
<ContractVisualizer contract={contract} />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={isMobile ? 12 : 8} style={{ height: isMobile ? 300 : 400 }}>
|
||||||
|
<ContractTransactionGraph contract={contract} transactions={transArray} />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={12}>
|
||||||
|
<ContractTransactionTable
|
||||||
|
permissions={permissions}
|
||||||
|
contract={contract}
|
||||||
|
transactions={transArray}
|
||||||
|
objectNames={objectMap}
|
||||||
|
transactionRevokedCallback={t => {
|
||||||
|
let vt = validTransactions;
|
||||||
|
vt.delete(t);
|
||||||
|
setValidTransactions(vt);
|
||||||
|
setTransArray(Array.from(vt.values()));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<ObjectControls
|
||||||
|
actions={
|
||||||
|
<ContractActions
|
||||||
|
contract={contract}
|
||||||
|
permissions={permissions}
|
||||||
|
refreshCallback={() => {}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
objectButton={
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setDisplayTab(0);
|
||||||
|
}}
|
||||||
|
className={displayTab === 0 ? classes.activeButton : classes.inactiveButton}
|
||||||
|
component="span">
|
||||||
|
<ContractsIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
notesButton={
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setDisplayTab(1);
|
||||||
|
} else {
|
||||||
|
setOpenNoteDrawer(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: "0.3625rem",
|
||||||
|
marginBottom: "0.3625rem",
|
||||||
|
marginRight: "0.5rem"
|
||||||
|
}}
|
||||||
|
className={displayTab === 1 ? classes.activeButton : classes.inactiveButton}
|
||||||
|
component="span">
|
||||||
|
<NotesIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box margin={2} marginTop={0}>
|
||||||
|
{loading ? (
|
||||||
|
<LinearProgress />
|
||||||
|
) : (
|
||||||
|
<React.Fragment>
|
||||||
|
<TabPanelMine value={displayTab} index={0}>
|
||||||
|
{contractDisplay()}
|
||||||
|
</TabPanelMine>
|
||||||
|
<TabPanelMine value={displayTab} index={1}>
|
||||||
|
<Typography style={{ paddingRight: 20, fontSize: 30, fontWeight: 650 }}>
|
||||||
|
{contract.name()} - Notes
|
||||||
|
</Typography>
|
||||||
|
<Card style={{ padding: 10, marginTop: 15 }}>
|
||||||
|
<Chat objectKey={contract.key()} type={pond.NoteType.NOTE_TYPE_CONTRACT} />
|
||||||
|
</Card>
|
||||||
|
</TabPanelMine>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<GrainTransaction
|
||||||
|
open={openGrainTransactionDialog}
|
||||||
|
mainObject={contract}
|
||||||
|
allowAttachmentUploads
|
||||||
|
restrictMatching
|
||||||
|
asDestination
|
||||||
|
close={() => {
|
||||||
|
setOpenGrainTransactionDialog(false);
|
||||||
|
}}
|
||||||
|
callback={t => {
|
||||||
|
setOpenGrainTransactionDialog(false);
|
||||||
|
if (t) {
|
||||||
|
let transactions = validTransactions;
|
||||||
|
transactions.set(t.key, Transaction.create(t));
|
||||||
|
setValidTransactions(transactions);
|
||||||
|
setTransArray(Array.from(transactions.values()));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Drawer
|
||||||
|
open={openNoteDrawer}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenNoteDrawer(false);
|
||||||
|
}}
|
||||||
|
anchor="right"
|
||||||
|
classes={{ paper: classes.drawerPaper }}>
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>Notes</Typography>
|
||||||
|
<Card style={{ padding: 10 }}>
|
||||||
|
<Chat objectKey={contract.key()} type={pond.NoteType.NOTE_TYPE_CONTRACT} />
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
src/pages/Contracts.tsx
Normal file
112
src/pages/Contracts.tsx
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { Box, Button, Grid2 as Grid, LinearProgress, TextField, Typography } from "@mui/material";
|
||||||
|
import ContractSettings from "contract/contractSettings";
|
||||||
|
import ContractsList from "contract/contractList";
|
||||||
|
import { Contract } from "models/Contract";
|
||||||
|
import moment from "moment";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useContractAPI, useGlobalState } from "providers";
|
||||||
|
// import { useContractAPI } from "providers/pond/contractAPI";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
|
||||||
|
export default function Contracts() {
|
||||||
|
const [openSettings, setOpenSettings] = useState(false);
|
||||||
|
const [{ as }] = useGlobalState()
|
||||||
|
const contractAPI = useContractAPI();
|
||||||
|
const [contracts, setContracts] = useState<Contract[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
//const [{ backgroundTasksComplete }] = useGlobalState();
|
||||||
|
const [contractPermissions, setContractPermissions] = useState<Map<string, pond.Permission[]>>(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
const [year, setYear] = useState(moment().format("YYYY"));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
setLoading(true);
|
||||||
|
contractAPI
|
||||||
|
.listContractsByYear(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
year,
|
||||||
|
as
|
||||||
|
)
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
let contracts: Contract[] = [];
|
||||||
|
let contractPermissions: Map<string, pond.Permission[]> = new Map();
|
||||||
|
resp.data.contracts.forEach(contract => {
|
||||||
|
let c = Contract.create(contract);
|
||||||
|
contracts.push(c);
|
||||||
|
let p = pond.EvaluatePermissionsResponse.fromObject(
|
||||||
|
resp.data.contractPermissions[c.key()]
|
||||||
|
);
|
||||||
|
contractPermissions.set(c.key(), p.permissions);
|
||||||
|
});
|
||||||
|
setContracts(contracts);
|
||||||
|
setContractPermissions(contractPermissions);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {});
|
||||||
|
}
|
||||||
|
}, [contractAPI, year]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography style={{ fontSize: 25, fontWeight: 650 }}>Contracts</Typography>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignContent="center"
|
||||||
|
alignItems="center">
|
||||||
|
<Grid>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenSettings(true);
|
||||||
|
}}>
|
||||||
|
Add Contract
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
label="Contract Year"
|
||||||
|
value={year}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
onChange={e => setYear(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
{!loading ? (
|
||||||
|
<ContractsList
|
||||||
|
contracts={contracts}
|
||||||
|
contractPermissions={contractPermissions}
|
||||||
|
updateList={c => {
|
||||||
|
setContracts([...c]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<LinearProgress />
|
||||||
|
)}
|
||||||
|
<ContractSettings
|
||||||
|
open={openSettings}
|
||||||
|
close={() => {
|
||||||
|
setOpenSettings(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -42,7 +42,8 @@ export {
|
||||||
useMutationAPI, //TODO: update api with resolve,reject
|
useMutationAPI, //TODO: update api with resolve,reject
|
||||||
useGrainBagAPI,
|
useGrainBagAPI,
|
||||||
useTransactionAPI,
|
useTransactionAPI,
|
||||||
useFileControllerAPI
|
useFileControllerAPI,
|
||||||
|
useContractAPI //TODO: update api with resolve, reject
|
||||||
} from "./pond/pond";
|
} from "./pond/pond";
|
||||||
// export { SecurityContext, useSecurity } from "./security";
|
// export { SecurityContext, useSecurity } from "./security";
|
||||||
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||||
|
|
|
||||||
176
src/providers/pond/contractAPI.tsx
Normal file
176
src/providers/pond/contractAPI.tsx
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
|
||||||
|
export interface IContractInterface {
|
||||||
|
addContract: (
|
||||||
|
name: string,
|
||||||
|
settings: pond.ContractSettings
|
||||||
|
) => Promise<AxiosResponse<pond.AddContractResponse>>;
|
||||||
|
listContracts: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string,
|
||||||
|
as?: string,
|
||||||
|
) => Promise<AxiosResponse<pond.ListContractsResponse>>;
|
||||||
|
updateContract: (
|
||||||
|
key: string,
|
||||||
|
name: string,
|
||||||
|
settings: pond.ContractSettings
|
||||||
|
) => Promise<AxiosResponse<pond.UpdateContractResponse>>;
|
||||||
|
removeContract: (key: string) => Promise<AxiosResponse<pond.RemoveContractResponse>>;
|
||||||
|
listContractsByYear: (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string,
|
||||||
|
contractYear?: string,
|
||||||
|
as?: string
|
||||||
|
) => Promise<AxiosResponse<pond.ListContractsByYearResponse>>;
|
||||||
|
//list contract page data
|
||||||
|
getContractPageData: (key: string) => Promise<AxiosResponse<pond.GetContractPageDataResponse>>;
|
||||||
|
//for the moment however simply passing the contract from the earlier list is sufficient
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContractAPIcontext = createContext<IContractInterface>({} as IContractInterface);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function ContractProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get, del, post, put } = useHTTP();
|
||||||
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
|
//add
|
||||||
|
const addContract = (name: string, settings: pond.ContractSettings) => {
|
||||||
|
if (as) {
|
||||||
|
return post<pond.AddContractResponse>(
|
||||||
|
pondURL("/contracts?name=" + name + "&as=" + as),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return post<pond.AddContractResponse>(pondURL("/contracts?name=" + name), settings);
|
||||||
|
};
|
||||||
|
//list
|
||||||
|
const listContracts = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string
|
||||||
|
) => {
|
||||||
|
let asText = "";
|
||||||
|
if (as) asText = "&as=" + as;
|
||||||
|
if (specificUser) asText = "&as=" + specificUser;
|
||||||
|
return get<pond.ListContractsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/contracts?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + (order ? order : "asc")) +
|
||||||
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
|
(numerical ? "&numerical=true" : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "") +
|
||||||
|
asText
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const listContractsByYear = (
|
||||||
|
limit: number,
|
||||||
|
offset: number,
|
||||||
|
order?: "asc" | "desc",
|
||||||
|
orderBy?: string,
|
||||||
|
search?: string,
|
||||||
|
keys?: string[],
|
||||||
|
types?: string[],
|
||||||
|
numerical?: boolean,
|
||||||
|
specificUser?: string,
|
||||||
|
contractYear?: string,
|
||||||
|
as?: string,
|
||||||
|
) => {
|
||||||
|
let asText = "";
|
||||||
|
if (as) asText = "&as=" + as;
|
||||||
|
if (specificUser) asText = "&as=" + specificUser;
|
||||||
|
return get<pond.ListContractsByYearResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/contractsByYear?limit=" +
|
||||||
|
limit +
|
||||||
|
"&offset=" +
|
||||||
|
offset +
|
||||||
|
("&order=" + (order ? order : "asc")) +
|
||||||
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
|
(numerical ? "&numerical=true" : "") +
|
||||||
|
(search ? "&search=" + search : "") +
|
||||||
|
(keys ? "&keys=" + keys.toString() : "") +
|
||||||
|
(types ? "&types=" + types.toString() : "") +
|
||||||
|
(contractYear ? "&year=" + contractYear : "") +
|
||||||
|
asText
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
//update
|
||||||
|
const updateContract = (key: string, name: string, settings: pond.ContractSettings) => {
|
||||||
|
if (as) {
|
||||||
|
return put<pond.UpdateContractResponse>(
|
||||||
|
pondURL("/contracts/" + key + "?as=" + as + "&name=" + name),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return put<pond.UpdateContractResponse>(
|
||||||
|
pondURL("/contracts/" + key + "?name=" + name),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
};
|
||||||
|
//remove
|
||||||
|
const removeContract = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return del<pond.RemoveContractResponse>(pondURL("/contracts/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
return del<pond.RemoveContractResponse>(pondURL("/contracts/" + key + "?as=" + as));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getContractPageData = (key: string) => {
|
||||||
|
if (as) {
|
||||||
|
return get<pond.GetContractPageDataResponse>(pondURL("/contractsPage/" + key + "?as=" + as));
|
||||||
|
}
|
||||||
|
|
||||||
|
return get<pond.GetContractPageDataResponse>(pondURL("/contractsPage/" + key));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContractAPIcontext.Provider
|
||||||
|
value={{
|
||||||
|
addContract,
|
||||||
|
listContracts,
|
||||||
|
listContractsByYear,
|
||||||
|
updateContract,
|
||||||
|
removeContract,
|
||||||
|
getContractPageData
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</ContractAPIcontext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useContractAPI = () => useContext(ContractAPIcontext);
|
||||||
|
|
@ -31,9 +31,14 @@ export const FileControllerAPIContext = createContext<IFileControllerAPI>({} as
|
||||||
export default function FileControllerProvider(props: PropsWithChildren<any>) {
|
export default function FileControllerProvider(props: PropsWithChildren<any>) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
const { get, post, del } = useHTTP();
|
const { get, post, del, options } = useHTTP();
|
||||||
|
|
||||||
const uploadFiles = (files: FileList, toAttach?: toAttach, keys?: string[], types?: string[]) => {
|
const uploadFiles = (files: FileList, toAttach?: toAttach, keys?: string[], types?: string[]) => {
|
||||||
|
let headers = {
|
||||||
|
...options().headers,
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
};
|
||||||
|
let opt = { headers };
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
Array.from(files).forEach((f, i) => {
|
Array.from(files).forEach((f, i) => {
|
||||||
formData.append("file" + i, f);
|
formData.append("file" + i, f);
|
||||||
|
|
@ -53,7 +58,7 @@ export default function FileControllerProvider(props: PropsWithChildren<any>) {
|
||||||
(types ? "&types=" + types.join(",") : "")
|
(types ? "&types=" + types.join(",") : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return post<pond.AddFileResponse>(url, formData);
|
return post<pond.AddFileResponse>(url, formData, opt);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import MutationProvider, {useMutationAPI} from "./mutationAPI"
|
||||||
import PreferenceProvider, {usePreferenceAPI} from "./preferenceAPI";
|
import PreferenceProvider, {usePreferenceAPI} from "./preferenceAPI";
|
||||||
import TaskProvider, { useTaskAPI } from "./taskAPI";
|
import TaskProvider, { useTaskAPI } from "./taskAPI";
|
||||||
import DataDogProvider, { useDataDogProxyAPI } from "./datadogProxyAPI";
|
import DataDogProvider, { useDataDogProxyAPI } from "./datadogProxyAPI";
|
||||||
|
import ContractProvider, { useContractAPI } from "./contractAPI";
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
|
|
@ -78,7 +79,9 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<PreferenceProvider>
|
<PreferenceProvider>
|
||||||
<TaskProvider>
|
<TaskProvider>
|
||||||
<DataDogProvider>
|
<DataDogProvider>
|
||||||
|
<ContractProvider>
|
||||||
{children}
|
{children}
|
||||||
|
</ContractProvider>
|
||||||
</DataDogProvider>
|
</DataDogProvider>
|
||||||
</TaskProvider>
|
</TaskProvider>
|
||||||
</PreferenceProvider>
|
</PreferenceProvider>
|
||||||
|
|
@ -143,5 +146,6 @@ export {
|
||||||
useMutationAPI,
|
useMutationAPI,
|
||||||
usePreferenceAPI,
|
usePreferenceAPI,
|
||||||
useTaskAPI,
|
useTaskAPI,
|
||||||
useDataDogProxyAPI
|
useDataDogProxyAPI,
|
||||||
|
useContractAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue