diff --git a/jams-react-client/src/views/Blueprints/Blueprints.js b/jams-react-client/src/views/Blueprints/Blueprints.js index f5bc0b5d2a2f97a91235d5252dfe85bd985b487e..3d204a78e95a11996b08ca246c28e78de56b7d53 100644 --- a/jams-react-client/src/views/Blueprints/Blueprints.js +++ b/jams-react-client/src/views/Blueprints/Blueprints.js @@ -1,8 +1,7 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Link, useHistory } from "react-router-dom"; // @mui/material components import { makeStyles } from "@mui/styles"; -import InputLabel from "@mui/material/InputLabel"; // core components import GridItem from "components/Grid/GridItem.js"; import GridContainer from "components/Grid/GridContainer.js"; @@ -13,17 +12,12 @@ import Card from "components/Card/Card.js"; import CardBody from "components/Card/CardBody.js"; import CardFooter from "components/Card/CardFooter.js"; -import FormControl from "@mui/material/FormControl"; -import Input from "@mui/material/Input"; -import InputAdornment from "@mui/material/InputAdornment"; - import GroupIcon from "@mui/icons-material/Group"; import PersonIcon from "@mui/icons-material/Person"; import Search from "@mui/icons-material/Search"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import InfoIcon from "@mui/icons-material/Info"; -import AllInbox from "@mui/icons-material/AllInbox"; import axios from "axios"; import configApiCall from "api.js"; import auth from "auth.js"; @@ -41,9 +35,8 @@ import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; -import { debounce } from "lodash"; - import i18next from "i18next"; +import CreateBlueprintDialog from "./CreateBlueprintDialog"; const styles = { ...headerLinksStyle, @@ -77,9 +70,6 @@ const styles = { display: "flex", alignItems: "center", }, - whiteButtonText: { - color: "white", - }, }; const useStyles = makeStyles(styles); @@ -92,12 +82,8 @@ export default function Blueprints() { const [zeroBlueprint, setZeroBlueprint] = useState(false); const [progress, setProgress] = useState(0); - const [blueprintName, setBlueprintName] = useState(""); - const [blueprintNameExits, setBlueprintNameExits] = useState(false); const [open, setOpen] = useState(false); - const [disableCreate, setDisableCreate] = useState(true); - const [removedBlueprint, setRemovedBlueprint] = useState(""); const [openRemoveDialog, setOpenRemoveDialog] = useState(false); @@ -132,76 +118,6 @@ export default function Blueprints() { }; }, [history, open, openRemoveDialog]); - const handleCheckBlueprintNameExists = (searchBlueprintNameValue) => { - setDisableCreate(true); - axios( - configApiCall( - api_path_blueprints + "?name=" + searchBlueprintNameValue, - "GET", - null, - null - ) - ) - .then(() => { - setDisableCreate(true); - setBlueprintNameExits(true); - }) - .catch(() => { - setDisableCreate(false); - setBlueprintNameExits(false); - }); - }; - - const initCheckBlueprintNameExists = useCallback( - debounce( - (searchBlueprintNameValue) => - handleCheckBlueprintNameExists(searchBlueprintNameValue), - 500 - ), - [] - ); - - const handleClose = () => { - setOpen(false); - }; - - const handleCreateBlueprint = () => { - let defaultPolicyData = { - videoEnabled: true, - publicInCalls: false, - allowCertFromContact: true, - allowCertFromHistory: true, - allowCertFromTrusted: true, - autoAnswer: false, - peerDiscovery: true, - accountDiscovery: true, - accountPublish: true, - rendezVous: false, - defaultModerators: "", - upnpEnabled: true, - allowLookup: true, - }; - - axios( - configApiCall( - api_path_blueprints + "?name=" + blueprintName, - "POST", - defaultPolicyData, - null - ) - ) - .then(() => { - setOpen(false); - setDisableCreate(true); - console.log("Successfully created " + blueprintName); - }) - .catch((error) => { - setOpen(false); - console.log("Could not create " + blueprintName + " " + error); - }); - history.push(`/blueprint/${blueprintName}`); - }; - const handleRemoveBlueprint = (blueprintRemovedName) => { setRemovedBlueprint(blueprintRemovedName); setOpenRemoveDialog(true); @@ -217,7 +133,7 @@ export default function Blueprints() { ) ) .then(() => { - console.log("Successfully create " + removedBlueprint); + console.log("Successfully created " + removedBlueprint); setOpenRemoveDialog(false); }) .catch((error) => { @@ -229,67 +145,7 @@ export default function Blueprints() { return ( <div> - <Dialog - open={open} - onClose={handleClose} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - <DialogTitle id="alert-dialog-title"> - {i18next.t("create_blueprint", "Create blueprint")} - </DialogTitle> - <DialogContent> - <FormControl - className={classes.margin} - size="medium" - error={blueprintNameExits} - > - <InputLabel htmlFor="blueprintName"> - {i18next.t("blueprint_name", "Blueprint name")} - </InputLabel> - <Input - id="blueprintName" - placeholder={i18next.t("blueprint_name", "Blueprint name")} - startAdornment={ - <InputAdornment position="start"> - <AllInbox /> - </InputAdornment> - } - onChange={(e) => { - setBlueprintName(e.target.value); - initCheckBlueprintNameExists(e.target.value); - }} - /> - </FormControl> - {disableCreate && blueprintName.length > 0 && ( - <p> - {i18next.t( - "blueprint_name_already_exists", - "Blueprint name already exists!" - )} - </p> - )} - {disableCreate && blueprintName.length === 0 && ( - <p> - {i18next.t("blueprint_name_is_empty", "Blueprint name is empty")} - </p> - )} - </DialogContent> - <DialogActions> - <Button onClick={handleClose} color="primary"> - {i18next.t("cancel", "Cancel")} - </Button> - <Button - onClick={handleCreateBlueprint} - color="info" - className={classes.whiteButtonText} - disabled={disableCreate} - autoFocus - > - {i18next.t("create_blueprint", "Create blueprint")} - </Button> - </DialogActions> - </Dialog> + <CreateBlueprintDialog open={open} setOpen={setOpen} /> <Dialog open={openRemoveDialog} onClose={() => setOpenRemoveDialog(false)} diff --git a/jams-react-client/src/views/Blueprints/CreateBlueprintDialog.js b/jams-react-client/src/views/Blueprints/CreateBlueprintDialog.js new file mode 100644 index 0000000000000000000000000000000000000000..ff6e1fa40178f1635b14513ba54f9a6c0d1a5e69 --- /dev/null +++ b/jams-react-client/src/views/Blueprints/CreateBlueprintDialog.js @@ -0,0 +1,174 @@ +import React, { useCallback, useState } from "react"; +import { useHistory } from "react-router-dom"; +import axios from "axios"; +import i18next from "i18next"; +import { + Dialog, + DialogTitle, + DialogContent, + FormControl, + InputLabel, + Input, + InputAdornment, + DialogActions, + Button, + debounce, +} from "@mui/material"; +import { AllInbox } from "@mui/icons-material"; +import { makeStyles } from "@mui/styles"; + +import configApiCall from "api"; +import { api_path_blueprints } from "globalUrls"; +import headerLinksStyle from "assets/jss/material-dashboard-react/components/headerLinksStyle"; + +const styles = { + ...headerLinksStyle, + whiteButtonText: { + color: "white", + }, +}; + +const useStyles = makeStyles(styles); + +export default function CreateBlueprintDialog({ open, setOpen }) { + const history = useHistory(); + const classes = useStyles(); + + const [blueprintName, setBlueprintName] = useState(""); + const [blueprintNameExits, setBlueprintNameExits] = useState(false); + const [disableCreate, setDisableCreate] = useState(true); + + const handleClose = () => { + setOpen(false); + }; + + const handleCheckBlueprintNameExists = (searchBlueprintNameValue) => { + setDisableCreate(true); + axios( + configApiCall( + api_path_blueprints + "?name=" + searchBlueprintNameValue, + "GET", + null, + null + ) + ) + .then(() => { + setDisableCreate(true); + setBlueprintNameExits(true); + }) + .catch(() => { + setDisableCreate(false); + setBlueprintNameExits(false); + }); + }; + + const initCheckBlueprintNameExists = useCallback( + debounce( + (searchBlueprintNameValue) => + handleCheckBlueprintNameExists(searchBlueprintNameValue), + 500 + ), + [] + ); + + const handleCreateBlueprint = () => { + let defaultPolicyData = { + videoEnabled: true, + publicInCalls: false, + allowCertFromContact: true, + allowCertFromHistory: true, + allowCertFromTrusted: true, + autoAnswer: false, + peerDiscovery: true, + accountDiscovery: true, + accountPublish: true, + rendezVous: false, + defaultModerators: "", + upnpEnabled: true, + allowLookup: true, + }; + + axios( + configApiCall( + api_path_blueprints + "?name=" + blueprintName, + "POST", + defaultPolicyData, + null + ) + ) + .then(() => { + setOpen(false); + setDisableCreate(true); + console.log("Successfully created " + blueprintName); + }) + .catch((error) => { + setOpen(false); + console.log("Could not create " + blueprintName + " " + error); + }); + history.push(`/blueprint/${blueprintName}`); + }; + + return ( + <Dialog + open={open} + onClose={handleClose} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title"> + {i18next.t("create_blueprint", "Create blueprint")} + </DialogTitle> + <DialogContent> + <FormControl + className={classes.margin} + size="medium" + error={blueprintNameExits} + > + <InputLabel htmlFor="blueprintName"> + {i18next.t("blueprint_name", "Blueprint name")} + </InputLabel> + <Input + id="blueprintName" + placeholder={i18next.t("blueprint_name", "Blueprint name")} + startAdornment={ + <InputAdornment position="start"> + <AllInbox /> + </InputAdornment> + } + onChange={(e) => { + setBlueprintName(e.target.value); + initCheckBlueprintNameExists(e.target.value); + }} + /> + </FormControl> + {disableCreate && blueprintName.length > 0 && ( + <p> + {i18next.t( + "blueprint_name_already_exists", + "Blueprint name already exists!" + )} + </p> + )} + {disableCreate && blueprintName.length === 0 && ( + <p> + {i18next.t("blueprint_name_is_empty", "Blueprint name is empty")} + </p> + )} + </DialogContent> + <DialogActions> + <Button onClick={handleClose} color="primary"> + {i18next.t("cancel", "Cancel")} + </Button> + <Button + onClick={handleCreateBlueprint} + color="info" + className={classes.whiteButtonText} + disabled={disableCreate} + autoFocus + > + {i18next.t("create_blueprint", "Create blueprint")} + </Button> + </DialogActions> + </Dialog> + ); +} diff --git a/jams-react-client/src/views/Groups/CreateGroupDialog.js b/jams-react-client/src/views/Groups/CreateGroupDialog.js new file mode 100644 index 0000000000000000000000000000000000000000..084978356c4b468ef96b7e89359ab998881b808d --- /dev/null +++ b/jams-react-client/src/views/Groups/CreateGroupDialog.js @@ -0,0 +1,224 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { useHistory } from "react-router-dom"; +import { + Dialog, + DialogTitle, + DialogContent, + Grid, + FormControl, + InputLabel, + Input, + InputAdornment, + Select, + DialogActions, +} from "@mui/material"; +import PeopleOutlineIcon from "@mui/icons-material/PeopleOutline"; +import axios from "axios"; +import i18next from "i18next"; +import { debounce } from "lodash"; + +import configApiCall from "api"; +import { + api_path_blueprints, + api_path_get_list_group, + api_path_post_create_group, +} from "globalUrls"; +import { getBlueprintsOptions } from "./getBlueprintsOptions"; +import auth from "auth"; +import * as tool from "../../tools"; +import Button from "components/CustomButtons/Button.js"; + +export default function CreateGroupDialog({ + openCreate, + setOpenCreate, + classes, +}) { + const history = useHistory(); + + const [groupName, setGroupName] = useState(""); + const [groupNameExits, setGroupNameExits] = useState(false); + const [disableCreate, setDisableCreate] = useState(true); + + const [blueprints, setBlueprints] = useState([]); + const [selectedBlueprint, setSelectedBlueprint] = useState({ + value: 0, + label: "No blueprint", + }); + + const handleCheckGroupNameExists = (searchGroupNameValue) => { + setDisableCreate(true); + axios( + configApiCall( + api_path_get_list_group + "?groupName=" + searchGroupNameValue, + "GET", + null, + null + ) + ) + .then((response) => { + let allGroups = response.data; + setDisableCreate(false); + setGroupNameExits(false); + allGroups.forEach((group) => { + if (searchGroupNameValue === group.name) { + setDisableCreate(true); + setGroupNameExits(true); + } + }); + }) + .catch(() => { + setDisableCreate(false); + setGroupNameExits(false); + }); + }; + + const initCheckGroupNameExists = useCallback( + debounce( + (searchGroupNameValue) => + handleCheckGroupNameExists(searchGroupNameValue), + 500 + ), + [] + ); + + const handleCloseCreate = () => { + setOpenCreate(false); + }; + + const handleCreateGroup = () => { + let blueprintName = ""; + if (selectedBlueprint.label !== "No blueprint") + blueprintName = selectedBlueprint.label; + + axios( + configApiCall( + api_path_post_create_group, + "POST", + { name: groupName, blueprintName }, + null + ) + ) + .then((response) => { + console.log("Successfully created " + groupName); + setOpenCreate(false); + history.push(`/group/${response.data.id}`); + }) + .catch((error) => { + alert("Error creating group: " + error); + setOpenCreate(false); + }); + }; + + const blueprintsOptionsItems = tool.buildSelectMenuItems( + getBlueprintsOptions(blueprints) + ); + + const handleBlueprintsChange = (e) => { + setSelectedBlueprint(getBlueprintsOptions(blueprints)[e.target.value]); + }; + + const getBlueprints = () => { + axios(configApiCall(api_path_blueprints + "?name=*", "GET", null, null)) + .then((response) => { + setBlueprints(response.data); + setSelectedBlueprint(getBlueprintsOptions(response.data)[0]); + }) + .catch((error) => { + if (error.response.status === 401) { + auth.authenticated = false; + history.push("/signin"); + } + if (error.response.status === 500) { + setBlueprints([]); + } + }); + }; + + useEffect(() => { + getBlueprints(); + }, []); + + return ( + <Dialog + open={openCreate} + onClose={handleCloseCreate} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + <DialogTitle id="alert-dialog-title"> + {i18next.t("create_group", "Create group")} + </DialogTitle> + <DialogContent> + <Grid container spacing={2}> + <Grid item xs={12} sm={12} md={12}> + <FormControl + className={classes.margin} + error={groupNameExits} + fullWidth + > + <InputLabel htmlFor="groupName"> + {i18next.t("group_name", "Group name")} + </InputLabel> + <Input + id="groupName" + placeholder={i18next.t("group_name", "Group name")} + startAdornment={ + <InputAdornment position="start"> + <PeopleOutlineIcon /> + </InputAdornment> + } + onChange={(e) => { + setGroupName(e.target.value); + initCheckGroupNameExists(e.target.value); + }} + /> + </FormControl> + {disableCreate && groupName.length > 0 && ( + <p> + {i18next.t( + "group_name_already_exists", + "Group name already exists!" + )} + </p> + )} + {disableCreate && groupName.length === 0 && ( + <p>{i18next.t("group_name_is_empty", "Group name is empty")}</p> + )} + </Grid> + <Grid item xs={12} sm={12} md={12}> + <InputLabel + className={classes.inputBottomMargin} + htmlFor="blueprint" + > + {i18next.t("select_blueprint", "Select a blueprint")} + </InputLabel> + <Select + labelId="demo-simple-select-label" + fullWidth + value={selectedBlueprint.value} + onChange={handleBlueprintsChange} + variant="outlined" + disabled={blueprints.length === 0} + > + {blueprintsOptionsItems} + </Select> + </Grid> + </Grid> + </DialogContent> + <DialogActions> + <Button onClick={handleCloseCreate} color="primary"> + Cancel + </Button> + <Button + onClick={handleCreateGroup} + color="info" + className={classes.whiteButtonText} + disabled={disableCreate} + autoFocus + > + Create + </Button> + </DialogActions> + </Dialog> + ); +} diff --git a/jams-react-client/src/views/Groups/Groups.js b/jams-react-client/src/views/Groups/Groups.js index 3461d86d0becdf3e2688ddea120fbb1dc2f59ef1..90491f336fc43d82fafe71f586229c32ba886b77 100644 --- a/jams-react-client/src/views/Groups/Groups.js +++ b/jams-react-client/src/views/Groups/Groups.js @@ -1,10 +1,8 @@ -import React, { useEffect, useState, useCallback } from "react"; +import React, { useEffect, useState } from "react"; import { Link, useHistory } from "react-router-dom"; // @mui/material components import { makeStyles } from "@mui/styles"; -import InputLabel from "@mui/material/InputLabel"; // core components -import Grid from "@mui/material/Grid"; import GridItem from "components/Grid/GridItem.js"; import GridContainer from "components/Grid/GridContainer.js"; import Button from "components/CustomButtons/Button.js"; @@ -16,44 +14,32 @@ import CustomInput from "components/CustomInput/CustomInput.js"; import IconButton from "@mui/material/IconButton"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import Search from "@mui/icons-material/Search"; -import PeopleOutlineIcon from "@mui/icons-material/PeopleOutline"; import MailOutlineIcon from "@mui/icons-material/MailOutline"; import PersonIcon from "@mui/icons-material/Person"; import InfoIcon from "@mui/icons-material/Info"; -import Select from "@mui/material/Select"; - -import * as tool from "../../tools"; import axios from "axios"; import configApiCall from "api.js"; import auth from "auth.js"; import { - api_path_post_create_group, api_path_get_list_group, api_path_delete_group, - api_path_blueprints, api_path_get_group_members, } from "globalUrls"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; -import FormControl from "@mui/material/FormControl"; -import Input from "@mui/material/Input"; -import InputAdornment from "@mui/material/InputAdornment"; - import Dialog from "@mui/material/Dialog"; import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; -import { debounce } from "lodash"; - import LinearProgress from "@mui/material/LinearProgress"; import i18next from "i18next"; -import { getBlueprintsOptions } from "./getBlueprintsOptions"; +import CreateGroupDialog from "./CreateGroupDialog"; const styles = { cardCategoryWhite: { @@ -105,11 +91,7 @@ export default function Groups() { const [progress, setProgress] = useState(0); const [searchValue, setSearchValue] = useState(null); - const [blueprints, setBlueprints] = useState([]); - const [openCreate, setOpenCreate] = useState(false); - const [groupName, setGroupName] = useState(""); - const [groupNameExits, setGroupNameExits] = useState(false); const [removedGroup, setRemovedGroup] = useState({ id: 0, @@ -117,8 +99,6 @@ export default function Groups() { }); const [openRemoveDialog, setOpenRemoveDialog] = useState(false); - const [disableCreate, setDisableCreate] = useState(true); - const handleRemoveGroup = (group) => { setRemovedGroup(group); setOpenRemoveDialog(true); @@ -145,23 +125,6 @@ export default function Groups() { history.push("/groups"); }; - const getBlueprints = () => { - axios(configApiCall(api_path_blueprints + "?name=*", "GET", null, null)) - .then((response) => { - setBlueprints(response.data); - setSelectedBlueprint(getBlueprintsOptions(response.data)[0]); - }) - .catch((error) => { - if (error.response.status === 401) { - auth.authenticated = false; - history.push("/signin"); - } - if (error.response.status === 500) { - setBlueprints([]); - } - }); - }; - useEffect(() => { setLoading(true); const timer = setInterval(() => { @@ -201,7 +164,6 @@ export default function Groups() { }); }); setGroups(allGroups); - getBlueprints(); setLoading(false); }) .catch((error) => { @@ -210,7 +172,6 @@ export default function Groups() { history.push("/signin"); } if (error.response.status === 404) { - getBlueprints(); setZeroGroup(true); } }); @@ -219,83 +180,6 @@ export default function Groups() { }; }, [openCreate, openRemoveDialog, history]); - const handleCheckGroupNameExists = (searchGroupNameValue) => { - setDisableCreate(true); - axios( - configApiCall( - api_path_get_list_group + "?groupName=" + searchGroupNameValue, - "GET", - null, - null - ) - ) - .then((response) => { - let allGroups = response.data; - setDisableCreate(false); - setGroupNameExits(false); - allGroups.forEach((group) => { - if (searchGroupNameValue === group.name) { - setDisableCreate(true); - setGroupNameExits(true); - } - }); - }) - .catch(() => { - setDisableCreate(false); - setGroupNameExits(false); - }); - }; - - const initCheckGroupNameExists = useCallback( - debounce( - (searchGroupNameValue) => - handleCheckGroupNameExists(searchGroupNameValue), - 500 - ), - [] - ); - - const handleCloseCreate = () => { - setOpenCreate(false); - }; - - const handleCreateGroup = () => { - let blueprintName = ""; - if (selectedBlueprint.label !== "No blueprint") - blueprintName = selectedBlueprint.label; - - axios( - configApiCall( - api_path_post_create_group, - "POST", - { name: groupName, blueprintName }, - null - ) - ) - .then((response) => { - console.log("Successfully created " + groupName); - setOpenCreate(false); - history.push(`/group/${response.data.id}`); - }) - .catch((error) => { - alert("Error creating group: " + error); - setOpenCreate(false); - }); - }; - - const blueprintsOptionsItems = tool.buildSelectMenuItems( - getBlueprintsOptions(blueprints) - ); - - const [selectedBlueprint, setSelectedBlueprint] = useState({ - value: 0, - label: "No blueprint", - }); - - const handleBlueprintsChange = (e) => { - setSelectedBlueprint(getBlueprintsOptions(blueprints)[e.target.value]); - }; - if (!auth.hasAdminScope()) { return ( <div> @@ -310,89 +194,11 @@ export default function Groups() { } else { return ( <div> - <Dialog - open={openCreate} - onClose={handleCloseCreate} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - <DialogTitle id="alert-dialog-title"> - {i18next.t("create_group", "Create group")} - </DialogTitle> - <DialogContent> - <Grid container spacing={2}> - <Grid item xs={12} sm={12} md={12}> - <FormControl - className={classes.margin} - error={groupNameExits} - fullWidth - > - <InputLabel htmlFor="groupName"> - {i18next.t("group_name", "Group name")} - </InputLabel> - <Input - id="groupName" - placeholder={i18next.t("group_name", "Group name")} - startAdornment={ - <InputAdornment position="start"> - <PeopleOutlineIcon /> - </InputAdornment> - } - onChange={(e) => { - setGroupName(e.target.value); - initCheckGroupNameExists(e.target.value); - }} - /> - </FormControl> - {disableCreate && groupName.length > 0 && ( - <p> - {i18next.t( - "group_name_already_exists", - "Group name already exists!" - )} - </p> - )} - {disableCreate && groupName.length === 0 && ( - <p> - {i18next.t("group_name_is_empty", "Group name is empty")} - </p> - )} - </Grid> - <Grid item xs={12} sm={12} md={12}> - <InputLabel - className={classes.inputBottomMargin} - htmlFor="blueprint" - > - {i18next.t("select_blueprint", "Select a blueprint")} - </InputLabel> - <Select - labelId="demo-simple-select-label" - fullWidth - value={selectedBlueprint.value} - onChange={handleBlueprintsChange} - variant="outlined" - disabled={blueprints.length === 0} - > - {blueprintsOptionsItems} - </Select> - </Grid> - </Grid> - </DialogContent> - <DialogActions> - <Button onClick={handleCloseCreate} color="primary"> - Cancel - </Button> - <Button - onClick={handleCreateGroup} - color="info" - className={classes.whiteButtonText} - disabled={disableCreate} - autoFocus - > - Create - </Button> - </DialogActions> - </Dialog> + <CreateGroupDialog + openCreate={openCreate} + setOpenCreate={setOpenCreate} + classes={classes} + /> <Dialog open={openRemoveDialog} onClose={() => setOpenRemoveDialog(false)}