groupd now generate tabs
This commit is contained in:
parent
c1ff72025a
commit
916d0f9132
4 changed files with 609 additions and 8 deletions
484
src/group/GroupSettings.tsx
Normal file
484
src/group/GroupSettings.tsx
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
darken,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
Grid2,
|
||||||
|
lighten,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
Paper,
|
||||||
|
Step,
|
||||||
|
StepLabel,
|
||||||
|
Stepper,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
// import { darken, lighten } from "@material-ui/core/styles/colorManipulator";
|
||||||
|
// import { Theme } from "@material-ui/core/styles/createMuiTheme";
|
||||||
|
import DeleteButton from "common/DeleteButton";
|
||||||
|
import Loader from "common/Loader";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import SearchBar from "common/SearchBar";
|
||||||
|
// import SearchBar from "common/SearchBar";
|
||||||
|
// import RemoveGroup from "group/RemoveGroup";
|
||||||
|
import { useDeviceAPI, useGroupAPI, usePrevious, useSnackbar } from "hooks";
|
||||||
|
import { Device, Group } from "models";
|
||||||
|
import { filterDevices } from "pbHelpers/Device";
|
||||||
|
import { groupsAreEqual } from "pbHelpers/Group";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import RemoveGroup from "./RemoveGroup";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
deviceListContainer: {
|
||||||
|
marginBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
devicesList: {
|
||||||
|
overflow: "auto",
|
||||||
|
minHeight: "25vh",
|
||||||
|
maxHeight: "35vh",
|
||||||
|
height: "auto",
|
||||||
|
background:
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? lighten(theme.palette.background.paper, 0.04)
|
||||||
|
: darken(theme.palette.background.paper, 0.04)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialGroup?: Group;
|
||||||
|
mode?: "add" | "update" | "remove" | undefined;
|
||||||
|
isDialogOpen: boolean;
|
||||||
|
closeDialogCallback: Function;
|
||||||
|
refreshCallback: Function;
|
||||||
|
canEdit?: boolean;
|
||||||
|
groupDevices?: Device[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GroupSettings(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { success, error, warning } = useSnackbar();
|
||||||
|
const {
|
||||||
|
initialGroup,
|
||||||
|
mode,
|
||||||
|
isDialogOpen,
|
||||||
|
closeDialogCallback,
|
||||||
|
refreshCallback,
|
||||||
|
canEdit,
|
||||||
|
groupDevices
|
||||||
|
} = props;
|
||||||
|
const prevInitialGroup = usePrevious(initialGroup);
|
||||||
|
const groupAPI = useGroupAPI();
|
||||||
|
const deviceAPI = useDeviceAPI();
|
||||||
|
const [devices, setDevices] = useState<Device[]>([]);
|
||||||
|
const [group, setGroup] = useState<Group>(initialGroup ? Group.clone(initialGroup) : new Group());
|
||||||
|
const [isRemoveGroupOpen, setIsRemoveGroupOpen] = useState<boolean>(
|
||||||
|
mode === "remove" ? true : false
|
||||||
|
);
|
||||||
|
const [tabIndex, setTabIndex] = useState<number>(0);
|
||||||
|
const prevTabIndex = usePrevious(tabIndex);
|
||||||
|
const [steps] = useState<string[]>(["General group information", "Select devices for the group"]);
|
||||||
|
const [deviceSearch, setDeviceSearch] = useState<string>("");
|
||||||
|
const [loadingDevices, setLoadingDevices] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const loadDevices = useCallback(() => {
|
||||||
|
setLoadingDevices(true);
|
||||||
|
deviceAPI
|
||||||
|
.list(1000000, 0, "asc")
|
||||||
|
.then((response: any) => {
|
||||||
|
let rDevices: Device[] = response.data.devices
|
||||||
|
? response.data.devices.map((device: any) => Device.any(device))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
setDevices(rDevices);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
setDevices([]);
|
||||||
|
})
|
||||||
|
.finally(() => setLoadingDevices(false));
|
||||||
|
}, [deviceAPI]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (prevInitialGroup !== initialGroup) {
|
||||||
|
setGroup(initialGroup ? Group.clone(initialGroup) : new Group());
|
||||||
|
//setDevices([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevTabIndex !== 1 && tabIndex === 1) {
|
||||||
|
loadDevices();
|
||||||
|
}
|
||||||
|
}, [initialGroup, loadDevices, prevInitialGroup, prevTabIndex, props, tabIndex, groupDevices]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
closeDialogCallback();
|
||||||
|
setGroup(initialGroup ? Group.clone(initialGroup) : new Group());
|
||||||
|
setDevices([]);
|
||||||
|
setTabIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
const groupName = group.name();
|
||||||
|
switch (mode) {
|
||||||
|
case "add":
|
||||||
|
groupAPI
|
||||||
|
.addGroup(group.settings)
|
||||||
|
.then((response: any) => {
|
||||||
|
success(groupName + " was successfully created");
|
||||||
|
close();
|
||||||
|
refreshCallback();
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
err.response.data.error
|
||||||
|
? warning(err.response.data.error)
|
||||||
|
: error("Error occured when creating a group");
|
||||||
|
close();
|
||||||
|
})
|
||||||
|
.finally(() => setTabIndex(0));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
groupAPI
|
||||||
|
.updateGroup(group.id(), group.settings)
|
||||||
|
.then((response: any) => {
|
||||||
|
success(groupName + " was successfully updated");
|
||||||
|
close();
|
||||||
|
refreshCallback();
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
err.response.data.error
|
||||||
|
? warning(err.response.data.error)
|
||||||
|
: error("Error occured when updating " + groupName);
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRemoveGroup = () => {
|
||||||
|
setIsRemoveGroupOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeRemoveGroup = () => {
|
||||||
|
setIsRemoveGroupOpen(false);
|
||||||
|
if (mode === "remove") {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid = () => {
|
||||||
|
const validGroup: boolean = group !== undefined && group !== null;
|
||||||
|
const validUpdate: boolean =
|
||||||
|
mode !== "update" || !groupsAreEqual(Group.clone(initialGroup), group);
|
||||||
|
|
||||||
|
return validGroup && validUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTab = (value: number) => {
|
||||||
|
setTabIndex(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
let nextStepIndex = tabIndex + 1;
|
||||||
|
setTabIndex(nextStepIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const backStep = () => {
|
||||||
|
let backStepIndex = tabIndex - 1;
|
||||||
|
setTabIndex(backStepIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeDeviceSearch = (value: string) => {
|
||||||
|
setDeviceSearch(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeName = (event: any) => {
|
||||||
|
let updatedGroup = Group.clone(group);
|
||||||
|
updatedGroup.settings.name = event.target.value;
|
||||||
|
setGroup(updatedGroup);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeDescription = (event: any) => {
|
||||||
|
let updatedGroup = Group.clone(group);
|
||||||
|
updatedGroup.settings.description = event.target.value;
|
||||||
|
setGroup(updatedGroup);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeDevices = (device: number) => {
|
||||||
|
let updatedGroup = Group.clone(group);
|
||||||
|
const exists = updatedGroup.settings.devices.includes(device);
|
||||||
|
if (exists) {
|
||||||
|
updatedGroup.settings.devices = updatedGroup.settings.devices.filter(
|
||||||
|
groupDevice => groupDevice !== device
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
updatedGroup.settings.devices.push(device);
|
||||||
|
}
|
||||||
|
setGroup(updatedGroup);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
const title = () => {
|
||||||
|
switch (mode) {
|
||||||
|
case "add":
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
Group Creation
|
||||||
|
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||||
|
Groups are a great way to organize your devices
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
case "update":
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
Group Settings
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{group.name()}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <React.Fragment />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generalGroupContent = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TextField
|
||||||
|
id="group-name"
|
||||||
|
label="Name"
|
||||||
|
value={group.settings.name}
|
||||||
|
onChange={changeName}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
disabled={!canEdit}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
id="group-description"
|
||||||
|
label="Description"
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
// rowsMax="4"
|
||||||
|
value={group.settings.description}
|
||||||
|
onChange={changeDescription}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
disabled={!canEdit}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const devicesGroupContent = () => {
|
||||||
|
if (mode !== "add") return devicesTab();
|
||||||
|
return (
|
||||||
|
<Paper elevation={0} className={classes.deviceListContainer}>
|
||||||
|
<SearchBar value={deviceSearch} onChange={changeDeviceSearch} shape="square" />
|
||||||
|
{loadingDevices ? (
|
||||||
|
<List className={classes.devicesList}>
|
||||||
|
<ListItem>
|
||||||
|
<Grid2 container justifyContent="center">
|
||||||
|
<Loader size="small" />
|
||||||
|
</Grid2>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
) : (
|
||||||
|
<List dense className={classes.devicesList}>
|
||||||
|
{filterDevices(deviceSearch, devices, [])
|
||||||
|
.sort((a, b: Device) => a.name().localeCompare(b.name()))
|
||||||
|
.map(device => {
|
||||||
|
const label = `checkbox-list-secondary-label-${device.id()}`;
|
||||||
|
return (
|
||||||
|
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}>
|
||||||
|
<ListItemText id={label} primary={device.name()} />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Checkbox
|
||||||
|
edge="end"
|
||||||
|
onChange={() => changeDevices(device.id())}
|
||||||
|
checked={group.settings.devices.includes(device.id())}
|
||||||
|
inputProps={{ "aria-labelledby": label }}
|
||||||
|
disabled={!canEdit}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//useEffect(() => {
|
||||||
|
// if (tabIndex === 1) loadDevices()
|
||||||
|
//}, [groupDevices])
|
||||||
|
|
||||||
|
const addDevice = (device: number) => {
|
||||||
|
groupAPI.addDevice(group.id(), device).then(() => {
|
||||||
|
refreshCallback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeDevice = (device: number) => {
|
||||||
|
groupAPI.removeDevice(group.id(), device).then(() => {
|
||||||
|
refreshCallback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const devicesTab = () => {
|
||||||
|
return (
|
||||||
|
<Paper elevation={0} className={classes.deviceListContainer}>
|
||||||
|
<SearchBar value={deviceSearch} onChange={changeDeviceSearch} shape="square" />
|
||||||
|
{loadingDevices ? (
|
||||||
|
<List className={classes.devicesList}>
|
||||||
|
<ListItem>
|
||||||
|
<Grid2 container justifyContent="center">
|
||||||
|
<Loader size="small" />
|
||||||
|
</Grid2>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
) : (
|
||||||
|
<List dense className={classes.devicesList}>
|
||||||
|
{filterDevices(deviceSearch, devices, [])
|
||||||
|
.sort((a, b: Device) => a.name().localeCompare(b.name()))
|
||||||
|
.map(device => {
|
||||||
|
const label = `checkbox-list-secondary-label-${device.id()}`;
|
||||||
|
return (
|
||||||
|
<ListItem key={device.id()} onClick={() => changeDevices(device.id())}>
|
||||||
|
<ListItemText id={label} primary={device.name()} />
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<Checkbox
|
||||||
|
edge="end"
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
if (checked) addDevice(device.id());
|
||||||
|
else removeDevice(device.id());
|
||||||
|
}}
|
||||||
|
checked={Boolean(groupDevices?.find(dev => dev.id() === device.id()))}
|
||||||
|
inputProps={{ "aria-labelledby": label }}
|
||||||
|
disabled={!canEdit}
|
||||||
|
/>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => {
|
||||||
|
if (mode === "add") {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{tabIndex === 0 && generalGroupContent()}
|
||||||
|
{tabIndex === 1 && devicesGroupContent()}
|
||||||
|
<Stepper activeStep={tabIndex} alternativeLabel>
|
||||||
|
{steps.map(label => (
|
||||||
|
<Step key={label}>
|
||||||
|
<StepLabel>{label}</StepLabel>
|
||||||
|
</Step>
|
||||||
|
))}
|
||||||
|
</Stepper>
|
||||||
|
<Divider />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
} else if (mode === "update") {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{tabIndex === 0 && generalGroupContent()}
|
||||||
|
{tabIndex === 1 && devicesGroupContent()}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <React.Fragment />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = () => {
|
||||||
|
return (
|
||||||
|
<Grid2 container justifyContent="space-between" direction="row">
|
||||||
|
<Grid2 size={{ xs: 5 }}>
|
||||||
|
{mode === "add" ? (
|
||||||
|
<Button onClick={backStep} color="primary" disabled={tabIndex === 0}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
mode === "update" &&
|
||||||
|
canEdit && <DeleteButton onClick={openRemoveGroup}>Delete</DeleteButton>
|
||||||
|
)}
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={{ xs: 7 }} container justifyContent="flex-end">
|
||||||
|
<Button onClick={close} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{(mode === "add" && tabIndex === steps.length - 1) || mode === "update"
|
||||||
|
? canEdit && (
|
||||||
|
<Button onClick={submit} color="primary" disabled={!isFormValid()}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
: mode === "add" && (
|
||||||
|
<Button onClick={nextStep} color="primary">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogs = () => {
|
||||||
|
return (
|
||||||
|
<RemoveGroup
|
||||||
|
group={group}
|
||||||
|
isDialogOpen={isRemoveGroupOpen}
|
||||||
|
closeDialogCallback={closeRemoveGroup}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
onClose={close}
|
||||||
|
aria-labelledby="group-settings-dialog"
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth>
|
||||||
|
<DialogTitle id="group-settings-title">{title()}</DialogTitle>
|
||||||
|
{mode === "update" && (
|
||||||
|
<Tabs
|
||||||
|
value={tabIndex}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
changeTab(value);
|
||||||
|
}}
|
||||||
|
indicatorColor="secondary"
|
||||||
|
textColor="inherit"
|
||||||
|
variant="fullWidth">
|
||||||
|
<Tab label="General" />
|
||||||
|
<Tab label="Devices" />
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
|
<DialogContent>{content()}</DialogContent>
|
||||||
|
<DialogActions>{actions()}</DialogActions>
|
||||||
|
{dialogs()}
|
||||||
|
</ResponsiveDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
84
src/group/RemoveGroup.tsx
Normal file
84
src/group/RemoveGroup.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Theme,
|
||||||
|
Typography
|
||||||
|
} from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { useGroupAPI, useSnackbar } from "hooks";
|
||||||
|
import { Group } from "models";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
contentPadding: {
|
||||||
|
paddingTop: theme.spacing(2),
|
||||||
|
paddingBottom: theme.spacing(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
group: Group;
|
||||||
|
isDialogOpen: boolean;
|
||||||
|
closeDialogCallback: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RemoveGroup(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const groupAPI = useGroupAPI();
|
||||||
|
const { success, error, warning } = useSnackbar();
|
||||||
|
const { group, isDialogOpen, closeDialogCallback } = props;
|
||||||
|
let groupName = group.name();
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
closeDialogCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
groupAPI
|
||||||
|
.removeGroup(group.id())
|
||||||
|
.then((response: any) => {
|
||||||
|
success(groupName + " was successfully removed");
|
||||||
|
// navigate("/groups");
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
err ? warning(err) : error("Error occured while removing " + groupName);
|
||||||
|
closeDialog();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
fullWidth
|
||||||
|
onClose={closeDialog}
|
||||||
|
aria-labelledby="remove-group-label"
|
||||||
|
aria-describedby="remove-group-description">
|
||||||
|
<DialogTitle id="remove-group-title">Delete {groupName}?</DialogTitle>
|
||||||
|
<DialogContent className={classes.contentPadding}>
|
||||||
|
<Typography color="error" align="left" variant="subtitle1">
|
||||||
|
WARNING:
|
||||||
|
</Typography>
|
||||||
|
<Typography color="textPrimary" align="left" variant="body1">
|
||||||
|
Clicking 'Accept' will delete {groupName} for you and all other users.
|
||||||
|
</Typography>
|
||||||
|
<Typography color="textSecondary" align="left" variant="body2">
|
||||||
|
Devices will NOT be deleted in this process.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={closeDialog} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={submit} color="primary">
|
||||||
|
Accept
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@ export class Group {
|
||||||
my.settings = pond.GroupSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
my.settings = pond.GroupSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
||||||
my.status = pond.GroupStatus.fromObject(cloneDeep(or(pb.status, {})));
|
my.status = pond.GroupStatus.fromObject(cloneDeep(or(pb.status, {})));
|
||||||
}
|
}
|
||||||
|
my.id = my.id.bind(my);
|
||||||
|
my.name = my.name.bind(my);
|
||||||
return my;
|
return my;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
|
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
|
||||||
import { Box, Chip, IconButton, Theme, Tooltip, Typography } from "@mui/material";
|
import { Box, Chip, IconButton, Tab, Tabs, Theme, Tooltip, Typography } from "@mui/material";
|
||||||
import { blue, green } from "@mui/material/colors";
|
import { blue, green } from "@mui/material/colors";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
|
@ -15,6 +15,7 @@ import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||||
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
|
import { getDeviceStateHelper } from "pbHelpers/DeviceState";
|
||||||
import { Group } from "models";
|
import { Group } from "models";
|
||||||
import GroupSettings from "group/GroupSettings";
|
import GroupSettings from "group/GroupSettings";
|
||||||
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -49,11 +50,12 @@ export default function Devices() {
|
||||||
const [devices, setDevices] = useState<pond.Device[]>([])
|
const [devices, setDevices] = useState<pond.Device[]>([])
|
||||||
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const [groupLimit, setGroupLimit] = useState(10);
|
const [groups, setGroups] = useState<Group[]>([])
|
||||||
const [groupPage, setGroupPage] = useState(0);
|
const [groupLimit, ] = useState(10);
|
||||||
|
const [groupPage, ] = useState(0);
|
||||||
const [orderGroup, ] = useState<"asc" | "desc">("asc");
|
const [orderGroup, ] = useState<"asc" | "desc">("asc");
|
||||||
const [orderGroupBy, ] = useState("name");
|
const [orderGroupBy, ] = useState("name");
|
||||||
const [searchGroup, setGroupSearch] = useState("");
|
const [searchGroup, ] = useState("");
|
||||||
const [totalGroups, setTotalGroups] = useState(0);
|
const [totalGroups, setTotalGroups] = useState(0);
|
||||||
|
|
||||||
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
|
const [selectedGroup, setSelectedGroup] = useState<Group | undefined>(undefined);
|
||||||
|
|
@ -62,6 +64,8 @@ export default function Devices() {
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [tab, setTab] = useState("all");
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
setIsProvisionDialogOpen(true);
|
setIsProvisionDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -79,7 +83,7 @@ export default function Devices() {
|
||||||
setSelectedGroup(group);
|
setSelectedGroup(group);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeGroupSettings = (event: any) => {
|
const closeGroupSettings = (_event: any) => {
|
||||||
setGroupSettingsIsOpen(false);
|
setGroupSettingsIsOpen(false);
|
||||||
setGroupSettingsMode(undefined);
|
setGroupSettingsMode(undefined);
|
||||||
setSelectedGroup(undefined);
|
setSelectedGroup(undefined);
|
||||||
|
|
@ -93,10 +97,20 @@ export default function Devices() {
|
||||||
orderGroupBy,
|
orderGroupBy,
|
||||||
searchGroup,
|
searchGroup,
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
console.log(resp.data)
|
// console.log(resp.data)
|
||||||
|
let newGroups: Group[] = []
|
||||||
|
resp.data.groups.forEach((group: pond.Group) => {
|
||||||
|
newGroups.push(Group.create(group))
|
||||||
|
})
|
||||||
|
setGroups(newGroups)
|
||||||
|
setTotalGroups(resp.data.total)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(groups)
|
||||||
|
}, [groups])
|
||||||
|
|
||||||
const loadDevices = () => {
|
const loadDevices = () => {
|
||||||
deviceAPI.list(
|
deviceAPI.list(
|
||||||
limit,
|
limit,
|
||||||
|
|
@ -140,6 +154,11 @@ export default function Devices() {
|
||||||
navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
|
navigate(appendToUrl(or(device.settings?.deviceId, "")), { state: {device: device} })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (_event: React.SyntheticEvent, newValue: string) => {
|
||||||
|
if (newValue !== "never") setTab(newValue);
|
||||||
|
if (newValue === "never") openGroupSettings(undefined, "add");
|
||||||
|
};
|
||||||
|
|
||||||
const columns = (): Column<pond.Device>[] => {
|
const columns = (): Column<pond.Device>[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -170,7 +189,6 @@ export default function Devices() {
|
||||||
label={device.settings?.name ? device.settings.name : "Device " + device.settings?.deviceId}
|
label={device.settings?.name ? device.settings.name : "Device " + device.settings?.deviceId}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -227,6 +245,19 @@ export default function Devices() {
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<PageContainer padding={isMobile ? 0 : 2}>
|
<PageContainer padding={isMobile ? 0 : 2}>
|
||||||
|
<SmartBreadcrumb />
|
||||||
|
<Tabs
|
||||||
|
value={tab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
>
|
||||||
|
<Tab value={"all"} label="All" />
|
||||||
|
{groups.map((group, index) => {
|
||||||
|
if (group.id()) return (
|
||||||
|
<Tab value={group.id()} label={group.name()} key={"group-tab-"+index}/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<Tab value={"never"} label={groupButton()} />
|
||||||
|
</Tabs>
|
||||||
<ResponsiveTable<pond.Device>
|
<ResponsiveTable<pond.Device>
|
||||||
title="Devices"
|
title="Devices"
|
||||||
subtitle={subtitle()}
|
subtitle={subtitle()}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue