groupd now generate tabs

This commit is contained in:
Carter 2024-12-19 14:10:16 -06:00
parent c1ff72025a
commit 916d0f9132
4 changed files with 609 additions and 8 deletions

484
src/group/GroupSettings.tsx Normal file
View 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>
);
}