added binyard and grainbag stuff

This commit is contained in:
Carter 2025-03-11 13:35:15 -06:00
parent e52a9a4fbf
commit 235464bb93
11 changed files with 1553 additions and 33 deletions

View file

@ -0,0 +1,207 @@
import {
createStyles,
IconButton,
ListItemIcon,
ListItemText,
makeStyles,
Menu,
MenuItem,
Tooltip
} from "@material-ui/core";
import SettingsIcon from "@material-ui/icons/Settings";
import MoreIcon from "@material-ui/icons/MoreVert";
import React, { useState } from "react";
import ObjectUsersIcon from "@material-ui/icons/AccountCircle";
import ObjectTeamsIcon from "@material-ui/icons/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 "@material-ui/core/colors";
import RemoveSelfIcon from "@material-ui/icons/ExitToApp";
import { Share } from "@material-ui/icons";
import { GrainBag } from "models/GrainBag";
import GrainBagSettings from "./grainBagSettings";
const useStyles = makeStyles(() => {
return createStyles({
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"]
}
}
});
});
interface OpenState {
users: boolean;
teams: boolean;
settings: boolean;
removeSelf: boolean;
share: boolean;
}
interface Props {
grainBag: GrainBag;
refreshCallback: (updatedBag: GrainBag) => void;
permissions: pond.Permission[];
}
export default function GrainBagActions(props: Props) {
const classes = useStyles();
const { grainBag, 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>
<MenuItem
onClick={() => {
setOpenState({ ...openState, share: true });
setAnchorEl(null);
}}
button
dense>
<ListItemIcon>
<Share className={classes.blueIcon} />
</ListItemIcon>
<ListItemText secondary="Share" />
</MenuItem>
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, users: true });
setAnchorEl(null);
}}
button>
<ListItemIcon>
<ObjectUsersIcon />
</ListItemIcon>
<ListItemText primary="Users" />
</MenuItem>
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, teams: true });
setAnchorEl(null);
}}
button>
<ListItemIcon>
<ObjectTeamsIcon />
</ListItemIcon>
<ListItemText primary="Teams" />
</MenuItem>
<MenuItem
dense
onClick={() => {
setOpenState({ ...openState, removeSelf: true });
setAnchorEl(null);
}}
button>
<ListItemIcon>
<RemoveSelfIcon className={classes.red} />
</ListItemIcon>
<ListItemText primary="Leave" />
</MenuItem>
</Menu>
);
};
const dialogs = () => {
const key = grainBag.key;
const label = grainBag.name();
return (
<React.Fragment>
<GrainBagSettings
open={openState.settings}
grainBag={grainBag}
close={updatedBag => {
if (updatedBag) {
refreshCallback(updatedBag);
}
setOpenState({ ...openState, settings: false });
}}
/>
<ShareObject
scope={{ kind: "grainbag", key: key() } as Scope}
label={label}
permissions={permissions}
isDialogOpen={openState.share}
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
/>
<ObjectUsers
scope={{ kind: "grainbag", key: key() } as Scope}
label={label}
permissions={permissions}
isDialogOpen={openState.users}
closeDialogCallback={() => setOpenState({ ...openState, users: false })}
refreshCallback={refreshCallback}
/>
<ObjectTeams
scope={{ kind: "grainbag", key: key() } as Scope}
label={label}
permissions={permissions}
isDialogOpen={openState.teams}
closeDialogCallback={() => setOpenState({ ...openState, teams: false })}
refreshCallback={refreshCallback}
/>
<RemoveSelfFromObject
scope={{ kind: "grainbag", key: key() } as Scope}
path={"bins"}
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>
);
}

View file

@ -0,0 +1,85 @@
import { Box, Card, Typography } from "@material-ui/core";
import GrainDescriber from "grain/GrainDescriber";
import { GrainBag } from "models/GrainBag";
import moment from "moment";
import React, { useEffect, useState } from "react";
import GrainBagSVG from "./grainBagSVG";
interface Props {
grainBag: GrainBag;
}
export default function GrainBagCard(props: Props) {
const { grainBag } = props;
const [fillLevel, setFillLevel] = useState(0);
useEffect(() => {
setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100);
}, [grainBag]);
const timeDisplay = () => {
let now = moment();
let duration = moment.duration(moment(grainBag.settings.fillDate).diff(now));
let days = duration.asDays();
days = Math.abs(days);
duration.subtract(moment.duration(days, "days"));
// let hours = duration.hours();
// hours = Math.abs(hours);
return Math.floor(days) + " days";
};
return (
<Card>
<Box padding={1}>
<Box>
<Typography
align="left"
variant="body1"
color="textPrimary"
noWrap
style={{ fontSize: "0.8rem", fontWeight: 700 }}>
{grainBag.name()}
</Typography>
</Box>
<Box display="flex" justifyContent="center" paddingTop={0}>
<Box padding={0} style={{ flex: 1, textAlign: "left", flexDirection: "column" }}>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{grainBag.isCustom()
? grainBag.settings.customGrain
: GrainDescriber(grainBag.settings.supportedGrain).name}
</Typography>
<Typography variant="subtitle2" style={{ fontSize: "0.7rem", fontWeight: 700 }}>
{grainBag.settings.grainSubtype}
</Typography>
<Box paddingTop={2} style={{ textAlign: "left" }}>
<Typography variant="subtitle2" style={{ fontSize: 10 }}>
Days in Field
</Typography>
<Typography variant="subtitle2" style={{ fontWeight: 700 }}>
{timeDisplay()}
</Typography>
<Typography variant="subtitle2" style={{ fontSize: 10 }}>
Initial Moisture
</Typography>
<Typography variant="subtitle2" style={{ fontWeight: 700 }}>
{grainBag.settings.initialMoisture}%
</Typography>
</Box>
</Box>
<Box padding={1} style={{ flex: 1, textAlign: "center", flexDirection: "column" }}>
<GrainBagSVG fillPercent={fillLevel} height={128} />
</Box>
</Box>
<Typography align="center" color="textSecondary" noWrap style={{ fontSize: "0.65rem" }}>
{grainBag.bushels() <= 0
? "empty"
: grainBag.bushels().toLocaleString() +
"/" +
grainBag.capacity().toLocaleString() +
" bu"}
</Typography>
</Box>
</Card>
);
}

View file

@ -0,0 +1,106 @@
import { Box, Card, CircularProgress, Typography } from "@material-ui/core";
import BarGraph, { BarData } from "charts/BarGraph";
import GrainDescriber from "grain/GrainDescriber";
import { GrainBag } from "models/GrainBag";
import moment, { Moment } from "moment";
import { pond } from "protobuf-ts/pond";
import { useGrainBagAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
import { getGrainUnit } from "utils";
interface Props {
grainBag: GrainBag;
startDate: Moment;
endDate: Moment;
customHeight?: number | string;
}
export default function GrainBagInventoryGraph(props: Props) {
const { grainBag, startDate, endDate, customHeight } = props;
const grainBagAPI = useGrainBagAPI();
const [data, setData] = useState<BarData[]>([]);
const [loadingData, setLoadingData] = useState<boolean>(false);
const { openSnack } = useSnackbar();
useEffect(() => {
if (grainBag.key() && !loadingData) {
setLoadingData(true);
grainBagAPI
.listHistory(grainBag.key(), 500, 0, startDate.toISOString(), endDate.toISOString())
.then(resp => {
let barData: BarData[] = [];
let lastBushels = 0;
resp.data.history.forEach(hist => {
if (hist.object?.grainBagSettings) {
//let time = hist.timestamp;
let val = hist.object.grainBagSettings.currentBushels ?? 0;
if (
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
grainBag.bushelsPerTonne() > 1
) {
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
}
if (val !== lastBushels) {
lastBushels = val;
barData.push({
timestamp: moment(hist.timestamp).valueOf(),
value: val
});
}
}
});
if (barData.length === 0) {
barData.push({
timestamp: moment.now().valueOf(),
value:
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT &&
grainBag.bushelsPerTonne() > 1
? Math.round((grainBag.bushels() / grainBag.bushelsPerTonne()) * 100) / 100
: grainBag.bushels()
});
}
setData(barData);
})
.catch(err => {
openSnack("There was a problem retrieving inventory information");
})
.finally(() => {
setLoadingData(false);
});
}
}, [grainBagAPI, grainBag, startDate, endDate, openSnack]); //eslint-disable-line react-hooks/exhaustive-deps
return (
<Card raised>
<Box padding={2}>
<Typography variant="h6" style={{ fontWeight: "bold" }}>
Inventory over Time
</Typography>
</Box>
{!loadingData && data.length > 0 ? (
<BarGraph
barColour={
grainBag.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
? GrainDescriber(grainBag.grain()).colour
: "yellow"
}
data={data}
yMin={0}
yMax={
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT
? grainBag.capacity() / grainBag.bushelsPerTonne()
: grainBag.capacity()
}
customHeight={customHeight}
useGradient
labels
labelColour="white"
/>
) : (
<Box height={400} display="flex" justifyContent="center" alignItems="center">
<CircularProgress />
</Box>
)}
</Card>
);
}

View file

@ -0,0 +1,92 @@
import { Box, Button, createStyles, Grid, makeStyles, Theme, Typography } from "@material-ui/core";
import React from "react";
import ScrollMenu from "react-horizontal-scroll-menu";
import { useHistory } from "react-router";
import { ArrowForward, ArrowBack } from "@material-ui/icons";
import { useMobile } from "hooks";
import { GrainBag } from "models/GrainBag";
import GrainBagCard from "./grainBagCard";
interface Props {
grainBags: GrainBag[];
title?: string;
gridView?: boolean;
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
gridListTile: {
minHeight: "184px",
height: "auto !important",
width: "184px",
padding: 2
},
hidden: {
visibility: "hidden"
}
})
);
export default function GrainBagList(props: Props) {
const { grainBags, title, gridView } = props;
const classes = useStyles();
const history = useHistory();
const isMobile = useMobile();
const goToBag = (i: number) => {
let path = "/grainbags/" + grainBags[i].key();
history.replace(path);
};
const scroll = () => {
return (
<React.Fragment>
<ScrollMenu
wheel={false}
alignCenter={false}
inertiaScrolling
hideArrows
hideSingleArrow
arrowDisabledClass={classes.hidden}
onSelect={e => {
goToBag(e as number);
}}
arrowLeft={
<Button style={{ height: 184, display: isMobile ? "none" : "block" }}>
<ArrowBack />
</Button>
}
arrowRight={
<Button style={{ height: 184, display: isMobile ? "none" : "block" }}>
<ArrowForward />
</Button>
}
data={grainBags.map((b, i) => (
<Box key={i} className={classes.gridListTile}>
<GrainBagCard grainBag={b} />
</Box>
))}
/>
</React.Fragment>
);
};
const grid = () => {
return (
<Grid container direction="row">
{grainBags.map((b, i) => (
<Box key={i} className={classes.gridListTile} onClick={() => goToBag(i)}>
<GrainBagCard grainBag={b} />
</Box>
))}
</Grid>
);
};
return (
<React.Fragment>
{title && <Typography>{title}</Typography>}
{gridView ? grid() : scroll()}
</React.Fragment>
);
}

View file

@ -0,0 +1,78 @@
import { Box, makeStyles, Theme, createStyles } from "@material-ui/core";
import React from "react";
const GRAIN_COLOUR = "#b5a962";
const useStyles = makeStyles((theme: Theme) => {
return createStyles({
bag: {
fill: theme.palette.type === "light" ? "#373737" : "#292929",
fillOpacity: 1
},
inventory: {
fill: GRAIN_COLOUR,
fillOpacity: 0.5
}
});
});
interface Props {
fillPercent: number;
height: number;
}
export default function GrainBagSVG(props: Props) {
const { fillPercent, height } = props;
const classes = useStyles();
const bagFillSVG = () => {
const topY = 173;
const bottomY = 235;
const fillLevel = ((bottomY - topY) * (100 - fillPercent)) / 100 + topY;
return (
<g>
{/* starts drawing top right corner */}
<path
className={classes.inventory}
d={
"M 193.27593," +
fillLevel + //increase the y value to make the drawing start lower
//this command makes the top curve
"c -0.23147,-0.43237 -0.46296,-0.84552 -0.71457,-1.24905 -2.15378,-2.45005 -6.6425,-4.13146 -11.83573,-4.13146 -5.19323,0 -9.68195,1.68141 -11.83574,4.12186 -0.25161,0.40353 -0.48309,0.82628 -0.71457,1.24904 " +
//this command draws down from the top left corner after finishing the first curve
"V 235 " +
//these 3 commands make the bottom curve
"c 0.7045,0.21137 1.44927,0.41314 2.22423,0.58609 0,0 0,0 0.0101,0 0.11071,0.01 0.20128,0.0192 0.2516,0.0288 0.27175,0.0769 0.55355,0.14412 0.84542,0.20177 0.0503,0.01 0.11071,0.0192 0.16103,0.0288 0.26167,0.048 0.53341,0.0961 0.80515,0.14412 0.0302,0 0.0503,0 0.0704,0.01 2.06321,0.31707 4.72021,0.43236 8.1421,0.43236 " +
"h 0.0906 " +
"c 3.41183,0 6.06883,-0.11529 8.1421,-0.43236 0.0201,0 0.0503,0 0.0704,-0.01 0.27174,-0.048 0.54348,-0.0961 0.80515,-0.14412 0.0503,0 0.10065,-0.0192 0.16104,-0.0288 0.30193,-0.0576 0.58373,-0.13451 0.84541,-0.20177 0.0503,0 0.13083,-0.0192 0.25161,-0.0288 0,0 0,0 0.0201,0 0.77497,-0.17295 1.50967,-0.37472 2.21417,-0.58609 " +
//this command connects where the line is at and the starting point
"z"
}
id="path275"
/>
</g>
);
};
return (
<Box height={height} width={height * 0.5}>
<svg width="100%" height="100%" viewBox="0 0 10 20">
<g id="layer1" transform="translate(-104.92152,-72.241116)">
<g id="g121" transform="matrix(0.26458333,0,0,0.26458333,62.153011,29.46019)">
{bagFillSVG()}
<g id="g339" transform="matrix(0.93700864,0,0,0.92692889,161.64576,161.68242)">
{/* this path draws the bag */}
<path
className={classes.bag}
d="M 38.95,75.7 C 38.57,74.77 38.45,73.76 38.63,72.77 39.06,70.37 39.12,67.29 39.12,63.36 39.12,56.26 37.16,13.93 33.97,9.14 33.19,7.97 32.9,6.54 33.21,5.17 33.38,4.45 33.47,3.66 33.51,2.84 33.54,2.26 32.97,1.84 32.42,2.02 30.3,2.7 22.38,0.74 20.22,0.74 20.2,0.74 20.18,0.74 20.16,0.74 20.14,0.74 20.12,0.74 20.1,0.74 17.93,0.74 10.02,2.7 7.9,2.02 7.35,1.84 6.78,2.26 6.81,2.84 6.85,3.66 6.94,4.45 7.11,5.17 7.43,6.54 7.13,7.97 6.35,9.14 3.16,13.93 1.21,56.25 1.21,63.35 1.21,67.29 1.27,70.36 1.7,72.76 1.88,73.75 1.77,74.76 1.38,75.69 0.98,76.65 0.8,77.87 0.73,79.13 0.7,79.71 1.26,80.14 1.81,79.96 3.78,79.33 7.32,79.72 7.79,79.77 10.57,80.57 14.51,80.83 20.11,80.83 20.13,80.83 20.15,80.83 20.17,80.83 20.19,80.83 20.21,80.83 20.23,80.83 25.83,80.83 29.77,80.57 32.55,79.77 33.02,79.71 36.55,79.32 38.53,79.96 39.08,80.14 39.64,79.71 39.61,79.13 39.55,77.87 39.36,76.66 38.96,75.69 Z M 32.63,75.79 C 31.93,76.01 31.2,76.22 30.43,76.4 30.43,76.4 30.42,76.4 30.41,76.4 30.29,76.41 30.21,76.42 30.16,76.43 29.89,76.51 29.61,76.58 29.32,76.64 29.27,76.65 29.22,76.66 29.16,76.67 28.9,76.72 28.63,76.77 28.36,76.82 28.34,76.82 28.31,76.82 28.29,76.83 26.24,77.16 23.6,77.28 20.2,77.28 H 20.11 C 16.72,77.28 14.08,77.16 12.02,76.83 12,76.83 11.98,76.83 11.95,76.82 11.68,76.77 11.41,76.72 11.15,76.67 11.1,76.67 11.05,76.65 10.99,76.64 10.69,76.58 10.41,76.5 10.15,76.43 10.1,76.43 10.02,76.41 9.9,76.4 9.9,76.4 9.9,76.4 9.89,76.4 9.12,76.22 8.38,76.01 7.68,75.79
V 13.2 C 7.91,12.75 8.14,12.32 8.39,11.9 10.53,9.36 14.99,7.61 20.15,7.61 25.31,7.61 29.77,9.36 31.91,11.9 32.16,12.32 32.39,12.76 32.62,13.2
V 75.78 Z"
id="path329"
/>
</g>
</g>
</g>
</svg>
</Box>
);
}

View file

@ -0,0 +1,456 @@
import {
Button,
createStyles,
DialogActions,
DialogContent,
DialogTitle,
Grid,
InputAdornment,
makeStyles,
Switch,
TextField,
Theme,
useTheme
} from "@material-ui/core";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
import GrainTransaction from "grain/GrainTransaction";
import { clone } from "lodash";
import { GrainBag } from "models/GrainBag";
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 { getGrainUnit } from "utils";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
bottomSpacing: {
marginBottom: theme.spacing(1)
}
})
);
interface Props {
open: boolean;
close: (updatedBag?: GrainBag) => void;
coordinates?: { start: pond.Coordinates; end: pond.Coordinates };
grainBag?: GrainBag;
}
export default function GrainBagSettings(props: Props) {
const { open, close, grainBag, coordinates } = props;
const classes = useStyles();
const grainBagAPI = useGrainBagAPI();
const [bagName, setBagName] = useState("");
const [bagDiameterM, setBagDiameterM] = useState(0);
const [bagLengthM, setBagLengthM] = useState(0);
const [bagDiameterDisplay, setBagDiameterDisplay] = useState(0);
const [bagLengthDispla, setBagLengthDisplay] = useState(0);
const [{ user }] = useGlobalState();
const [isCustom, setIsCustom] = useState<boolean>(false);
const [customType, setCustomType] = useState("");
const [supportedGrainType, setSupportedGrainType] = useState(pond.Grain.GRAIN_INVALID);
const [selectedGrainOp, setSelectedGrainOp] = useState<Option | null>(null);
const grainOptions = GrainOptions();
const [grainSubtype, setGrainSubtype] = useState("");
const [grainCapacity, setGrainCapacity] = useState(0);
const [grainFill, setGrainFill] = useState("");
const [grainBushels, setGrainBushels] = useState(0);
const [fillDate, setFillDate] = useState(moment().format("YYYY-MM-DD"));
const [initialMoisture, setInitialMoisture] = useState(0);
const history = useHistory();
const { openSnack } = useSnackbar();
const theme = useTheme();
const [grainUpdate, setGrainUpdate] = useState(false);
const [grainDiff, setGrainDiff] = useState(0);
const [bushelsPerTonne, setBushelsPerTonne] = useState(0);
useEffect(() => {
if (grainBag) {
//if a grainbag is passed in set all the state values
setBagDiameterM(grainBag.settings.diameter);
setBagDiameterDisplay(
user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET
? grainBag.settings.diameter * 3.281
: grainBag.settings.diameter
);
setBagLengthM(grainBag.settings.length);
setBagLengthDisplay(
user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET
? grainBag.settings.length * 3.281
: grainBag.settings.length
);
let grainVal = grainBag.bushels();
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT) {
grainVal = grainBag.bushels() / grainBag.bushelsPerTonne();
}
setGrainFill(grainVal.toFixed(2));
setBagName(grainBag.name());
setIsCustom(grainBag.isCustom());
setSupportedGrainType(grainBag.settings.supportedGrain);
setSelectedGrainOp(ToGrainOption(grainBag.settings.supportedGrain));
setCustomType(grainBag.settings.customGrain);
setGrainSubtype(grainBag.settings.grainSubtype);
setGrainCapacity(grainBag.capacity());
setGrainBushels(grainBag.bushels());
setFillDate(grainBag.settings.fillDate);
setInitialMoisture(grainBag.settings.initialMoisture);
}
}, [grainBag, user]);
const removeGrainBag = () => {
if (!grainBag) return;
grainBagAPI
.removeGrainBag(grainBag.key())
.then(resp => {
openSnack("Removed Grain Bag");
close();
history.push("/bins");
})
.catch(err => {
openSnack("Failed to remove Grain Bag");
});
};
const submit = () => {
//if a bag was passed in do an update
if (grainBag) {
grainBag.title = bagName;
grainBag.settings.initialMoisture = initialMoisture;
grainBag.settings.diameter = bagDiameterM;
grainBag.settings.length = bagLengthM;
grainBag.settings.customGrain = customType;
grainBag.settings.supportedGrain = supportedGrainType;
grainBag.settings.bushelCapacity = grainCapacity;
grainBag.settings.grainSubtype = grainSubtype;
grainBag.settings.fillDate = fillDate;
grainBag.settings.bushelsPerTonne = bushelsPerTonne;
grainBag.settings.storageType = isCustom
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
if (grainBag.bushels() !== grainBushels) {
//grainBag.settings.currentBushels = grainBushels;
setGrainDiff(grainBushels - grainBag.bushels());
setGrainUpdate(true);
} else {
grainBagAPI
.updateGrainBag(grainBag.key(), bagName, grainBag.settings)
.then(resp => {
openSnack("Grain Bag Updated");
let updatedBag = clone(grainBag);
close(updatedBag);
})
.catch(err => {
openSnack("Failed to update Grain Bag");
close();
});
}
} else {
//otherwise make a new one
let settings = pond.GrainBagSettings.create();
settings.initialMoisture = initialMoisture;
settings.diameter = bagDiameterM;
settings.length = bagLengthM;
settings.customGrain = customType;
settings.supportedGrain = supportedGrainType;
settings.bushelCapacity = grainCapacity;
settings.currentBushels = grainBushels;
settings.grainSubtype = grainSubtype;
settings.fillDate = fillDate;
settings.storageType = isCustom
? pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN
: pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
if (coordinates) {
settings.startLocation = coordinates.start;
settings.endLocation = coordinates.end;
}
grainBagAPI
.addGrainBag(bagName, settings)
.then(resp => {
openSnack("New grain bag added");
let newBag = GrainBag.create(
pond.GrainBag.fromObject({
title: bagName,
key: resp.data.key,
settings: settings
})
);
close(newBag);
})
.catch(() => {
openSnack("Failed to add new grain bag");
});
}
};
const grainBox = () => {
if (isCustom) {
return (
<React.Fragment>
<TextField
label="Grain"
value={customType}
type="text"
onChange={event => {
setCustomType(event.target.value);
}}
fullWidth
className={classes.bottomSpacing}
variant="outlined"
/>
<TextField
label="Bushels Per Tonne"
value={bushelsPerTonne}
type="number"
onChange={event => {
setBushelsPerTonne(+event.target.value);
}}
fullWidth
className={classes.bottomSpacing}
variant="outlined"
/>
</React.Fragment>
);
} else {
return (
<SearchSelect
style={{ marginBottom: theme.spacing(1) }}
label="Type"
selected={selectedGrainOp}
changeSelection={option => {
let newGrainType = option
? pond.Grain[option.value as keyof typeof pond.Grain]
: pond.Grain.GRAIN_INVALID;
setSupportedGrainType(newGrainType);
setBushelsPerTonne(GrainDescriber(newGrainType).bushelsPerTonne);
setSelectedGrainOp(option);
}}
group
//disabled={!canEdit}
options={grainOptions}
/>
);
}
};
const formInvalid = () => {
let invalid = true;
if (bagName !== "" && grainCapacity > 0) {
invalid = false;
}
return invalid;
};
return (
<React.Fragment>
<GrainTransaction
open={grainUpdate}
mainObject={grainBag ?? GrainBag.create()}
grainAdjustment={grainDiff}
close={() => {
setGrainUpdate(false);
setGrainDiff(0);
//setPendingGrainAmount(undefined);
//resetFillPercentage();
//setGrainDiff(0);
}}
callback={() => {
let updatedBag = clone(grainBag);
close(updatedBag);
}}
/>
<ResponsiveDialog
open={open}
onClose={() => {
close();
}}>
<DialogTitle>Grain Bag Settings</DialogTitle>
<DialogContent>
<TextField
label="Grain Bag Name"
fullWidth
variant="outlined"
value={bagName}
onChange={e => setBagName(e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Bag Diameter"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "m"}
</InputAdornment>
)
}}
fullWidth
variant="outlined"
type="number"
value={bagDiameterDisplay}
onChange={e => {
setBagDiameterDisplay(+e.target.value);
let inMeters = +e.target.value;
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
//if the users preferred unit is in feet that means the value that will be saved needs to be converted to meters
inMeters = inMeters / 3.281;
}
setBagDiameterM(inMeters);
}}
className={classes.bottomSpacing}
/>
<TextField
label="Bag Length"
fullWidth
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "m"}
</InputAdornment>
)
}}
type="number"
value={bagLengthDispla}
onChange={e => {
setBagLengthDisplay(+e.target.value);
let inMeters = +e.target.value;
if (user.settings.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
//if the users preferred unit is in feet that means the value that will be saved needs to be converted to meters
inMeters = inMeters / 3.281;
}
setBagLengthM(inMeters);
}}
className={classes.bottomSpacing}
/>
<TextField
fullWidth
type="date"
label="Fill Date"
value={fillDate}
InputLabelProps={{ shrink: true }}
onChange={e => setFillDate(e.target.value)}
/>
<Grid container alignItems="center">
<Grid item>Grain</Grid>
<Grid item>
<Switch
color="default"
value={isCustom}
checked={isCustom}
onChange={(_, checked) => {
setIsCustom(checked);
if (checked) {
setSupportedGrainType(pond.Grain.GRAIN_CUSTOM);
} else {
setCustomType("");
}
}}
name="storage"
/>
</Grid>
<Grid item>Custom</Grid>
</Grid>
{grainBox()}
<TextField
label="Grain Subtype"
fullWidth
variant="outlined"
value={grainSubtype}
onChange={e => setGrainSubtype(e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Initial Moisture"
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
value={initialMoisture}
onChange={e => setInitialMoisture(+e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label="Capacity"
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: <InputAdornment position="end">bu</InputAdornment>
}}
value={grainCapacity}
onChange={e => setGrainCapacity(+e.target.value)}
className={classes.bottomSpacing}
/>
<TextField
label={
"Current " +
(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bushelsPerTonne > 0
? "Weight"
: "Bushels")
}
fullWidth
type="number"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && bushelsPerTonne > 0
? "mT"
: "bu"}
</InputAdornment>
)
}}
value={grainFill}
onChange={e => {
let bushelVal = +e.target.value;
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT) {
//convert the number as a weight into the bushel value
bushelVal = +e.target.value * (bushelsPerTonne > 0 ? bushelsPerTonne : 1);
}
if (bushelVal > grainCapacity) {
bushelVal = grainCapacity;
}
setGrainBushels(bushelVal);
setGrainFill(e.target.value);
}}
className={classes.bottomSpacing}
/>
</DialogContent>
<DialogActions>
<Grid container direction="row" justify="space-between">
<Grid item>
{grainBag && (
<Button
onClick={() => {
removeGrainBag();
}}
variant="contained"
style={{ background: "red" }}>
Delete
</Button>
)}
</Grid>
<Grid item>
<Button
onClick={() => {
close();
}}>
Cancel
</Button>
<Button onClick={submit} disabled={formInvalid()} color="primary" variant="contained">
Confirm
</Button>
</Grid>
</Grid>
</DialogActions>
</ResponsiveDialog>
</React.Fragment>
);
}

