added object controls and fixed the grainbag visualizer
This commit is contained in:
parent
7bf4736066
commit
d8b42c8050
5 changed files with 406 additions and 22 deletions
220
src/common/ObjectControls.tsx
Normal file
220
src/common/ObjectControls.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
133
src/pages/grainBag.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -127,19 +127,20 @@ export default function GrainBagProvider(props: PropsWithChildren<Props>) {
|
|||
};
|
||||
|
||||
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)
|
||||
)
|
||||
);
|
||||
let url = pondURL(
|
||||
"/grainbags/" +
|
||||
id +
|
||||
"/history?limit=" +
|
||||
limit +
|
||||
"&offset=" +
|
||||
offset +
|
||||
(start && "&start=" + start) +
|
||||
(end && "&end=" + end)
|
||||
)
|
||||
if (as) {
|
||||
url = url + "?as=" + as
|
||||
}
|
||||
return get<pond.ListGrainBagHistoryResponse>(url);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue