added grain bag stuff and file selector/uploader stuff
This commit is contained in:
parent
ae30251035
commit
2d78947d2e
18 changed files with 1391 additions and 36 deletions
70
package-lock.json
generated
70
package-lock.json
generated
|
|
@ -18,10 +18,12 @@
|
|||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.26.0",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@turf/area": "^7.2.0",
|
||||
"@types/classnames": "^2.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"geojson": "^0.5.0",
|
||||
"json-2-csv": "^5.5.8",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
|
|
@ -3422,6 +3424,47 @@
|
|||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@turf/area": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/area/-/area-7.2.0.tgz",
|
||||
"integrity": "sha512-zuTTdQ4eoTI9nSSjerIy4QwgvxqwJVciQJ8tOPuMHbXJ9N/dNjI7bU8tasjhxas/Cx3NE9NxVHtNpYHL0FSzoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@turf/helpers": "^7.2.0",
|
||||
"@turf/meta": "^7.2.0",
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@turf/helpers": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.2.0.tgz",
|
||||
"integrity": "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@turf/meta": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.2.0.tgz",
|
||||
"integrity": "sha512-igzTdHsQc8TV1RhPuOLVo74Px/hyPrVgVOTgjWQZzt3J9BVseCdpfY/0cJBdlSRI4S/yTmmHl7gAqjhpYH5Yaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@turf/helpers": "^7.2.0",
|
||||
"@types/geojson": "^7946.0.10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/turf"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
|
|
@ -3539,6 +3582,12 @@
|
|||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
|
|
@ -4174,8 +4223,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
|
|
@ -5672,6 +5723,15 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/geojson": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz",
|
||||
"integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
|
|
@ -8566,6 +8626,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -28,10 +28,12 @@
|
|||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.26.0",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@turf/area": "^7.2.0",
|
||||
"@types/classnames": "^2.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
"geojson": "^0.5.0",
|
||||
"json-2-csv": "^5.5.8",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
|
|
|
|||
202
src/common/FileUploads/FileSelector.tsx
Normal file
202
src/common/FileUploads/FileSelector.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
darken,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { useFileControllerAPI, useGlobalState } from "providers";
|
||||
import { useState } from "react";
|
||||
import ErrorIcon from "@mui/icons-material/Error";
|
||||
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
|
||||
import { getThemeType } from "theme";
|
||||
import { toAttach } from "providers/pond/fileControllerAPI";
|
||||
import DeleteIcon from "products/AgIcons/Delete";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
interface Props {
|
||||
toAttach?: toAttach;
|
||||
uploadStart: () => void;
|
||||
uploadEnd: (fileID?: string, fileName?: string) => void;
|
||||
uniqueID?: string;
|
||||
keys?: string[];
|
||||
types?: string[];
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
fileSelect: {
|
||||
cursor: "pointer",
|
||||
border: "1px solid white",
|
||||
padding: 7,
|
||||
borderRadius: 5,
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === "light" ? 0.05 : 0.25
|
||||
),
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.background.paper
|
||||
}
|
||||
},
|
||||
fileDisabled: {
|
||||
border: "1px solid gray",
|
||||
color: "gray",
|
||||
padding: 7,
|
||||
borderRadius: 5,
|
||||
backgroundColor: theme.palette.background.paper
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default function FileSelector(props: Props) {
|
||||
const { uploadEnd, uploadStart, uniqueID, toAttach, keys, types } = props;
|
||||
const [{ userTeamPermissions }] = useGlobalState();
|
||||
const [fileList, setFileList] = useState<FileList | undefined>();
|
||||
const classes = useStyles();
|
||||
const fileAPI = useFileControllerAPI();
|
||||
const [uploadFailed, setUploadFailed] = useState(false);
|
||||
const [fileID, setFileID] = useState("");
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [errorMsg, setErrorMsg] = useState("File failed to upload");
|
||||
const [fileInvalid, setFileInvalid] = useState(false);
|
||||
const maxFileSizeMB = 10;
|
||||
const bytesConversionFactor = 1048576;
|
||||
|
||||
const uploadFile = () => {
|
||||
if (fileList) {
|
||||
let image = fileID;
|
||||
uploadStart();
|
||||
setUploading(true);
|
||||
fileAPI
|
||||
.uploadFiles(fileList, toAttach, keys, types)
|
||||
.then(resp => {
|
||||
image = resp.data.uuid;
|
||||
setUploadFailed(false);
|
||||
})
|
||||
.catch(err => {
|
||||
//file failed to upload
|
||||
if (err.response) {
|
||||
setErrorMsg(err.response.data.error);
|
||||
}
|
||||
setUploadFailed(true);
|
||||
})
|
||||
.finally(() => {
|
||||
setFileID(image);
|
||||
uploadEnd(image, fileName);
|
||||
setUploading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteFile = () => {
|
||||
fileAPI
|
||||
.deleteFile(fileID)
|
||||
.then(resp => {
|
||||
console.log("no errors");
|
||||
})
|
||||
.catch(err => {
|
||||
console.log("error found");
|
||||
});
|
||||
};
|
||||
|
||||
const checkFile = (file: File) => {
|
||||
let invalid = false;
|
||||
let largeFile = file.size / bytesConversionFactor > maxFileSizeMB;
|
||||
if (largeFile || !file.type.includes("image/")) {
|
||||
invalid = true;
|
||||
let errMsg = "File type not allowed";
|
||||
if (largeFile) errMsg = "File too large";
|
||||
setErrorMsg(errMsg);
|
||||
}
|
||||
setFileInvalid(invalid);
|
||||
};
|
||||
|
||||
const uploadStatus = () => {
|
||||
if (uploading) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
if (uploadFailed) {
|
||||
return <ErrorIcon style={{ height: 40, width: 40, color: "red" }} />;
|
||||
}
|
||||
if (fileID !== "") {
|
||||
return <CheckCircleOutlineIcon style={{ height: 40, width: 40, color: "green" }} />;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width={"100%"}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Grid >
|
||||
<input
|
||||
disabled={
|
||||
uploading ||
|
||||
(!uploadFailed && fileID !== "") ||
|
||||
!userTeamPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)
|
||||
}
|
||||
multiple={false}
|
||||
name={uniqueID || "fileInput"}
|
||||
id={uniqueID || "fileInput"}
|
||||
hidden
|
||||
type="file"
|
||||
onChange={event => {
|
||||
if (event.target.files) {
|
||||
setFileList(event.target.files);
|
||||
let file = event.target.files[0];
|
||||
if (file) {
|
||||
setFileName(file.name);
|
||||
checkFile(file);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={uniqueID || "fileInput"}
|
||||
className={
|
||||
userTeamPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)
|
||||
? classes.fileSelect
|
||||
: classes.fileDisabled
|
||||
}>
|
||||
{fileName === "" ? "Choose a file..." : fileName}
|
||||
</label>
|
||||
</Grid>
|
||||
<Grid >
|
||||
<Grid container spacing={2} alignContent="center" alignItems="center">
|
||||
<Grid >{uploadStatus()}</Grid>
|
||||
{!uploadFailed && fileID !== "" && (
|
||||
<Grid>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
deleteFile();
|
||||
}}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid >
|
||||
{fileList && (
|
||||
<Button
|
||||
onClick={uploadFile}
|
||||
variant="contained"
|
||||
disabled={uploading || (!uploadFailed && fileID !== "") || fileInvalid}>
|
||||
Upload
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{(uploadFailed || fileInvalid) && <Typography color="error">{errorMsg}</Typography>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
43
src/common/FileUploads/FileUploader.tsx
Normal file
43
src/common/FileUploads/FileUploader.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { toAttach } from "providers/pond/fileControllerAPI";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import FileSelector from "./FileSelector";
|
||||
|
||||
interface Props {
|
||||
startingSelectorCount?: number;
|
||||
toAttach?: toAttach;
|
||||
uploadStart: () => void;
|
||||
uploadEnd: (fileID?: string, fileName?: string) => void;
|
||||
keys?: string[];
|
||||
types?: string[];
|
||||
}
|
||||
|
||||
export default function FileUploader(props: Props) {
|
||||
const { uploadStart, uploadEnd, startingSelectorCount, toAttach, keys, types } = props;
|
||||
const [numSelectors, setNumSelectors] = useState(startingSelectorCount || 1);
|
||||
const [selectors, setSelectors] = useState<JSX.Element[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let currentSelectors: JSX.Element[] = [];
|
||||
for (let i = 0; i < numSelectors; i++) {
|
||||
currentSelectors.push(
|
||||
<Box key={i} margin={2}>
|
||||
<FileSelector
|
||||
toAttach={toAttach}
|
||||
keys={keys}
|
||||
types={types}
|
||||
uniqueID={"fileSelector" + i}
|
||||
uploadStart={uploadStart}
|
||||
uploadEnd={(fileID, fileName) => {
|
||||
setNumSelectors(numSelectors + 1);
|
||||
uploadEnd(fileID, fileName);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
setSelectors([...currentSelectors]);
|
||||
}, [numSelectors, uploadEnd, uploadStart, toAttach, keys, types]);
|
||||
|
||||
return <React.Fragment>{selectors}</React.Fragment>;
|
||||
}
|
||||
|
|
@ -15,9 +15,9 @@ import FileUploader from "common/FileUploads/FileUploader";
|
|||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import SearchSelect, { Option } from "common/SearchSelect";
|
||||
import { useMobile } from "hooks";
|
||||
import { Bin, Field } from "models";
|
||||
import { Contract } from "models/Contract";
|
||||
import { GrainBag } from "models/GrainBag";
|
||||
import { Bin, Field, Contract, GrainBag } from "models";
|
||||
// import { Contract } from "models/Contract";
|
||||
// import { GrainBag } from "models/GrainBag";
|
||||
import moment from "moment";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import {
|
||||
|
|
@ -28,7 +28,7 @@ import {
|
|||
useSnackbar,
|
||||
useTransactionAPI
|
||||
} from "providers";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { getGrainUnit } from "utils";
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import {
|
||||
Button,
|
||||
createStyles,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Grid,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
makeStyles,
|
||||
Switch,
|
||||
TextField,
|
||||
Theme,
|
||||
useTheme
|
||||
} from "@material-ui/core";
|
||||
} from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import SearchSelect, { Option } from "common/SearchSelect";
|
||||
import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
|
||||
|
|
@ -22,16 +21,16 @@ import moment from "moment";
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getGrainUnit } from "utils";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
bottomSpacing: {
|
||||
marginBottom: theme.spacing(1)
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -61,7 +60,8 @@ export default function GrainBagSettings(props: Props) {
|
|||
const [grainBushels, setGrainBushels] = useState(0);
|
||||
const [fillDate, setFillDate] = useState(moment().format("YYYY-MM-DD"));
|
||||
const [initialMoisture, setInitialMoisture] = useState(0);
|
||||
const history = useHistory();
|
||||
// const history = useHistory();
|
||||
const navigate = useNavigate()
|
||||
const { openSnack } = useSnackbar();
|
||||
const theme = useTheme();
|
||||
const [grainUpdate, setGrainUpdate] = useState(false);
|
||||
|
|
@ -108,7 +108,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
.then(resp => {
|
||||
openSnack("Removed Grain Bag");
|
||||
close();
|
||||
history.push("/bins");
|
||||
navigate("/bins");
|
||||
})
|
||||
.catch(err => {
|
||||
openSnack("Failed to remove Grain Bag");
|
||||
|
|
@ -336,8 +336,8 @@ export default function GrainBagSettings(props: Props) {
|
|||
onChange={e => setFillDate(e.target.value)}
|
||||
/>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item>Grain</Grid>
|
||||
<Grid item>
|
||||
<Grid>Grain</Grid>
|
||||
<Grid>
|
||||
<Switch
|
||||
color="default"
|
||||
value={isCustom}
|
||||
|
|
@ -353,7 +353,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
name="storage"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>Custom</Grid>
|
||||
<Grid>Custom</Grid>
|
||||
</Grid>
|
||||
{grainBox()}
|
||||
<TextField
|
||||
|
|
@ -424,8 +424,8 @@ export default function GrainBagSettings(props: Props) {
|
|||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Grid container direction="row" justify="space-between">
|
||||
<Grid item>
|
||||
<Grid container direction="row" justifyContent="space-between">
|
||||
<Grid >
|
||||
{grainBag && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
|
@ -437,7 +437,7 @@ export default function GrainBagSettings(props: Props) {
|
|||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid >
|
||||
<Button
|
||||
onClick={() => {
|
||||
close();
|
||||
|
|
|
|||
237
src/models/Contract.ts
Normal file
237
src/models/Contract.ts
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
import { Option } from "common/SearchSelect";
|
||||
import { GrainOptions, ToGrainOption } from "grain";
|
||||
import GrainDescriber from "grain/GrainDescriber";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { getGrainUnit, stringToMaterialColour } from "utils";
|
||||
import { or } from "utils/types";
|
||||
|
||||
export class Contract {
|
||||
public settings: pond.ContractSettings = pond.ContractSettings.create();
|
||||
public title: string = "Contract";
|
||||
public colour: string = "";
|
||||
public unit: string = "";
|
||||
public label: string = "";
|
||||
private objKey: string = "";
|
||||
|
||||
public static create(pb?: pond.Contract): Contract {
|
||||
let my = new Contract();
|
||||
if (pb) {
|
||||
my.settings = pond.ContractSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
||||
my.title = pb.name;
|
||||
my.objKey = pb.key;
|
||||
my.unit = my.measurementUnit();
|
||||
my.colour = my.commodityColour();
|
||||
my.label = my.commodityLabel();
|
||||
}
|
||||
return my;
|
||||
}
|
||||
|
||||
private measurementUnit(): string {
|
||||
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
|
||||
return getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private commodityColour(): string {
|
||||
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
|
||||
if (this.isCustom()) {
|
||||
return stringToMaterialColour(this.settings.customCommodity);
|
||||
} else {
|
||||
return GrainDescriber(this.settings.commodity).colour;
|
||||
}
|
||||
}
|
||||
return "blue";
|
||||
}
|
||||
|
||||
public commodityLabel(): string {
|
||||
let label = "";
|
||||
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
|
||||
if (this.isCustom()) {
|
||||
label = this.settings.customCommodity;
|
||||
} else {
|
||||
label = GrainDescriber(this.settings.commodity).name;
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public static clone(other?: Contract): Contract {
|
||||
if (other) {
|
||||
return Contract.create(
|
||||
pond.Contract.fromObject({
|
||||
title: other.title,
|
||||
key: other.objKey,
|
||||
settings: cloneDeep(other.settings)
|
||||
})
|
||||
);
|
||||
}
|
||||
return Contract.create();
|
||||
}
|
||||
|
||||
public static any(data: any): Contract {
|
||||
return Contract.create(pond.Contract.fromObject(cloneDeep(data)));
|
||||
}
|
||||
|
||||
public key(): string {
|
||||
if (this) {
|
||||
return this.objKey;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public name(): string {
|
||||
if (this) {
|
||||
return this.title;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public objectType(): pond.ObjectType {
|
||||
return pond.ObjectType.OBJECT_TYPE_CONTRACT;
|
||||
}
|
||||
|
||||
public objectTypeString(): string {
|
||||
return "Contract";
|
||||
}
|
||||
|
||||
//possibly add another field to the contract for the subtype
|
||||
public subtype(): string {
|
||||
return this.settings.customCommodity;
|
||||
}
|
||||
|
||||
//this is for grain transaction to compare if the source type matches
|
||||
public storage(): pond.BinStorage {
|
||||
let storage = pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
|
||||
if (this.settings.commodity === pond.Grain.GRAIN_CUSTOM) {
|
||||
storage = pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN;
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
public grain(): pond.Grain {
|
||||
return this.settings.commodity;
|
||||
}
|
||||
|
||||
public customType(): string {
|
||||
return this.settings.customCommodity;
|
||||
}
|
||||
|
||||
public isCustom(): boolean {
|
||||
let custom = false;
|
||||
switch (this.settings.type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
return this.settings.commodity === pond.Grain.GRAIN_CUSTOM;
|
||||
}
|
||||
return custom;
|
||||
}
|
||||
|
||||
public static customValue(type: pond.ContractType): number {
|
||||
let v = 0;
|
||||
switch (type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
return pond.Grain.GRAIN_CUSTOM;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public static commodityOptionsForType(type: pond.ContractType): Option[] {
|
||||
let ops: Option[] = [];
|
||||
if (type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
|
||||
ops = GrainOptions();
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
public static typeOptions(): Option[] {
|
||||
return [
|
||||
{
|
||||
label: "Grain",
|
||||
value: pond.ContractType.CONTRACT_TYPE_GRAIN
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public static toTypeOption(type: pond.ContractType): Option {
|
||||
switch (type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
return {
|
||||
label: "Grain",
|
||||
value: pond.ContractType.CONTRACT_TYPE_GRAIN
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Unknown",
|
||||
value: pond.ContractType.CONTRACT_TYPE_UNKNOWN
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static toCommodityOption(type: pond.ContractType, commodity: number): Option {
|
||||
switch (type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
return ToGrainOption(commodity);
|
||||
default:
|
||||
return { label: "None", value: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the conversion value in the settings, if not set will return 1
|
||||
* @returns 1 or bushels per tonne
|
||||
*/
|
||||
public conversionValue(): number {
|
||||
//trying to avoid a divide by 0 error by only returning a value greater than 0
|
||||
//since to get the secondary unit you divide the current unit by the conversion value
|
||||
if (this.settings.conversionValue > 0) {
|
||||
return this.settings.conversionValue;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//used for grain transactions to compare the source and destinations since different bushels per tonne for custom grain should be treated like seperate grain types
|
||||
public bushelsPerTonne(): number {
|
||||
return this.conversionValue();
|
||||
}
|
||||
|
||||
public sizeInPreferredUnit(): number {
|
||||
let size = this.settings.size;
|
||||
switch (this.settings.type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && this.conversionValue() > 1) {
|
||||
size = size / this.conversionValue();
|
||||
}
|
||||
}
|
||||
return Math.round(size * 100) / 100;
|
||||
}
|
||||
|
||||
public deliveredInPreferredUnit(): number {
|
||||
let del = this.settings.delivered;
|
||||
switch (this.settings.type) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && this.conversionValue() > 1) {
|
||||
del = del / this.conversionValue();
|
||||
}
|
||||
}
|
||||
return Math.round(del * 100) / 100;
|
||||
}
|
||||
|
||||
public static toStoredUnit(
|
||||
secondaryUnitVal: number,
|
||||
contractType: pond.ContractType,
|
||||
conversionValue: number
|
||||
): number {
|
||||
let storedunitVal = secondaryUnitVal;
|
||||
switch (contractType) {
|
||||
case pond.ContractType.CONTRACT_TYPE_GRAIN:
|
||||
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && conversionValue > 1) {
|
||||
storedunitVal = secondaryUnitVal * conversionValue;
|
||||
}
|
||||
}
|
||||
return storedunitVal;
|
||||
}
|
||||
}
|
||||
153
src/models/Field.ts
Normal file
153
src/models/Field.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { or } from "utils/types";
|
||||
import area from "@turf/area";
|
||||
import { Feature } from "geojson";
|
||||
import { GeometryMapping } from "./GeometryMapping";
|
||||
|
||||
export class Field {
|
||||
public settings: pond.FieldSettings = pond.FieldSettings.create();
|
||||
public status: pond.FieldStatus = pond.FieldStatus.create();
|
||||
public permissions: pond.Permission[] = [];
|
||||
|
||||
public static create(pf?: pond.Field): Field {
|
||||
let my = new Field();
|
||||
if (pf) {
|
||||
my.settings = pond.FieldSettings.fromObject(cloneDeep(or(pf.settings, {})));
|
||||
my.status = pond.FieldStatus.fromObject(cloneDeep(or(pf.status, {})));
|
||||
my.permissions = pf.fieldPermissions;
|
||||
}
|
||||
return my;
|
||||
}
|
||||
|
||||
public static clone(other?: Field): Field {
|
||||
if (other) {
|
||||
return Field.create(
|
||||
pond.Field.fromObject({
|
||||
settings: cloneDeep(other.settings),
|
||||
status: cloneDeep(other.status),
|
||||
permissions: cloneDeep(other.permissions)
|
||||
})
|
||||
);
|
||||
}
|
||||
return Field.create();
|
||||
}
|
||||
|
||||
public calculateAcres(): number {
|
||||
let totalAcres = 0;
|
||||
const metersToAcresConversionValue = 0.00024710538146717;
|
||||
let geoData = this.settings.fieldGeoData;
|
||||
if (geoData) {
|
||||
//round to 2 decimal places
|
||||
totalAcres =
|
||||
Math.round(
|
||||
area(
|
||||
GeometryMapping.geoJSON(geoData.geoShape, geoData.shapes, geoData.holes) as Feature
|
||||
) *
|
||||
metersToAcresConversionValue *
|
||||
100
|
||||
) / 100;
|
||||
}
|
||||
return totalAcres;
|
||||
}
|
||||
|
||||
public static any(data: any): Field {
|
||||
return Field.create(pond.Field.fromObject(cloneDeep(data)));
|
||||
}
|
||||
|
||||
public key(): string {
|
||||
return this.settings.key;
|
||||
}
|
||||
|
||||
public fieldName(): string {
|
||||
return this.settings.fieldName;
|
||||
}
|
||||
|
||||
public landLoc(): string {
|
||||
return this.settings.landLocation;
|
||||
}
|
||||
|
||||
public crop(): pond.Grain {
|
||||
return this.settings.crop;
|
||||
}
|
||||
|
||||
public name(): string {
|
||||
return this.settings.fieldName;
|
||||
}
|
||||
|
||||
public objectType(): pond.ObjectType {
|
||||
return pond.ObjectType.OBJECT_TYPE_FIELD;
|
||||
}
|
||||
|
||||
public objectTypeString(): string {
|
||||
return "Field";
|
||||
}
|
||||
|
||||
public grain(): pond.Grain {
|
||||
return this.settings.crop;
|
||||
}
|
||||
|
||||
public subtype(): string {
|
||||
return this.settings.grainSubtype;
|
||||
}
|
||||
|
||||
public customType(): string {
|
||||
let c = "";
|
||||
if (this.settings) {
|
||||
c = this.settings.customGrain;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public acres(): number {
|
||||
if (this.settings.acres > 0) {
|
||||
return this.settings.acres;
|
||||
} else {
|
||||
return this.calculateAcres();
|
||||
}
|
||||
}
|
||||
|
||||
//these are for when using the field as a source for a grain transaction to compare if the destination matches
|
||||
public storage(): pond.BinStorage {
|
||||
let storage = pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
|
||||
if (this.settings.crop === pond.Grain.GRAIN_CUSTOM) {
|
||||
storage = pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN;
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the bushels per tonne set in the bins settings, if not set will return 1
|
||||
* @returns 1 or bushels per tonne
|
||||
*/
|
||||
public bushelsPerTonne(): number {
|
||||
//trying to avoid a divide by 0 error by only returning a value greater than 0
|
||||
//since to get the weight you divide the current bushels by the bushels per tonne
|
||||
let bpt = 1;
|
||||
if (this.settings.bushelsPerTonne > 0) {
|
||||
bpt = this.settings.bushelsPerTonne;
|
||||
}
|
||||
return bpt;
|
||||
}
|
||||
|
||||
public center(): pond.Coordinates {
|
||||
let coords = pond.Coordinates.create();
|
||||
if (this.settings.fieldGeoData) {
|
||||
let minLong = 0;
|
||||
let minLat = 0;
|
||||
let maxLong = 0;
|
||||
let maxLat = 0;
|
||||
this.settings.fieldGeoData.shapes.forEach(shape => {
|
||||
shape.points.forEach(pair => {
|
||||
if (pair.longitude < minLong || minLong === 0) minLong = pair.longitude;
|
||||
if (pair.longitude > maxLong || maxLong === 0) maxLong = pair.longitude;
|
||||
if (pair.latitude < minLat || minLat === 0) minLat = pair.latitude;
|
||||
if (pair.latitude > maxLat || maxLat === 0) maxLat = pair.latitude;
|
||||
});
|
||||
});
|
||||
coords.longitude = (maxLong + minLong) / 2;
|
||||
coords.latitude = (maxLat + minLat) / 2;
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
}
|
||||
117
src/models/GeometryMapping.ts
Normal file
117
src/models/GeometryMapping.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
|
||||
// interface properties {
|
||||
// exclusion: boolean;
|
||||
// field: string;
|
||||
// main: boolean;
|
||||
// color: string;
|
||||
// }
|
||||
|
||||
interface geoJSONGeometry {
|
||||
coordinates: any[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeometryMapping is a geoJSON structure that React-map-gl's map layers needs in order to draw the shape on the map
|
||||
*/
|
||||
export class GeometryMapping {
|
||||
public type: "Feature" = "Feature";
|
||||
|
||||
public geometry: geoJSONGeometry = {
|
||||
coordinates: [],
|
||||
type: ""
|
||||
};
|
||||
|
||||
public static geoJSON(shape: string, shapes: pond.Shape[], holes?: pond.Shape[]) {
|
||||
let gm = new GeometryMapping();
|
||||
gm.geometry = getGeometry(shape, shapes, holes);
|
||||
return gm;
|
||||
}
|
||||
}
|
||||
|
||||
//helper function to build the correct geometry for the desired shape
|
||||
export function getGeometry(
|
||||
shape: string,
|
||||
shapes: pond.Shape[],
|
||||
holes?: pond.Shape[]
|
||||
): { coordinates: any[]; type: string } {
|
||||
let geo: geoJSONGeometry = {
|
||||
coordinates: [],
|
||||
type: shape
|
||||
};
|
||||
|
||||
switch (shape) {
|
||||
case "LineString":
|
||||
if (shapes[0]) {
|
||||
shapes[0].points.forEach(point => {
|
||||
geo.coordinates.push([point.longitude, point.latitude]);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Polygon":
|
||||
if (shapes[0]) {
|
||||
let shapeArray: any[] = [];
|
||||
shapes[0].points.forEach(point => {
|
||||
shapeArray.push([point.longitude, point.latitude]);
|
||||
});
|
||||
geo.coordinates.push(shapeArray);
|
||||
}
|
||||
if (holes) {
|
||||
holes.forEach(hole => {
|
||||
let holeArray: any[] = [];
|
||||
hole.points.forEach(point => {
|
||||
holeArray.push([point.longitude, point.latitude]);
|
||||
});
|
||||
geo.coordinates.push(holeArray);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "MultiPolygon":
|
||||
//this is an array of polygons (one polygon is an array of shapes, the first being the border, and all subsequent shapes being holes in that border)
|
||||
shapes.forEach(shape => {
|
||||
let polygon: any[] = [];
|
||||
let shapeArray: any[] = [];
|
||||
shape.points.forEach(point => {
|
||||
shapeArray.push([point.longitude, point.latitude]);
|
||||
});
|
||||
polygon.push(shapeArray);
|
||||
//just note JD and CNH dont do holes in their fields so this part will be UNTESTED until handling drawing multi polygons for our fields
|
||||
if (holes) {
|
||||
let holeArray: any[] = [];
|
||||
holes.forEach(hole => {
|
||||
if (hole.key === shape.key) {
|
||||
hole.points.forEach(point => {
|
||||
holeArray.push([point.longitude, point.latitude]);
|
||||
});
|
||||
}
|
||||
polygon.push(holeArray);
|
||||
});
|
||||
}
|
||||
geo.coordinates.push(polygon);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return geo;
|
||||
}
|
||||
|
||||
/**
|
||||
* takes in a 2D array of numbers an builds then into a shape object that can be added to either the holes or shapes of geoData
|
||||
* @param coordinates an array of number pairs
|
||||
* @returns pond.shape object using that array
|
||||
*/
|
||||
export function shapeFromCoords(coordinates: number[][]): pond.Shape {
|
||||
let shape = pond.Shape.create();
|
||||
coordinates.forEach(pair => {
|
||||
shape.points.push(
|
||||
pond.Coordinates.create({
|
||||
longitude: pair[0],
|
||||
latitude: pair[1]
|
||||
})
|
||||
);
|
||||
});
|
||||
return shape;
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export * from "./Tag";
|
|||
// export * from "./Upgrade";
|
||||
// export * from "./User";
|
||||
export * from "./user";
|
||||
// export * from "./Field";
|
||||
export * from "./Field";
|
||||
// export * from "./Team";
|
||||
export * from "./team";
|
||||
// export * from "./HarvestPlan";
|
||||
|
|
@ -22,4 +22,5 @@ export * from "./team";
|
|||
export * from "./Note";
|
||||
// export * from "./FieldMapping";
|
||||
// export * from "./FieldMarker";
|
||||
// export * from "./GrainBag";
|
||||
export * from "./GrainBag";
|
||||
export * from "./Contract";
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ import BinSettings from "bin/BinSettings";
|
|||
import AddBinFab from "bin/AddBinFab";
|
||||
import { GrainBag } from "models/GrainBag";
|
||||
import { getThemeType } from "theme/themeType";
|
||||
import GrainBagSettings from "grainBag/grainBagSettings";
|
||||
// import { useHistory } from "react-router";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
|
|
@ -1311,7 +1312,7 @@ export default function Bins(props: Props) {
|
|||
</Grid>
|
||||
<Grid item>
|
||||
<Box display="flex" paddingY={1} alignContent="center" alignItems="center">
|
||||
{/* <StyledToggleButtonGroup
|
||||
<StyledToggleButtonGroup
|
||||
id="tour-graph-tabs"
|
||||
value={bagView}
|
||||
exclusive
|
||||
|
|
@ -1325,7 +1326,7 @@ export default function Bins(props: Props) {
|
|||
sessionStorage.setItem("bagsView", "grid");
|
||||
}}>
|
||||
<ViewComfy />
|
||||
</StyledToggle> */}
|
||||
</StyledToggle>
|
||||
{/* hidden at dustins request so that grid view and list are the only two */}
|
||||
{/* <StyledToggle
|
||||
value={"scroll"}
|
||||
|
|
@ -1336,7 +1337,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewColumn />
|
||||
</StyledToggle> */}
|
||||
{/* <StyledToggle
|
||||
<StyledToggle
|
||||
value={"list"}
|
||||
aria-label="list view"
|
||||
onClick={() => {
|
||||
|
|
@ -1345,7 +1346,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewList />
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup> */}
|
||||
</StyledToggleButtonGroup>
|
||||
<IconButton
|
||||
className={classes.icon}
|
||||
onClick={() => {
|
||||
|
|
@ -1434,13 +1435,13 @@ export default function Bins(props: Props) {
|
|||
}
|
||||
}}
|
||||
/>
|
||||
{/* <GrainBagSettings
|
||||
<GrainBagSettings
|
||||
open={addBagOpen}
|
||||
close={() => {
|
||||
setAddBagOpen(false);
|
||||
}}
|
||||
/>
|
||||
<QrCodeGenerator
|
||||
{/* <QrCodeGenerator
|
||||
open={openQrGenerator}
|
||||
close={() => {
|
||||
setOpenQrGenerator(false);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export {
|
|||
// useComponentWebsocket,
|
||||
useDeviceAPI,
|
||||
// useDeviceWebsocket,
|
||||
// useFieldAPI,
|
||||
useFieldAPI,
|
||||
// useFieldMarkerAPI,
|
||||
useFirmwareAPI,
|
||||
useGroupAPI,
|
||||
|
|
@ -40,9 +40,9 @@ export {
|
|||
useGateAPI,
|
||||
// useObjectHeaterAPI,
|
||||
// useMutationAPI,
|
||||
// useGrainBagAPI,
|
||||
// useTransactionAPI,
|
||||
// useFileControllerAPI
|
||||
useGrainBagAPI,
|
||||
useTransactionAPI,
|
||||
useFileControllerAPI
|
||||
} from "./pond/pond";
|
||||
// export { SecurityContext, useSecurity } from "./security";
|
||||
export { SnackbarContext, useSnackbar } from "./Snackbar";
|
||||
|
|
|
|||
104
src/providers/pond/fieldAPI.tsx
Normal file
104
src/providers/pond/fieldAPI.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { useHTTP } from "hooks";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||
import { or } from "utils";
|
||||
import { pondURL } from "./pond";
|
||||
|
||||
export interface IFieldAPIContext {
|
||||
addField: (field: pond.FieldSettings) => Promise<any>;
|
||||
getField: (fieldId: string) => Promise<any>;
|
||||
listFields: (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order?: "asc" | "desc",
|
||||
orderBy?: string,
|
||||
search?: string,
|
||||
as?: string,
|
||||
asRoot?: boolean
|
||||
) => Promise<AxiosResponse<pond.ListFieldsResponse>>;
|
||||
removeField: (key: string) => Promise<AxiosResponse<pond.RemoveFieldResponse>>;
|
||||
updateField: (
|
||||
key: string,
|
||||
field: pond.FieldSettings,
|
||||
asRoot?: true
|
||||
) => Promise<AxiosResponse<pond.UpdateSiteResponse>>;
|
||||
}
|
||||
|
||||
export const FieldAPIContext = createContext<IFieldAPIContext>({} as IFieldAPIContext);
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function FieldProvider(props: PropsWithChildren<Props>) {
|
||||
const { children } = props;
|
||||
const { get, del, post, put } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
const addField = (field: pond.FieldSettings) => {
|
||||
if (as) return post<pond.AddFieldResponse>(pondURL("/fields?as=" + as), field);
|
||||
return post(pondURL("/fields"), field);
|
||||
};
|
||||
|
||||
const getField = (fieldId: string) => {
|
||||
if (as) return get(pondURL("/field/" + fieldId + "?as=" + as));
|
||||
return get(pondURL("/field/" + fieldId));
|
||||
};
|
||||
|
||||
const removeField = (key: string) => {
|
||||
if (as) return del<pond.RemoveFieldResponse>(pondURL("/fields/" + key + "?as=" + as));
|
||||
return del<pond.RemoveFieldResponse>(pondURL("/fields/" + key));
|
||||
};
|
||||
|
||||
const listFields = (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order?: "asc" | "desc",
|
||||
orderBy?: string,
|
||||
search?: string,
|
||||
as?: string,
|
||||
asRoot?: boolean
|
||||
) => {
|
||||
return get<pond.ListFieldsResponse>(
|
||||
pondURL(
|
||||
"/fields" +
|
||||
"?limit=" +
|
||||
limit +
|
||||
"&offset=" +
|
||||
offset +
|
||||
("&order=" + or(order, "asc")) +
|
||||
("&by=" + or(orderBy, "key")) +
|
||||
(as ? "&as=" + as : "") +
|
||||
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
||||
(search ? "&search=" + search : "")
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const updateField = (key: string, field: pond.FieldSettings, asRoot?: boolean) => {
|
||||
if (as)
|
||||
return put<pond.UpdateFieldResponse>(
|
||||
pondURL("/fields/" + key + "?as=" + as + (asRoot ? "&asRoot=" + asRoot.toString() : "")),
|
||||
field
|
||||
);
|
||||
return put<pond.UpdateFieldResponse>(
|
||||
pondURL("/fields/" + key + (asRoot ? "?asRoot=" + asRoot.toString() : "")),
|
||||
field
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldAPIContext.Provider
|
||||
value={{
|
||||
addField,
|
||||
getField,
|
||||
listFields,
|
||||
removeField,
|
||||
updateField
|
||||
}}>
|
||||
{children}
|
||||
</FieldAPIContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useFieldAPI = () => useContext(FieldAPIContext);
|
||||
122
src/providers/pond/fileControllerAPI.tsx
Normal file
122
src/providers/pond/fileControllerAPI.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { useHTTP } from "hooks";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers/StateContainer";
|
||||
import { createContext, PropsWithChildren, useContext } from "react";
|
||||
import { pondURL } from "./pond";
|
||||
|
||||
export interface toAttach {
|
||||
key: string;
|
||||
type: pond.ObjectType;
|
||||
}
|
||||
|
||||
export interface IFileControllerAPI {
|
||||
uploadFiles: (
|
||||
files: FileList,
|
||||
toAttach?: toAttach,
|
||||
keys?: string[],
|
||||
types?: string[]
|
||||
) => Promise<AxiosResponse<pond.AddFileResponse>>;
|
||||
listFileRefs: (
|
||||
objectKey: string,
|
||||
keys: string[],
|
||||
types: string[]
|
||||
) => Promise<AxiosResponse<pond.ListFileRefsResponse>>;
|
||||
retrieveFile: (path: string) => Promise<any>;
|
||||
deleteFile: (fileID: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export const FileControllerAPIContext = createContext<IFileControllerAPI>({} as IFileControllerAPI);
|
||||
|
||||
export default function FileControllerProvider(props: PropsWithChildren<any>) {
|
||||
const { children } = props;
|
||||
const [{ as }] = useGlobalState();
|
||||
const { get, post, del } = useHTTP();
|
||||
|
||||
const uploadFiles = (files: FileList, toAttach?: toAttach, keys?: string[], types?: string[]) => {
|
||||
let formData = new FormData();
|
||||
Array.from(files).forEach((f, i) => {
|
||||
formData.append("file" + i, f);
|
||||
});
|
||||
let url = pondURL(
|
||||
"/fileControl/upload" +
|
||||
(toAttach ? "?toAttach=" + toAttach.key + "&objectType=" + toAttach.type : "") +
|
||||
(keys ? "&keys=" + keys.join(",") : "") +
|
||||
(types ? "&types=" + types.join(",") : "")
|
||||
);
|
||||
if (as) {
|
||||
url = pondURL(
|
||||
"/fileControl/upload?as=" +
|
||||
as +
|
||||
(toAttach ? "&toAttach=" + toAttach.key + "&objectType=" + toAttach.type : "") +
|
||||
(keys ? "&keys=" + keys.join(",") : "") +
|
||||
(types ? "&types=" + types.join(",") : "")
|
||||
);
|
||||
}
|
||||
return post<pond.AddFileResponse>(url, formData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to get the file references from the backend.
|
||||
* Note: the object key is what the files are attached to and not used for the permissions,
|
||||
* For example on a contract the files are linked to the transaction
|
||||
* however you would check the users permission to the contract
|
||||
* @param objectKey what the files are attached to
|
||||
* @param keys the key of the object you want to check permissions for
|
||||
* @param types the type of the object you want to check permissions for
|
||||
* @returns promise that contains a list of file references in the data
|
||||
*/
|
||||
const listFileRefs = (objectKey: string, keys: string[], types: string[]) => {
|
||||
let url = pondURL(
|
||||
"/fileControl/list?objectKey=" +
|
||||
objectKey +
|
||||
"&keys=" +
|
||||
keys.join(",") +
|
||||
"&types=" +
|
||||
types.join(",")
|
||||
);
|
||||
if (as) {
|
||||
url = pondURL(
|
||||
"/fileControl/list?as=" +
|
||||
as +
|
||||
"&objectKey=" +
|
||||
objectKey +
|
||||
"&keys=" +
|
||||
keys.join(",") +
|
||||
"&types=" +
|
||||
types.join(",")
|
||||
);
|
||||
}
|
||||
return get<pond.ListFileRefsResponse>(url);
|
||||
};
|
||||
|
||||
const retrieveFile = (path: string) => {
|
||||
let url = pondURL("/fileControl/retrieve?path=" + path);
|
||||
if (as) {
|
||||
url = pondURL("/fileControl/retrieve?as=" + as + "&path=" + path);
|
||||
}
|
||||
return get(url, { responseType: "blob" });
|
||||
};
|
||||
|
||||
const deleteFile = (fileID: string) => {
|
||||
let url = pondURL("/fileControl/" + fileID + "/delete");
|
||||
if (as) {
|
||||
url = pondURL("/fileControl/" + fileID + "/delete?as=" + as);
|
||||
}
|
||||
return del(url, { responseType: "blob" });
|
||||
};
|
||||
|
||||
return (
|
||||
<FileControllerAPIContext.Provider
|
||||
value={{
|
||||
uploadFiles,
|
||||
listFileRefs,
|
||||
retrieveFile,
|
||||
deleteFile
|
||||
}}>
|
||||
{children}
|
||||
</FileControllerAPIContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useFileControllerAPI = () => useContext(FileControllerAPIContext);
|
||||
161
src/providers/pond/grainBagAPI.tsx
Normal file
161
src/providers/pond/grainBagAPI.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
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";
|
||||
import { GrainBag } from "models/GrainBag";
|
||||
|
||||
export interface IGrainBagInterface {
|
||||
addGrainBag: (
|
||||
name: string,
|
||||
settings: pond.GrainBagSettings
|
||||
) => Promise<AxiosResponse<pond.AddGrainBagResponse>>;
|
||||
listGrainBags: (
|
||||
limit: number,
|
||||
offset: number,
|
||||
order?: "asc" | "desc",
|
||||
orderBy?: string,
|
||||
search?: string,
|
||||
keys?: string[],
|
||||
types?: string[],
|
||||
numerical?: boolean,
|
||||
specificUser?: string
|
||||
) => Promise<AxiosResponse<pond.ListGrainBagsResponse>>;
|
||||
updateGrainBag: (
|
||||
key: string,
|
||||
name: string,
|
||||
settings: pond.GrainBagSettings
|
||||
) => Promise<AxiosResponse<pond.UpdateGrainBagResponse>>;
|
||||
bulkUpdateGrainBags: (
|
||||
bags: pond.GrainBag[]
|
||||
) => Promise<AxiosResponse<pond.BulkGrainBagUpdateResponse>>;
|
||||
getGrainBag: (key: string) => Promise<AxiosResponse<pond.GetGrainBagResponse>>;
|
||||
removeGrainBag: (key: string) => Promise<AxiosResponse<pond.RemoveGrainBagResponse>>;
|
||||
listHistory: (
|
||||
id: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
start?: string,
|
||||
end?: string
|
||||
) => Promise<AxiosResponse<pond.ListGrainBagHistoryResponse>>;
|
||||
}
|
||||
|
||||
export const GrainBagAPIcontext = createContext<IGrainBagInterface>({} as IGrainBagInterface);
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function GrainBagProvider(props: PropsWithChildren<Props>) {
|
||||
const { children } = props;
|
||||
const { get, del, post, put } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
const addGrainBag = (name: string, settings: pond.GrainBagSettings) => {
|
||||
if (as) {
|
||||
return post<pond.AddGrainBagResponse>(
|
||||
pondURL("/grainbags?name=" + name + "&as=" + as),
|
||||
settings
|
||||
);
|
||||
}
|
||||
return post<pond.AddGrainBagResponse>(pondURL("/grainbags?name=" + name), settings);
|
||||
};
|
||||
|
||||
const listGrainBags = (
|
||||
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.ListGrainBagsResponse>(
|
||||
pondURL(
|
||||
"/grainbags?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 updateGrainBag = (key: string, name: string, settings: pond.GrainBagSettings) => {
|
||||
if (as) {
|
||||
return put<pond.UpdateGrainBagResponse>(
|
||||
pondURL("/grainbags/" + key + "?as=" + as + "&name=" + name),
|
||||
settings
|
||||
);
|
||||
}
|
||||
return put<pond.UpdateGrainBagResponse>(
|
||||
pondURL("/grainbags/" + key + "?name=" + name),
|
||||
settings
|
||||
);
|
||||
};
|
||||
|
||||
const bulkUpdateGrainBags = (bags: pond.GrainBag[]) => {
|
||||
if (as)
|
||||
return put<pond.BulkGrainBagUpdateResponse>(pondURL("/bulkGrainBags/update?as=" + as), {
|
||||
bags: bags
|
||||
});
|
||||
return put<pond.BulkGrainBagUpdateResponse>(pondURL("/bulkGrainBags/update"), { bags: bags });
|
||||
};
|
||||
|
||||
const removeGrainBag = (key: string) => {
|
||||
if (as) {
|
||||
return del<pond.RemoveGrainBagResponse>(pondURL("/grainbags/" + key + "?as=" + as));
|
||||
}
|
||||
return del<pond.RemoveGrainBagResponse>(pondURL("/grainbags/" + key + "?as=" + as));
|
||||
};
|
||||
|
||||
const getGrainBag = (key: string) => {
|
||||
if (as) {
|
||||
return get<pond.GetGrainBagResponse>(pondURL("/grainbags/" + key + "?as=" + as));
|
||||
}
|
||||
return get<pond.GetGrainBagResponse>(pondURL("/grainbags/" + key));
|
||||
};
|
||||
|
||||
const listHistory = (id: string, limit: number, offset: number, start?: string, end?: string) => {
|
||||
return get<pond.ListGrainBagHistoryResponse>(
|
||||
pondURL(
|
||||
"/grainbags/" +
|
||||
id +
|
||||
"/history?limit=" +
|
||||
limit +
|
||||
"&offset=" +
|
||||
offset +
|
||||
(start && "&start=" + start) +
|
||||
(end && "&end=" + end) +
|
||||
(as && "&as=" + as)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<GrainBagAPIcontext.Provider
|
||||
value={{
|
||||
addGrainBag,
|
||||
listGrainBags,
|
||||
updateGrainBag,
|
||||
bulkUpdateGrainBags,
|
||||
removeGrainBag,
|
||||
getGrainBag,
|
||||
listHistory
|
||||
}}>
|
||||
{children}
|
||||
</GrainBagAPIcontext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useGrainBagAPI = () => useContext(GrainBagAPIcontext);
|
||||
|
|
@ -16,6 +16,10 @@ import FirmwareProvider, { useFirmwareAPI } from "./firmwareAPI";
|
|||
import ComponentProvider, { useComponentAPI } from "./componentAPI";
|
||||
import InteractionProvider, { useInteractionsAPI } from "./interactionsAPI";
|
||||
import BinYardProvider, { useBinYardAPI } from "./binYardAPI";
|
||||
import GrainBagProvider, { useGrainBagAPI } from "./grainBagAPI";
|
||||
import FieldProvider, { useFieldAPI } from "./fieldAPI";
|
||||
import TransactionProvider, { useTransactionAPI } from "./transactionAPI";
|
||||
import FileControllerProvider, { useFileControllerAPI } from "./fileControllerAPI";
|
||||
// import NoteProvider from "providers/noteAPI";
|
||||
|
||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||
|
|
@ -48,7 +52,15 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
|||
<ComponentProvider>
|
||||
<InteractionProvider>
|
||||
<BinYardProvider>
|
||||
{children}
|
||||
<GrainBagProvider>
|
||||
<FieldProvider>
|
||||
<TransactionProvider>
|
||||
<FileControllerProvider>
|
||||
{children}
|
||||
</FileControllerProvider>
|
||||
</TransactionProvider>
|
||||
</FieldProvider>
|
||||
</GrainBagProvider>
|
||||
</BinYardProvider>
|
||||
</InteractionProvider>
|
||||
</ComponentProvider>
|
||||
|
|
@ -82,6 +94,10 @@ export {
|
|||
useBinYardAPI,
|
||||
useGateAPI,
|
||||
useGroupAPI,
|
||||
useGrainBagAPI,
|
||||
useNoteAPI,
|
||||
useNotificationAPI
|
||||
useNotificationAPI,
|
||||
useFieldAPI,
|
||||
useTransactionAPI,
|
||||
useFileControllerAPI,
|
||||
};
|
||||
|
|
|
|||
127
src/providers/pond/transactionAPI.tsx
Normal file
127
src/providers/pond/transactionAPI.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
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 ITransactionAPIContext {
|
||||
addTransaction: (
|
||||
transaction: pond.Transaction,
|
||||
fileIDs?: string[]
|
||||
) => Promise<AxiosResponse<pond.AddTransactionResponse>>;
|
||||
transactionPageData: (
|
||||
start: string,
|
||||
end: string
|
||||
) => Promise<AxiosResponse<pond.TransactionPageDataResponse>>;
|
||||
listTransactions: (
|
||||
start: string,
|
||||
end: string,
|
||||
fromObject?: pond.ObjectType,
|
||||
toObject?: pond.ObjectType,
|
||||
fromKey?: string,
|
||||
toKey?: string
|
||||
) => Promise<AxiosResponse<pond.ListTransactionsResponse>>;
|
||||
revokeTransaction: (key: string) => Promise<AxiosResponse<pond.RevokeTransactionResponse>>;
|
||||
updateTransaction: (
|
||||
key: string,
|
||||
data: pond.TransactionData
|
||||
) => Promise<AxiosResponse<pond.UpdateTransactionResponse>>;
|
||||
}
|
||||
|
||||
export const TransactionAPIContext = createContext<ITransactionAPIContext>(
|
||||
{} as ITransactionAPIContext
|
||||
);
|
||||
|
||||
interface Props {}
|
||||
|
||||
export default function TransactionProvider(props: PropsWithChildren<Props>) {
|
||||
const { children } = props;
|
||||
const { get, post, put } = useHTTP();
|
||||
const [{ as }] = useGlobalState();
|
||||
|
||||
const addTransaction = (transaction: pond.Transaction, fileIDs?: string[]) => {
|
||||
if (as)
|
||||
return post<pond.AddTransactionResponse>(
|
||||
pondURL(
|
||||
"/transactions?as=" + as + (fileIDs ? "&fileAttachments=" + fileIDs.toString() : "")
|
||||
),
|
||||
transaction
|
||||
);
|
||||
return post<pond.AddTransactionResponse>(
|
||||
pondURL("/transactions" + (fileIDs ? "?fileAttachments=" + fileIDs.toString() : "")),
|
||||
transaction
|
||||
);
|
||||
};
|
||||
|
||||
const listTransactions = (
|
||||
start: string,
|
||||
end: string,
|
||||
fromObject?: pond.ObjectType,
|
||||
toObject?: pond.ObjectType,
|
||||
fromKey?: string,
|
||||
toKey?: string
|
||||
) => {
|
||||
if (as) {
|
||||
return get<pond.ListTransactionsResponse>(
|
||||
pondURL("/transactions?as=" + as + "&start=" + start + "&end=" + end)
|
||||
);
|
||||
}
|
||||
return get<pond.ListTransactionsResponse>(
|
||||
pondURL(
|
||||
"/transactions?start=" +
|
||||
start +
|
||||
"&end=" +
|
||||
end +
|
||||
(fromObject ? "&fromObject=" + fromObject : "") +
|
||||
(toObject ? "&toObject=" + toObject : "") +
|
||||
(fromKey ? "&fromKey=" + fromKey : "") +
|
||||
(toKey ? "&toKey=" + toKey : "")
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const updateTransaction = (key: string, data: pond.TransactionData) => {
|
||||
if (as)
|
||||
return post<pond.UpdateTransactionResponse>(
|
||||
pondURL("/transactions/" + key + "/update?as=" + as),
|
||||
data
|
||||
);
|
||||
return post<pond.UpdateTransactionResponse>(pondURL("/transactions/" + key + "/update"), data);
|
||||
};
|
||||
|
||||
const transactionPageData = (start: string, end: string) => {
|
||||
if (as) {
|
||||
return get<pond.TransactionPageDataResponse>(
|
||||
pondURL("/transactionsPage?as=" + as + "&start=" + start + "&end=" + end)
|
||||
);
|
||||
}
|
||||
return get<pond.TransactionPageDataResponse>(
|
||||
pondURL("/transactionsPage?start=" + start + "&end=" + end)
|
||||
);
|
||||
};
|
||||
|
||||
const revokeTransaction = (key: string) => {
|
||||
if (as) {
|
||||
return put<pond.RevokeTransactionResponse>(
|
||||
pondURL("/transactions/" + key + "/revoke?as=" + as)
|
||||
);
|
||||
}
|
||||
return put<pond.TransactionPageDataResponse>(pondURL("/transactions/" + key + "/revoke"));
|
||||
};
|
||||
|
||||
return (
|
||||
<TransactionAPIContext.Provider
|
||||
value={{
|
||||
addTransaction,
|
||||
listTransactions,
|
||||
updateTransaction,
|
||||
transactionPageData,
|
||||
revokeTransaction
|
||||
}}>
|
||||
{children}
|
||||
</TransactionAPIContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTransactionAPI = () => useContext(TransactionAPIContext);
|
||||
3
src/theme/index.ts
Normal file
3
src/theme/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./text";
|
||||
export { CreateTheme } from "./theme";
|
||||
export * from "./themeType";
|
||||
Loading…
Add table
Add a link
Reference in a new issue