View file

@ -0,0 +1,322 @@
import {
Box,
Card,
Grid,
Slider,
makeStyles,
createStyles,
Typography,
IconButton,
Theme,
useTheme,
darken
} from "@material-ui/core";
import GrainDescriber from "grain/GrainDescriber";
import GrainTransaction from "grain/GrainTransaction";
import { useMobile } from "hooks";
import useViewport from "hooks/useViewport";
import { round } from "lodash";
import { GrainBag } from "models/GrainBag";
import moment from "moment";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
import { getThemeType } from "theme";
import { getGrainUnit } from "utils";
import GrainBagSVG from "./grainBagSVG";
interface Props {
grainBag: GrainBag;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
enum zIndexPriority {
low = 1,
medium = 2,
high = 3
}
const useStyles = makeStyles((theme: Theme) => {
return createStyles({
grainOVerlay: {
position: "absolute",
height: "10%",
width: "100%",
textAlign: "center",
textShadow: "4px 4px 10px black"
},
cardContent: {
padding: theme.spacing(2)
},
bgItem: {
background: darken(theme.palette.background.paper, getThemeType() === "light" ? 0.05 : 0.25),
zIndex: zIndexPriority.low,
borderRadius: theme.shape.borderRadius,
position: "relative",
width: "auto",
marginRight: theme.spacing(1)
}
});
});
export default function GrainBagVisualizer(props: Props) {
const { grainBag } = props;
const [fillLevel, setFillLevel] = useState(0);
const isMobile = useMobile();
const viewport = useViewport();
const [sliderColour, setSliderCoulour] = useState("gold");
const [pendingGrainAmount, setPendingGrainAmount] = useState<number | null>();
const classes = useStyles();
const [grainDiff, setGrainDiff] = useState<number | undefined>();
const theme = useTheme();
const [openTransaction, setOpenTransaction] = useState(false);
useEffect(() => {
setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100);
}, [grainBag]);
const getSVGHeight = () => {
if (isMobile) {
return viewport.width * 0.8;
}
return viewport.height * 0.4;
};
const grainOverlay = () => {
let displayPending = pendingGrainAmount;
let displayDiff = grainDiff;
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && grainBag.bushelsPerTonne() > 1) {
if (displayPending && displayDiff) {
displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100;
displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100;
}
}
return (
<Box>
{grainDiff !== undefined && grainDiff > 0 && (
<div
className={classes.grainOVerlay}
style={{
top: "38%",
color: "#5CE422"
}}>
<Typography variant={"h5"} style={{ fontWeight: 750 }}>
+{displayDiff}
</Typography>
</div>
)}
<div
className={classes.grainOVerlay}
style={{
top: "45%"
}}>
<Typography variant={"h4"} style={{ fontWeight: 750 }}>
{displayPending}
</Typography>
</div>
{grainDiff !== undefined && grainDiff < 0 && (
<div
className={classes.grainOVerlay}
style={{
top: "55%",
color: "#E42222"
}}>
<Typography variant={"h5"} style={{ fontWeight: 750 }}>
{displayDiff}
</Typography>
</div>
)}
</Box>
);
};
const timeDisplay = () => {
let now = moment();
let duration = moment.duration(moment(grainBag.settings.fillDate).diff(now));
let days = duration.asDays();
days = Math.abs(days);
duration.subtract(moment.duration(days, "days"));
// let hours = duration.hours();
// hours = Math.abs(hours);
return Math.floor(days) + " days in bag";
};
const grainAmountDisplay = () => {
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT && grainBag.bushelsPerTonne() > 1) {
return (
<div style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>
{(grainBag.bushels() / grainBag.bushelsPerTonne()).toFixed(2) + " mT"}
</Typography>
<Typography variant="body2">({grainBag.fillPercent()}%)</Typography>
</div>
);
} else {
return (
<div style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>
{grainBag.bushels() <= 0 ? "Empty" : grainBag.bushels().toLocaleString()}
</Typography>
<Typography variant="body2">
{grainBag.bushels() <= 0 ? "" : " / " + grainBag.capacity().toLocaleString() + " bu"}
</Typography>
</div>
);
}
};
const overview = () => {
return (
<Box width={1} position="absolute" top={0}>
<Typography variant="subtitle2" color="textPrimary" style={{ fontWeight: 800 }}>
{grainBag.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN
? GrainDescriber(grainBag.grain()).name
: grainBag.customType()}
{grainBag.subtype() !== "" ? " - " + grainBag.subtype() : ""}
</Typography>
<Box className={classes.bgItem} padding={1}>
{grainAmountDisplay()}
</Box>
<Box padding="0" width={1} position="absolute" top={70}>
<Typography variant="subtitle2" color="textPrimary" style={{ fontWeight: 600 }}>
Fill Date
</Typography>
<Box className={classes.bgItem} display="flex" flexDirection={"column"} padding={1}>
<Typography variant="body2" style={{ fontWeight: "bold" }}>
{grainBag.settings.fillDate}
</Typography>
<Typography variant="body2" style={{ fontWeight: "bold" }}>
{timeDisplay()}
</Typography>
</Box>
</Box>
<Box padding="0" width={1} position="absolute" top={160}>
<Typography variant="subtitle2" color="textPrimary" style={{ fontWeight: 600 }}>
Initial Moisture
</Typography>
<Box className={classes.bgItem} display="flex" flexDirection={"column"} padding={1}>
<Typography variant="body2" style={{ fontWeight: "bold" }}>
{grainBag.settings.initialMoisture}%
</Typography>
</Box>
</Box>
</Box>
);
};
const visual = () => {
return (
<Box display="flex" width={1} justifyContent="flex-end">
<Box
position="relative"
height={1}
width={1}
bgcolor={theme.palette.background.paper}
display="flex"
justifyContent="center"
alignItems="center"
alignContent="flex-end">
<Box zIndex={zIndexPriority.high}>
{grainOverlay()}
<GrainBagSVG fillPercent={fillLevel} height={getSVGHeight()} />
</Box>
</Box>
</Box>
);
};
const controls = () => {
return (
<Box
height={1}
width={1}
display="flex"
flexDirection="column"
justifyContent="flex-end"
alignContent="flex-end">
<IconButton
onClick={() => {
setOpenTransaction(true);
}}
style={{
backgroundColor: "gold",
color: "black",
height: 40,
width: 40,
marginBottom: 10
}}>
+/-
</IconButton>
<Box
height={0.7}
width={1}
display="flex"
justifyContent="center"
alignContent="flex-end"
zIndex={zIndexPriority.high}>
<Slider
orientation="vertical"
value={fillLevel}
style={{ color: sliderColour }}
min={0}
max={100}
valueLabelDisplay="auto"
valueLabelFormat={value => value.toFixed() + "%"}
onChange={(_, value) => {
setFillLevel(value as number);
const capacity = grainBag.capacity();
const current = grainBag.bushels();
let grainAmount = ((value as number) / 100) * capacity;
if (grainAmount < current) {
setSliderCoulour("#E42222");
} else if (grainAmount > current) {
setSliderCoulour("#5CE422");
} else {
setSliderCoulour("gold");
}
setGrainDiff(round(grainAmount - current, 0));
setPendingGrainAmount(round(grainAmount, 0));
}}
onChangeCommitted={() => {
setOpenTransaction(true);
}}
aria-labelledby="grain-amount"
/>
</Box>
<Box height={0.2} width={1} position="relative">
<Box width={1} position="absolute" bottom="0" textAlign="center" />{" "}
{/* this box is used to push the slider up */}
</Box>
</Box>
);
};
return (
<Card raised className={classes.cardContent}>
<Grid container justify="flex-start" alignItems="stretch">
<Grid item xs sm md style={{ position: "relative" }}>
{overview()}
</Grid>
<Grid item style={{ position: "relative" }}>
{visual()}
</Grid>
<Grid item>{controls()}</Grid>
</Grid>
{/* dialog for grain inventory transactions */}
<GrainTransaction
open={openTransaction}
close={() => {
setOpenTransaction(false);
}}
mainObject={grainBag}
grainAdjustment={grainDiff}
callback={() => {
setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100);
setGrainDiff(undefined);
setPendingGrainAmount(null);
setSliderCoulour("gold");
}}
/>
</Card>
);
}

