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
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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue