added object controls and fixed the grainbag visualizer

This commit is contained in:
csawatzky 2025-03-27 14:00:48 -06:00
parent 7bf4736066
commit d8b42c8050
5 changed files with 406 additions and 22 deletions

View file

@ -0,0 +1,220 @@
import {
Grid2 as Grid,
IconButton,
ListItemIcon,
ListItemText,
Menu,
MenuItem,
Tooltip,
Theme,
Box
} from "@mui/material";
//import ObjectTeamsIcon from "@material-ui/icons/SupervisedUserCircle";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import React, { useState } from "react";
import DeviceIcon from "products/Bindapt/BindaptIcon";
import { Device, Scope } from "models";
import ObjectTeams from "teams/ObjectTeams";
import { pond } from "protobuf-ts/pond";
import { Link, SupervisedUserCircle as ObjectTeamsIcon } from "@mui/icons-material";
//import { useHistory } from "react-router";
import { useNavigate } from "react-router-dom";
import { useThemeType } from "hooks";
import { makeStyles } from "@mui/styles";
interface Props {
actions: JSX.Element;
objectButton?: JSX.Element;
mapFunction?: () => void;
linkDeviceFunction?: () => void;
notesButton?: JSX.Element;
teamScope?: Scope;
permissions?: pond.Permission[];
devices?: Device[];
}
const useStyles = makeStyles((theme: Theme) => ({
avatar: {
color: useThemeType() === "light" ? theme.palette.common.black : theme.palette.common.white,
backgroundColor: "transparent",
width: theme.spacing(5),
height: theme.spacing(5),
border: "1px solid",
borderColor: theme.palette.divider
}
}));
export default function ObjectControls(props: Props) {
const {
mapFunction,
linkDeviceFunction,
teamScope,
permissions,
actions,
devices,
notesButton,
objectButton
} = props;
const [openTeams, setOpenTeams] = useState(false);
//const history = useHistory();
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
// const goToDevice = (dev: Device) => {
// history.push("/devices/" + dev.id());
// };
const goToDevice = (dev: Device) => {
let path = "/devices/" + dev.id();
navigate(path);
};
const dialogs = () => {
return (
<React.Fragment>
{teamScope && permissions && (
<ObjectTeams
scope={teamScope}
label="Teams"
permissions={permissions}
isDialogOpen={openTeams}
refreshCallback={() => {}}
closeDialogCallback={() => {
setOpenTeams(false);
}}
/>
)}
</React.Fragment>
);
};
const deviceNavButton = () => {
if (devices) {
if (devices.length > 1) {
return (
<Tooltip key="devices" title="devices">
<IconButton
key="devButton"
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
setAnchorEl(event.currentTarget)
}
size="small"
style={{
marginTop: "0.3625rem",
marginBottom: "0.3625rem",
marginRight: "0.5rem"
}}
className={classes.avatar}
component="span">
<DeviceIcon />
</IconButton>
</Tooltip>
);
} else {
return devices.map(dev => (
<Tooltip key={dev.id()} title={dev.name()}>
<IconButton
key={dev.id()}
onClick={() => goToDevice(dev)}
size="small"
style={{
marginTop: "0.3625rem",
marginBottom: "0.3625rem",
marginRight: "0.5rem"
}}
className={classes.avatar}
component="span">
<DeviceIcon />
</IconButton>
</Tooltip>
));
}
}
};
const deviceMenu = () => {
return (
<Menu
id="groupMenu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
keepMounted
//classes={{ paper: classes.menuPaper }}
disableAutoFocusItem>
{devices &&
devices.map(dev => (
<MenuItem key={dev.id()} onClick={() => goToDevice(dev)}>
<ListItemIcon>
<DeviceIcon />
</ListItemIcon>
<ListItemText primary={dev.name()} />
</MenuItem>
))}
</Menu>
);
};
return (
<Box style={{ padding: 10 }}>
{dialogs()}
<Grid container direction="row">
{/* buttons on the left side start here */}
<Grid>
<Grid container direction="row">
{objectButton}
{notesButton}
{deviceNavButton()}
{deviceMenu()}
{mapFunction && (
<Grid>
<IconButton
onClick={mapFunction}
size="small"
style={{
marginTop: "0.3625rem",
marginBottom: "0.3625rem",
marginRight: "0.5rem"
}}
className={classes.avatar}
component="span">
<FieldsIcon />
</IconButton>
</Grid>
)}
{teamScope && permissions && (
<Grid>
<IconButton
onClick={() => {
setOpenTeams(true);
}}
size="small"
style={{
marginTop: "0.3625rem",
marginBottom: "0.3625rem",
marginRight: "0.5rem"
}}
className={classes.avatar}
component="span">
<ObjectTeamsIcon />
</IconButton>
</Grid>
)}
</Grid>
</Grid>
{/* bottons on the right side start here */}
<Grid>
{linkDeviceFunction && (
<Tooltip title="Link Devices">
<IconButton onClick={linkDeviceFunction}>
<Link />
</IconButton>
</Tooltip>
)}
{actions}
</Grid>
</Grid>
</Box>
);
}

View file

@ -53,6 +53,29 @@ const useStyles = makeStyles((theme: Theme) => {
position: "relative",
width: "auto",
marginRight: theme.spacing(1)
},
sliderRoot: {},
sliderThumb: {
left: 23
},
mobileSliderThumb: {
left: 28
},
sliderTrack: {
height: "100%",
},
sliderRail: {
height: "100%",
},
sliderValLabel: {
// left: -20,
height: 30,
width: 30,
top: -15,
background: "transparent"
},
sliderLabel: {
background: "gold"
}
});
});
@ -210,7 +233,7 @@ export default function GrainBagVisualizer(props: Props) {
position="relative"
height={1}
width={1}
bgcolor={theme.palette.background.paper}
//bgcolor={theme.palette.background.paper}
display="flex"
justifyContent="center"
alignItems="center"
@ -256,7 +279,14 @@ export default function GrainBagVisualizer(props: Props) {
<Slider
orientation="vertical"
value={fillLevel}
style={{ color: sliderColour }}
classes={{
root: classes.sliderRoot,
rail: classes.sliderRail,
track: classes.sliderTrack,
thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
valueLabel: classes.sliderValLabel,
}}
style={{height: "100%", color: sliderColour }}
min={0}
max={100}
valueLabelDisplay="auto"
@ -292,14 +322,14 @@ export default function GrainBagVisualizer(props: Props) {
return (
<Card raised className={classes.cardContent}>
<Grid container justifyContent="flex-start" alignItems="stretch">
<Grid style={{ position: "relative" }}>
<Grid container justifyContent="flex-start" alignItems="stretch" width={"100%"}>
<Grid style={{ position: "relative" }} size={6}>
{overview()}
</Grid>
<Grid style={{ position: "relative" }}>
<Grid style={{ position: "relative" }} size={5}>
{visual()}
</Grid>
<Grid>{controls()}</Grid>
<Grid size={1}>{controls()}</Grid>
</Grid>
{/* dialog for grain inventory transactions */}
<GrainTransaction

View file

@ -2,7 +2,7 @@ import { Box } from "@mui/material";
import DisplayDrawer from "common/DisplayDrawer";
import MapMarkerSettings from "maps/MapMarkerSettings";
import { GrainBag as BagModel } from "models/GrainBag";
//import GrainBag from "pages/grainBag";
import GrainBag from "pages/grainBag";
import { pond } from "protobuf-ts/pond";
import { useGrainBagAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react";
@ -86,8 +86,8 @@ export default function GrainBagDrawer(props: Props) {
};
const drawerBody = () => {
//return <Box>{bag.key() !== "" && <GrainBag bagKey={bag.key()} mobileView />}</Box>;
return <Box></Box>
return <Box>{bag.key() !== "" && <GrainBag bagKey={bag.key()} mobileView />}</Box>;
//return <Box></Box>
};
const update = () => {

133
src/pages/grainBag.tsx Normal file
View file

@ -0,0 +1,133 @@
import { Box, Card, Grid2 as Grid, Typography } from "@mui/material";
import ObjectControls from "common/ObjectControls";
import { GetDefaultDateRange } from "common/time/DateRange";
import TimeBar from "common/time/TimeBar";
import GrainBagActions from "grainBag/grainBagActions";
import GrainBagInventoryGraph from "grainBag/grainBagInventoryGraph";
import GrainBagVisualizer from "grainBag/grainBagVisualizer";
import { Scope } from "models";
import { GrainBag as IGrainBag } from "models/GrainBag";
import { Moment } from "moment";
//import { MatchParams } from "navigation/Routes";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar, useUserAPI } from "providers";
import { useEffect, useState } from "react";
//import { useRouteMatch } from "react-router";
import { useParams } from "react-router-dom";
import PageContainer from "./PageContainer";
interface Props {
bagKey?: string;
mobileView?: boolean;
}
export default function GrainBag(props: Props) {
const { bagKey, mobileView } = props;
//const match = useRouteMatch<MatchParams>();
//const bagID = bagKey ?? match.params.bagID;
const bagID = bagKey ?? useParams<{ bagID: string }>()?.bagID ?? "";
const grainBagAPI = useGrainBagAPI();
const [{ user, as }] = useGlobalState();
const userAPI = useUserAPI();
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
const [grainBag, setGrainBag] = useState<IGrainBag>(IGrainBag.create());
const defaultDateRange = GetDefaultDateRange();
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
const { openSnack } = useSnackbar();
useEffect(() => {
grainBagAPI
.getGrainBag(bagID)
.then(resp => {
if (resp.data.grainBag) {
setGrainBag(IGrainBag.create(resp.data.grainBag));
}
})
.catch(err => {
openSnack("There was a problem loading the Grain Bag");
});
}, [bagID, grainBagAPI, openSnack]);
useEffect(() => {
let key = bagID;
let kind = "grainbag";
if (as) {
key = as;
kind = "team";
}
console.log
userAPI.getUser(user.id(), { key: key, kind: kind } as Scope).then(resp => {
console.log(resp)
setPermissions(resp.permissions);
});
}, [as, bagID, userAPI, user]);
const updateDateRange = (newStartDate: any, newEndDate: any) => {
let range = GetDefaultDateRange();
range.start = newStartDate;
range.end = newEndDate;
setStartDate(newStartDate);
setEndDate(newEndDate);
};
return (
<PageContainer>
<ObjectControls
actions={
<GrainBagActions
grainBag={grainBag}
refreshCallback={updatedBag => {
if (updatedBag) {
setGrainBag(updatedBag);
}
}}
permissions={permissions}
/>
}
/>
<Card style={{ padding: 15, margin: 15 }}>
<Grid container width={"100%"} direction="row" spacing={3} style={{ padding: 5 }}>
<Grid size={{
xs: 12,
sm: 12,
md: mobileView ? 12 : 6,
lg: mobileView ? 12 : 4
}}>
<Box
paddingLeft={1}
display="flex"
justifyContent="space-between"
alignItems="left"
marginBottom={2}>
<Typography style={{ fontWeight: 650, fontSize: 25 }}>{grainBag.name()}</Typography>
</Box>
<GrainBagVisualizer grainBag={grainBag} />
</Grid>
<Grid size={{
xs: 12,
sm: 12,
md: 12,
lg: mobileView ? 12 : 8
}}>
<Box
paddingLeft={1}
display="flex"
justifyContent="space-between"
alignItems="left"
marginBottom={2}>
<TimeBar startDate={startDate} endDate={endDate} updateDateRange={updateDateRange} />
</Box>
<GrainBagInventoryGraph
grainBag={grainBag}
startDate={startDate}
endDate={endDate}
customHeight={500}
/>
</Grid>
</Grid>
</Card>
</PageContainer>
);
}

View file

@ -127,8 +127,7 @@ export default function GrainBagProvider(props: PropsWithChildren<Props>) {
};
const listHistory = (id: string, limit: number, offset: number, start?: string, end?: string) => {
return get<pond.ListGrainBagHistoryResponse>(
pondURL(
let url = pondURL(
"/grainbags/" +
id +
"/history?limit=" +
@ -136,10 +135,12 @@ export default function GrainBagProvider(props: PropsWithChildren<Props>) {
"&offset=" +
offset +
(start && "&start=" + start) +
(end && "&end=" + end) +
(as && "&as=" + as)
(end && "&end=" + end)
)
);
if (as) {
url = url + "?as=" + as
}
return get<pond.ListGrainBagHistoryResponse>(url);
};
return (