38
src/models/BinYard.ts Normal file
View file

@ -0,0 +1,38 @@
import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
//model not used
export class BinYard {
public settings: pond.BinYardSettings = pond.BinYardSettings.create();
public permissions: pond.Permission[] = [];
public static create(pb?: pond.BinYard): BinYard {
let my = new BinYard();
if (pb) {
my.settings = pond.BinYardSettings.fromObject(cloneDeep(or(pb.settings, {})));
my.permissions = pb.yardPermissions;
}
return my;
}
public static clone(other?: BinYard): BinYard {
if (other) {
return BinYard.create(
pond.BinYard.fromObject({
settings: cloneDeep(other.settings),
binPermissions: cloneDeep(other.permissions)
})
);
}
return BinYard.create();
}
public static any(data: any): BinYard {
return BinYard.create(pond.BinYard.fromObject(cloneDeep(data)));
}
public key(): string {
return this.settings.key;
}
}

135
src/models/GrainBag.ts Normal file
View file

@ -0,0 +1,135 @@
import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
export class GrainBag {
public settings: pond.GrainBagSettings = pond.GrainBagSettings.create();
public title: string = "Bag";
private objKey: string = "";
public static create(pb?: pond.GrainBag): GrainBag {
let my = new GrainBag();
if (pb) {
my.settings = pond.GrainBagSettings.fromObject(cloneDeep(or(pb.settings, {})));
my.title = pb.name;
my.objKey = pb.key;
}
return my;
}
public static clone(other?: GrainBag): GrainBag {
if (other) {
return GrainBag.create(
pond.GrainBag.fromObject({
title: other.title,
key: other.objKey,
settings: cloneDeep(other.settings)
})
);
}
return GrainBag.create();
}
public static any(data: any): GrainBag {
return GrainBag.create(pond.GrainBag.fromObject(cloneDeep(data)));
}
public key(): string {
if (this) {
return this.objKey;
} else {
return "";
}
}
public startLocation(): pond.Location {
let start = pond.Location.create();
start.latitude = this.settings.startLocation?.latitude ?? 0;
start.longitude = this.settings.startLocation?.longitude ?? 0;
return start;
}
public endLocation(): pond.Location {
let end = pond.Location.create();
end.latitude = this.settings.endLocation?.latitude ?? 0;
end.longitude = this.settings.endLocation?.longitude ?? 0;
return end;
}
public bushels(): number {
return this.settings.currentBushels;
}
public capacity(): number {
return this.settings.bushelCapacity;
}
public isCustom(): boolean {
return this.settings.storageType !== pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
}
public name(): string {
if (this) {
return this.title;
} else {
return "";
}
}
public fillPercent(): number {
let fill = 0;
if (this.settings.bushelCapacity > 0) {
fill = Math.round((this.settings.currentBushels / this.settings.bushelCapacity) * 100);
}
return fill;
}
public objectType(): pond.ObjectType {
return pond.ObjectType.OBJECT_TYPE_GRAIN_BAG;
}
public objectTypeString(): string {
return "Grain Bag";
}
public storage(): pond.BinStorage {
return this.settings.storageType;
}
public grain(): pond.Grain {
return this.settings.supportedGrain;
}
public customType(): string {
return this.settings.customGrain;
}
public subtype(): string {
return this.settings.grainSubtype;
}
public centerLocation(): pond.Location {
let center = pond.Location.create();
if (this.startLocation() && this.endLocation()) {
center.longitude = (this.startLocation().longitude + this.endLocation().longitude) / 2;
center.latitude = (this.startLocation().latitude + this.endLocation().latitude) / 2;
}
return center;
}
/**
* 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.currentBushels) {
if (this.settings.bushelsPerTonne > 0) {
bpt = this.settings.bushelsPerTonne;
}
}
return bpt;
}
}

View file

@ -1,6 +1,6 @@
export * from "./Backpack";
export * from "./Bin";
// export * from "./BinYard";
export * from "./BinYard";
export * from "./Component";
export * from "./Device";
export * from "./Firmware";

View file

@ -57,7 +57,7 @@ import GrainDescriber, { grainName } from "grain/GrainDescriber";
import { useMobile, useWidth } from "hooks";
import { Dictionary } from "lodash";
// import { Bin, Interaction, BinYard as BinYardModel } from "models";
import { Bin, Interaction } from "models";
import { Bin, Interaction, BinYard as BinYardModel } from "models";
// import { GrainBag } from "models/GrainBag";
import { pond, quack } from "protobuf-ts/pond";
import {
@ -80,6 +80,7 @@ import { makeStyles } from "@mui/styles";
import { useNavigate } from "react-router-dom";
import BinSettings from "bin/BinSettings";
import AddBinFab from "bin/AddBinFab";
import { GrainBag } from "models/GrainBag";
// import { useHistory } from "react-router";
const useStyles = makeStyles((theme: Theme) => {
@ -168,7 +169,7 @@ export default function Bins(props: Props) {
const [grainBins, setGrainBins] = useState<Bin[]>([]);
const [fertilizerBins, setFertilizerBins] = useState<Bin[]>([]);
const [emptyBins, setEmptyBins] = useState<Bin[]>([]);
// const [grainBags, setGrainBags] = useState<GrainBag[]>([]);
const [grainBags, setGrainBags] = useState<GrainBag[]>([]);
const [displayedInventory, setDisplayedInventory] = useState<GrainAmount[]>([]);
// const [displayedFertilizer, setDisplayedFertilizer] = useState<GrainAmount[]>([]);
const [carouselIndex, setCarouselIndex] = useState(0);
@ -372,22 +373,22 @@ export default function Bins(props: Props) {
if (yards.length === 0) {
let y: pond.BinYardSettings[] = [];
let p: Dictionary<pond.Permission[]> = {};
// let sortedYards = resp.data.binYards.sort((a, b) => {
// let yard1 = BinYardModel.create(a);
// let yard2 = BinYardModel.create(b);
// return (
// yard1.settings.userSort[as ?? user.id()] - yard2.settings.userSort[as ?? user.id()]
// );
// });
// sortedYards.forEach(yard => {
// let newYard = pond.BinYard.fromObject(yard ?? {});
// if (newYard.settings) {
// y.push(newYard.settings);
// p[newYard.settings.key] = newYard.yardPermissions;
// }
// });
// setYardPermissions(p);
// setYards(y);
let sortedYards = resp.data.binYards.sort((a, b) => {
let yard1 = BinYardModel.create(a);
let yard2 = BinYardModel.create(b);
return (
yard1.settings.userSort[as ?? user.id()] - yard2.settings.userSort[as ?? user.id()]
);
});
sortedYards.forEach(yard => {
let newYard = pond.BinYard.fromObject(yard ?? {});
if (newYard.settings) {
y.push(newYard.settings);
p[newYard.settings.key] = newYard.yardPermissions;
}
});
setYardPermissions(p);
setYards(y);
}
//move load of grain bags into this api call
@ -637,7 +638,7 @@ export default function Bins(props: Props) {
<Divider />
</Grid>
<Grid item xs={12}>
{/*<BinsList
<BinsList
valDisplay={cardValDisplay}
gridView={binView === "grid"}
bins={fertilizerBins}
@ -650,7 +651,7 @@ export default function Bins(props: Props) {
setScrollTranslations({ ...scrollTranslations, fertilizer: newTranslation });
}
}}
/>*/}
/>
</Grid>
</React.Fragment>
)}
@ -660,7 +661,7 @@ export default function Bins(props: Props) {
<Divider />
</Grid>
<Grid item xs={12}>
{/*<BinsList
<BinsList
valDisplay={cardValDisplay}
gridView={binView === "grid"}
bins={emptyBins}
@ -673,7 +674,7 @@ export default function Bins(props: Props) {
setScrollTranslations({ ...scrollTranslations, empty: newTranslation });
}
}}
/>*/}
/>
</Grid>
</React.Fragment>
)}
@ -683,16 +684,16 @@ export default function Bins(props: Props) {
};
const grainBagsContent = () => {
// if (!allLoading && grainBags.length <= 0) {
// return (
// <Box textAlign="center" marginTop={1}>
// <Typography variant="subtitle1">No bags found</Typography>
// <Typography variant="body1" color="textSecondary">
// Create a grain bag and it will appear here
// </Typography>
// </Box>
// );
// }
if (!allLoading && grainBags.length <= 0) {
return (
<Box textAlign="center" marginTop={1}>
<Typography variant="subtitle1">No bags found</Typography>
<Typography variant="body1" color="textSecondary">
Create a grain bag and it will appear here
</Typography>
</Box>
);
}
// if (bagView === "list") {
// return (