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>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue