view as team notification pops up if they don't have devices or bins but have a team

This commit is contained in:
Carter 2025-07-16 13:32:08 -06:00
parent 4f8b0254bb
commit 0127eeefc7
4 changed files with 130 additions and 35 deletions

View file

@ -18,7 +18,6 @@ import {
ImageList, ImageList,
ImageListItem, ImageListItem,
InputLabel, InputLabel,
lighten,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
Menu, Menu,
@ -26,8 +25,6 @@ import {
Select, Select,
Skeleton, Skeleton,
Theme, Theme,
ToggleButton,
ToggleButtonGroup,
Typography, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
@ -61,7 +58,8 @@ import {
// useBinYardAPI, // useBinYardAPI,
useGlobalState, useGlobalState,
useInteractionsAPI, useInteractionsAPI,
useSnackbar useSnackbar,
useTeamAPI
} from "providers"; } from "providers";
import React, { SetStateAction, useCallback, useEffect, useState } from "react"; import React, { SetStateAction, useCallback, useEffect, useState } from "react";
// import { getThemeType } from "theme"; // import { getThemeType } from "theme";
@ -72,7 +70,7 @@ import ObjectTable from "objects/ObjectTable";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { green, yellow } from "@mui/material/colors"; import { green, yellow } from "@mui/material/colors";
import { makeStyles, styled } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import BinSettings from "bin/BinSettings"; import BinSettings from "bin/BinSettings";
import AddBinFab from "bin/AddBinFab"; import AddBinFab from "bin/AddBinFab";
@ -82,6 +80,7 @@ import GrainBagSettings from "grainBag/grainBagSettings";
import GrainBagList from "grainBag/grainBagList"; import GrainBagList from "grainBag/grainBagList";
import BinYards from "bin/BinYards"; import BinYards from "bin/BinYards";
import ButtonGroup from "common/ButtonGroup"; import ButtonGroup from "common/ButtonGroup";
import TeamDialog from "teams/TeamDialog";
// import { useHistory } from "react-router"; // import { useHistory } from "react-router";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
@ -141,11 +140,13 @@ export default function Bins(props: Props) {
const classes = useStyles(); const classes = useStyles();
const isMobile = useMobile(); const isMobile = useMobile();
const binAPI = useBinAPI(); const binAPI = useBinAPI();
const teamAPI = useTeamAPI();
// const binyardAPI = useBinYardAPI(); // const binyardAPI = useBinYardAPI();
const interactionsAPI = useInteractionsAPI(); const interactionsAPI = useInteractionsAPI();
const [{ user, as }] = useGlobalState(); const [{ user, as }] = useGlobalState();
const [allLoading, setAllLoading] = useState(false); const [allLoading, setAllLoading] = useState(false);
const [binsLoading, setBinsLoading] = useState(false); const [binsLoading, setBinsLoading] = useState(false);
const [doneLoadingPage, setDoneLoadingPage] = useState(false)
const [addBinOpen, setAddBinOpen] = useState(false); const [addBinOpen, setAddBinOpen] = useState(false);
const [paginatedBins, setPaginatedBins] = useState<PaginatedBins>({ const [paginatedBins, setPaginatedBins] = useState<PaginatedBins>({
bins: [], bins: [],
@ -188,13 +189,16 @@ export default function Bins(props: Props) {
const [qrKeyList, setQrKeyList] = useState<QrCodeKey[]>([]); const [qrKeyList, setQrKeyList] = useState<QrCodeKey[]>([]);
const [totalFanControllers, setTotalFanControllers] = useState(0); const [totalFanControllers, setTotalFanControllers] = useState(0);
const [totalFanControllersOn, setTotalFanControllersOn] = useState(0); const [totalFanControllersOn, setTotalFanControllersOn] = useState(0);
const [yardsLoading, setYardsLoading] = useState(false); // const [yardsLoading, setYardsLoading] = useState(false);
const [openAddAlerts, setOpenAddAlerts] = useState(false); const [openAddAlerts, setOpenAddAlerts] = useState(false);
const [alertBins, setAlertBins] = useState<Bin[]>([]); const [alertBins, setAlertBins] = useState<Bin[]>([]);
const { openSnack } = useSnackbar(); const { openSnack } = useSnackbar();
// const history = useHistory(); // const history = useHistory();
const navigate = useNavigate() const navigate = useNavigate()
const [cardValDisplay, setCardValDisplay] = useState<"high" | "low" | "average">("average"); const [cardValDisplay, setCardValDisplay] = useState<"high" | "low" | "average">("average");
const [teamsDialog, setTeamsDialog] = useState(false)
// const [scrollTranslations, setScrollTranslations] = useState({ // const [scrollTranslations, setScrollTranslations] = useState({
// bins: 0, // bins: 0,
// empty: 0, // empty: 0,
@ -252,6 +256,18 @@ export default function Bins(props: Props) {
// selected: {} // selected: {}
// })); // }));
useEffect(() => {
if (doneLoadingPage && grainBins.length < 1 && grainBags.length < 1) {
teamAPI.listTeams(1, 0).then(resp => {
// console.log(resp.data.teams)
if (resp.data.teams.length > 0) {
// console.log("should?")
setTeamsDialog(true)
}
})
}
}, [doneLoadingPage, grainBins, grainBags])
useEffect(() => { useEffect(() => {
let ebt = sessionStorage.getItem("expandBinTotal"); let ebt = sessionStorage.getItem("expandBinTotal");
if (ebt === "true") { if (ebt === "true") {
@ -400,7 +416,7 @@ export default function Bins(props: Props) {
const bags = resp.data.bags.map(b => GrainBag.create(b)); const bags = resp.data.bags.map(b => GrainBag.create(b));
setGrainBags(bags); setGrainBags(bags);
}) })
.catch(err => { .catch(() => {
setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 }); setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 });
setBinMetrics(pond.BinMetrics.create()); setBinMetrics(pond.BinMetrics.create());
setYards([]); setYards([]);
@ -408,6 +424,7 @@ export default function Bins(props: Props) {
.finally(() => { .finally(() => {
setAllLoading(false); setAllLoading(false);
setBinsLoading(false); setBinsLoading(false);
setDoneLoadingPage(true)
}); });
}, [binAPI, yardFilter, order, orderBy, as, loadingAs, loadingFilter]); // eslint-disable-line react-hooks/exhaustive-deps }, [binAPI, yardFilter, order, orderBy, as, loadingAs, loadingFilter]); // eslint-disable-line react-hooks/exhaustive-deps
@ -454,7 +471,7 @@ export default function Bins(props: Props) {
setFertilizerBins([...fert]); setFertilizerBins([...fert]);
setQrKeyList([...newKeyList]); setQrKeyList([...newKeyList]);
}) })
.catch(err => { .catch(() => {
setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 }); setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 });
}) })
.finally(() => { .finally(() => {
@ -462,13 +479,13 @@ export default function Bins(props: Props) {
}); });
}; };
const searchYards = ( // const searchYards = (
search?: string, // search?: string,
limit?: number, // limit?: number,
offset?: number, // offset?: number,
order?: "asc" | "desc" // order?: "asc" | "desc"
) => { // ) => {
setYardsLoading(true); // setYardsLoading(true);
// binyardAPI.listBinYards(limit ?? binsLimit, offset ?? 0, order, "name", search).then(resp => { // binyardAPI.listBinYards(limit ?? binsLimit, offset ?? 0, order, "name", search).then(resp => {
// let y: pond.BinYardSettings[] = []; // let y: pond.BinYardSettings[] = [];
// let p: Dictionary<pond.Permission[]> = {}; // let p: Dictionary<pond.Permission[]> = {};
@ -493,7 +510,7 @@ export default function Bins(props: Props) {
// } // }
// setYardsLoading(false); // setYardsLoading(false);
// }); // });
}; // };
useEffect(() => { useEffect(() => {
loadBins(); loadBins();
@ -515,7 +532,7 @@ export default function Bins(props: Props) {
} }
const duplicateBin = (bin: Bin) => { const duplicateBin = (bin: Bin) => {
binAPI.addBin(bin.settings, as).then(resp => { binAPI.addBin(bin.settings, as).then(() => {
loadBins(); loadBins();
}); });
}; };
@ -556,7 +573,7 @@ export default function Bins(props: Props) {
.then(resp => { .then(resp => {
openSnack("Successfully added " + resp.data.numAlertsSet + " alerts"); openSnack("Successfully added " + resp.data.numAlertsSet + " alerts");
}) })
.catch(err => { .catch(() => {
console.log("errors found"); console.log("errors found");
}); });
}; };
@ -1080,7 +1097,7 @@ export default function Bins(props: Props) {
control={ control={
<Checkbox <Checkbox
checked={displayGrain} checked={displayGrain}
onChange={(e, checked) => { onChange={(_, checked) => {
setDisplayGrain(checked); setDisplayGrain(checked);
}} }}
/> />
@ -1093,7 +1110,7 @@ export default function Bins(props: Props) {
control={ control={
<Checkbox <Checkbox
checked={displayFert} checked={displayFert}
onChange={(e, checked) => { onChange={(_, checked) => {
setDisplayFert(checked); setDisplayFert(checked);
}} }}
/> />
@ -1106,7 +1123,7 @@ export default function Bins(props: Props) {
control={ control={
<Checkbox <Checkbox
checked={displayEmpty} checked={displayEmpty}
onChange={(e, checked) => { onChange={(_, checked) => {
setDisplayEmpty(checked); setDisplayEmpty(checked);
}} }}
/> />
@ -1392,15 +1409,17 @@ export default function Bins(props: Props) {
{addALertsDialog()} {addALertsDialog()}
<BinYards <BinYards
yards={yards} yards={yards}
yardsLoading={yardsLoading} // yardsLoading={yardsLoading}
yardsLoading={false}
yardPerms={yardPermissions} yardPerms={yardPermissions}
showTabs={!props.insert} showTabs={!props.insert}
setYardFilter={setYardFilter} setYardFilter={setYardFilter}
bins={paginatedBins.bins} bins={paginatedBins.bins}
loadBins={loadBins} loadBins={loadBins}
loadYards={(search, limit, offset) => { // loadYards={(search, limit, offset) => {
searchYards(search, limit, offset); // searchYards(search, limit, offset);
}} // }}
loadYards={() => {}}
grainBags={grainBags} grainBags={grainBags}
setShowing={(val: "all" | "bins" | "bags") => { setShowing={(val: "all" | "bins" | "bags") => {
setShowing(val); setShowing(val);
@ -1473,6 +1492,13 @@ export default function Bins(props: Props) {
/> />
{binMenu()} {binMenu()}
<AddBinFab onClick={() => setAddBinOpen(true)} pulse={binsTotal < 1 && !allLoading} /> <AddBinFab onClick={() => setAddBinOpen(true)} pulse={binsTotal < 1 && !allLoading} />
<TeamDialog
open={teamsDialog}
setOpen={setTeamsDialog}
title={"You have teams!"}
info={"Looks like you don't have any bins, but are a part of at least one team. If you want to see your teams bins, please select your team and choose the option \"View as team\""}
hideKey={"bins-teams-notification-dialog"}
/>
</Box> </Box>
); );
}; };

View file

@ -3,7 +3,7 @@ import PageContainer from "./PageContainer";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import LogsDisplay from "common/LogsDisplay"; import LogsDisplay from "common/LogsDisplay";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDeviceAPI, useSnackbar } from "hooks"; import { useDeviceAPI, useMobile, useSnackbar } from "hooks";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import { Component, Device, Interaction } from "models"; import { Component, Device, Interaction } from "models";
import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { getContextKeys, getContextTypes } from "pbHelpers/Context";
@ -43,6 +43,7 @@ export default function DeviceSupport() {
const deviceAPI = useDeviceAPI() const deviceAPI = useDeviceAPI()
const snackbar = useSnackbar() const snackbar = useSnackbar()
const theme = useTheme() const theme = useTheme()
const isMobile = useMobile()
const { state } = useLocation(); const { state } = useLocation();
const [{ user, as }] = useGlobalState() const [{ user, as }] = useGlobalState()
@ -188,7 +189,7 @@ export default function DeviceSupport() {
} }
return ( return (
<PageContainer> <PageContainer spacing={isMobile ? 0 : 2}>
<Grid container justifyContent={"space-between"}> <Grid container justifyContent={"space-between"}>
<Grid> <Grid>
<SmartBreadcrumb deviceName={device.name()} state={{devicePageData: devicePageData}} /> <SmartBreadcrumb deviceName={device.name()} state={{devicePageData: devicePageData}} />

View file

@ -5,7 +5,7 @@ import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
import ProvisionDevice from "device/ProvisionDevice"; import ProvisionDevice from "device/ProvisionDevice";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useDeviceAPI, useGlobalState, useGroupAPI } from "providers"; import { useDeviceAPI, useGlobalState, useGroupAPI, useTeamAPI } from "providers";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
@ -28,6 +28,7 @@ import StatusDust from "common/StatusDust";
import StatusGas from "common/StatusGas"; import StatusGas from "common/StatusGas";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import LoadingScreen from "app/LoadingScreen"; import LoadingScreen from "app/LoadingScreen";
import TeamDialog from "teams/TeamDialog";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -98,9 +99,12 @@ export default function Devices() {
const location = useLocation(); const location = useLocation();
const deviceAPI = useDeviceAPI(); const deviceAPI = useDeviceAPI();
const groupAPI = useGroupAPI(); const groupAPI = useGroupAPI();
const teamAPI = useTeamAPI();
const [devicesLoading, setDevicesLoading] = useState(false) const [devicesLoading, setDevicesLoading] = useState(false)
const [limit, setLimit] = useState(10); const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [doneLoading, setDoneLoading] = useState(false)
const [teamsDialog, setTeamsDialog] = useState(false)
const [{ user }] = useGlobalState() const [{ user }] = useGlobalState()
@ -186,7 +190,7 @@ export default function Devices() {
const [preferencesAnchor, setPreferencesAnchor] = useState<any>(null) const [preferencesAnchor, setPreferencesAnchor] = useState<any>(null)
const [{ as }] = useGlobalState() const [{ as, team }] = useGlobalState()
const updateGroups = (newGroups: Group[]) => { const updateGroups = (newGroups: Group[]) => {
setGroups(newGroups); setGroups(newGroups);
@ -327,6 +331,7 @@ export default function Devices() {
setTotal(resp.data.total) setTotal(resp.data.total)
}).finally(() => { }).finally(() => {
setDevicesLoading(false) setDevicesLoading(false)
setDoneLoading(true)
}) })
} }
@ -338,6 +343,16 @@ export default function Devices() {
loadGroups() loadGroups()
}, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup, as]) }, [groupLimit, groupPage, orderGroup, orderGroupBy, searchGroup, as])
useEffect(() => {
if (doneLoading && devices.length < 1 && groups.length < 1) {
teamAPI.listTeams(1, 0).then(resp => {
if (resp.data.teams.length > 0 && team.key().length < 1) {
setTeamsDialog(true);
}
})
}
}, [doneLoading, devices, groups])
const handleChange = (event: any) => { const handleChange = (event: any) => {
setLimit(event.target.value); setLimit(event.target.value);
}; };
@ -826,6 +841,13 @@ export default function Devices() {
addGroupCallback={addGroupCallback} addGroupCallback={addGroupCallback}
/> />
{preferencesMenu()} {preferencesMenu()}
<TeamDialog
open={teamsDialog}
setOpen={setTeamsDialog}
title={"You have teams!"}
info={"Looks like you don't have any devices, but are a part of at least one team. If you want to see your teams devices, please select your team and choose the option \"View as team\""}
hideKey={"devices-teams-notification-dialog"}
/>
</PageContainer> </PageContainer>
) )
} }

View file

@ -1,6 +1,9 @@
import { import {
AppBar, AppBar,
Button, Button,
Checkbox,
FormControlLabel,
FormGroup,
IconButton, IconButton,
Theme, Theme,
Toolbar, Toolbar,
@ -18,6 +21,9 @@ import { makeStyles } from "@mui/styles";
interface Props { interface Props {
open: boolean; open: boolean;
setOpen: (value: React.SetStateAction<boolean>) => void; setOpen: (value: React.SetStateAction<boolean>) => void;
title?: string;
info?: string;
hideKey?: string;
} }
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
@ -37,16 +43,22 @@ const useStyles = makeStyles((theme: Theme) => {
}); });
export default function TeamDialog(props: Props) { export default function TeamDialog(props: Props) {
const { open, setOpen } = props; const { open, setOpen, title, info, hideKey } = props;
const teamAPI = useTeamAPI(); const teamAPI = useTeamAPI();
const userAPI = useUserAPI(); const userAPI = useUserAPI();
const [, dispatch] = useGlobalState(); const [, dispatch] = useGlobalState();
const [teamKey, setTeamKey] = useState<string>(""); const [teamKey, setTeamKey] = useState<string>("");
const [{ team, user }] = useGlobalState(); const [{ team, user }] = useGlobalState();
const [viewAsTeam, setViewAsTeam] = useState(user.settings.useTeam)
const [doNotShow, setDoNotShow] = useState(false)
const classes = useStyles(); const classes = useStyles();
const shouldHide = hideKey ?
(localStorage.getItem(hideKey) === "true" ? true : false)
: false
const setTeam = () => { const setTeam = () => {
setOpen(false); onClose()
dispatch({ key: "as", value: teamKey }); dispatch({ key: "as", value: teamKey });
if (teamKey === "" || teamKey.includes("auth")) { if (teamKey === "" || teamKey.includes("auth")) {
dispatch({ key: "team", value: Team.create() }); dispatch({ key: "team", value: Team.create() });
@ -55,13 +67,19 @@ export default function TeamDialog(props: Props) {
teamAPI.getTeam(teamKey).then(team => { teamAPI.getTeam(teamKey).then(team => {
dispatch({ key: "team", value: team }); dispatch({ key: "team", value: team });
user.settings.defaultTeam = team.key(); user.settings.defaultTeam = team.key();
user.settings.useTeam = true; //when selecting a team now set to view as team by default user.settings.useTeam = viewAsTeam; //when selecting a team now set to view as team by default
userAPI.updateUser(user.id(), user.protobuf()); userAPI.updateUser(user.id(), user.protobuf());
}); });
}; };
const onClose = () => {
console.log("close")
setOpen(false)
if (hideKey) localStorage.setItem(hideKey, ""+doNotShow)
}
return ( return (
<ResponsiveDialog open={open} onClose={() => setOpen(false)}> <ResponsiveDialog open={open && !shouldHide} onClose={onClose}>
<AppBar position="relative"> <AppBar position="relative">
<Toolbar> <Toolbar>
<IconButton <IconButton
@ -72,7 +90,7 @@ export default function TeamDialog(props: Props) {
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
<Typography variant="h6" className={classes.title}> <Typography variant="h6" className={classes.title}>
Select a Team to View As { title ? title : "Select a Team to View As"}
</Typography> </Typography>
<Button color="inherit" onClick={setTeam} > <Button color="inherit" onClick={setTeam} >
Save Save
@ -84,12 +102,40 @@ export default function TeamDialog(props: Props) {
Viewing as: {team.name()} Viewing as: {team.name()}
</Typography> </Typography>
)} )}
{info &&
<Typography variant="body1" className={classes.viewingAs}>
{info}
</Typography>
}
<TeamSearch <TeamSearch
//style={{ width: theme.spacing(32) }} //style={{ width: theme.spacing(32) }}
className={classes.searchBar} className={classes.searchBar}
loadUsers={user.hasFeature("admin")} loadUsers={user.hasFeature("admin")}
setTeamCallback={setTeamKey} setTeamCallback={setTeamKey}
/> />
{/* <Checkbox checked={viewAsTeam} onChange={() => setViewAsTeam(!viewAsTeam)} /> */}
<FormGroup sx={{ margin: 1, marginLeft: 2 }}>
<FormControlLabel
control={
<Checkbox
checked={viewAsTeam}
onChange={() => setViewAsTeam(!viewAsTeam)}
/>
}
label="View as team"
/>
</FormGroup>
{hideKey && <FormGroup sx={{ margin: 1, marginLeft: 2 }}>
<FormControlLabel
control={
<Checkbox
checked={doNotShow}
onChange={() => setDoNotShow(!doNotShow)}
/>
}
label="Do not show again"
/>
</FormGroup>}
</ResponsiveDialog> </ResponsiveDialog>
); );
} }