From 58c96417b59a88e4003fe6e66f95098a3cbbbb4d Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 7 Feb 2025 05:03:00 +0100 Subject: [PATCH] refactor(frontend): update permissions dialogs --- .../src/components/roles/PermissionsBody.tsx | 251 ++++++++++++++++ .../roles/PermissionsBodyDialog.tsx | 23 ++ .../components/roles/PermissionsDialog.tsx | 278 ------------------ 3 files changed, 274 insertions(+), 278 deletions(-) create mode 100644 frontend/src/components/roles/PermissionsBody.tsx create mode 100644 frontend/src/components/roles/PermissionsBodyDialog.tsx delete mode 100644 frontend/src/components/roles/PermissionsDialog.tsx diff --git a/frontend/src/components/roles/PermissionsBody.tsx b/frontend/src/components/roles/PermissionsBody.tsx new file mode 100644 index 00000000..abf70ebb --- /dev/null +++ b/frontend/src/components/roles/PermissionsBody.tsx @@ -0,0 +1,251 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import AddIcon from "@mui/icons-material/Add"; +import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined"; +import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Divider, + Grid, + MenuItem, + Paper, + Typography, +} from "@mui/material"; +import { FC, Fragment, useEffect, useState } from "react"; + +import { IconButton } from "@/app-components/buttons/IconButton"; +import { Input } from "@/app-components/inputs/Input"; +import { useCreate } from "@/hooks/crud/useCreate"; +import { useDelete } from "@/hooks/crud/useDelete"; +import { useFind } from "@/hooks/crud/useFind"; +import { useGetFromCache } from "@/hooks/crud/useGet"; +import { useToast } from "@/hooks/useToast"; +import { useTranslate } from "@/hooks/useTranslate"; +import { EntityType, Format } from "@/services/types"; +import { ComponentFormProps } from "@/types/common/dialogs.types"; +import { IPermission, IPermissionAttributes } from "@/types/permission.types"; +import { IRole } from "@/types/role.types"; + +const DEFAULT_PAYLOAD: IPermissionAttributes = { + action: "", + model: "", + relation: "", + role: "", +}; +const AccordionModelHead = () => ( + + + + + Action + + + + + Relation + + + +); + +export const PermissionsBody: FC> = ({ + data: role, + Wrapper = Fragment, + WrapperProps, + ...rest +}) => { + const { t } = useTranslate(); + const { toast } = useToast(); + const { data: models, refetch: modelRefetch } = useFind( + { entity: EntityType.MODEL, format: Format.FULL }, + { + hasCount: false, + }, + ); + const getPermissionFromCache = useGetFromCache(EntityType.PERMISSION); + const options = { + onError: (error: Error) => { + toast.error(error.message || t("message.internal_server_error")); + }, + onSuccess: () => { + modelRefetch(); + toast.success(t("message.item_delete_success")); + }, + }; + const { mutate: createPermission } = useCreate(EntityType.PERMISSION, { + ...options, + onError: (error: Error & { statusCode?: number }) => { + rest.onError?.(); + if (error.statusCode === 409) { + toast.error(t("message.permission_already_exists")); + } else { + toast.error(t("message.internal_server_error")); + } + }, + }); + const { mutate: deletePermission } = useDelete( + EntityType.PERMISSION, + options, + ); + const [expanded, setExpanded] = useState(false); + const [payload, setPayload] = + useState(DEFAULT_PAYLOAD); + const reset = () => setPayload(DEFAULT_PAYLOAD); + const handleChange = + (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { + setExpanded(isExpanded ? panel : false); + }; + + useEffect(() => { + if (expanded === false && models?.[0]?.id) setExpanded(models[0].id); + }, [models]); + + return ( + {}} {...WrapperProps}> + + {role?.name} + + {models?.map((model) => ( + + } + sx={{ + backgroundColor: "background.default", + borderRadius: 1, + fontFamily: "inherit", + }} + > + {model.name} + + + + + {model.permissions + ?.map((p) => getPermissionFromCache(p)) + ?.filter( + (permission) => permission && permission.role === role?.id, + ) + .map((p) => p as IPermission) + .map(({ id, action, relation }, index) => { + return ( + <> + {index > 0 && } + + + deletePermission(id)} + size="small" + > + + + + + {action} + + + {relation} + + + + ); + })} + + + { + if (role?.id) + createPermission({ + ...payload, + role: role.id, + model: model.id, + }); + reset(); + }} + > + + + + + { + if (e.target.value) + setPayload((currentPayload) => ({ + ...currentPayload, + action: e.target.value, + })); + }} + > + {t("label.create")} + {t("label.read")} + {t("label.update")} + {t("label.delete")} + + + + { + if (e.target.value) + setPayload((currentPayload) => ({ + ...currentPayload, + relation: e.target.value, + })); + }} + > + {t("label.role")} + + + + + + + ))} + + ); +}; diff --git a/frontend/src/components/roles/PermissionsBodyDialog.tsx b/frontend/src/components/roles/PermissionsBodyDialog.tsx new file mode 100644 index 00000000..3ee11651 --- /dev/null +++ b/frontend/src/components/roles/PermissionsBodyDialog.tsx @@ -0,0 +1,23 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { GenericFormDialog } from "@/app-components/dialogs"; +import { ComponentFormDialogProps } from "@/types/common/dialogs.types"; +import { IRole } from "@/types/role.types"; + +import { PermissionsBody } from "./PermissionsBody"; + +export const PermissionBodyDialog = ( + props: ComponentFormDialogProps, +) => ( + + Form={PermissionsBody} + editText="title.manage_permissions" + {...props} + /> +); diff --git a/frontend/src/components/roles/PermissionsDialog.tsx b/frontend/src/components/roles/PermissionsDialog.tsx deleted file mode 100644 index dca38329..00000000 --- a/frontend/src/components/roles/PermissionsDialog.tsx +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright © 2024 Hexastack. All rights reserved. - * - * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: - * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. - * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). - */ - -import AddIcon from "@mui/icons-material/Add"; -import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Dialog, - Grid, - MenuItem, - Paper, - Typography, - DialogContent, - DialogActions, - Button, - Divider, -} from "@mui/material"; -import { useState, FC, useEffect } from "react"; - -import { IconButton } from "@/app-components/buttons/IconButton"; -import { DialogTitle } from "@/app-components/dialogs/DialogTitle"; -import { Input } from "@/app-components/inputs/Input"; -import { useCreate } from "@/hooks/crud/useCreate"; -import { useDelete } from "@/hooks/crud/useDelete"; -import { useFind } from "@/hooks/crud/useFind"; -import { useGetFromCache } from "@/hooks/crud/useGet"; -import { DialogControlProps } from "@/hooks/useDialog"; -import { useToast } from "@/hooks/useToast"; -import { useTranslate } from "@/hooks/useTranslate"; -import { EntityType, Format } from "@/services/types"; -import { IPermission, IPermissionAttributes } from "@/types/permission.types"; -import { IRole } from "@/types/role.types"; - -export type PermissionsDialogProps = DialogControlProps<{ - role: IRole; -}>; - -const DEFAULT_PAYLOAD: IPermissionAttributes = { - action: "", - model: "", - relation: "", - role: "", -}; -const AccordionModelHead = () => ( - - - - - Action - - - - - Relation - - - -); - -export const PermissionsDialog: FC = ({ - open, - data, - closeDialog: closeFunction, -}) => { - const { t } = useTranslate(); - const { toast } = useToast(); - const { data: models, refetch: modelRefetch } = useFind( - { entity: EntityType.MODEL, format: Format.FULL }, - { - hasCount: false, - }, - ); - const getPermisionFromCache = useGetFromCache(EntityType.PERMISSION); - const { mutateAsync: createPermission } = useCreate(EntityType.PERMISSION, { - onError: (error: Error & { statusCode?: number }) => { - if (error.statusCode === 409) { - toast.error(t("message.permission_already_exists")); - } else { - toast.error(t("message.internal_server_error")); - } - }, - onSuccess: () => { - modelRefetch(); - toast.success(t("message.success_save")); - }, - }); - const { mutateAsync: deletePermission } = useDelete(EntityType.PERMISSION, { - onError: () => { - toast.error(t("message.internal_server_error")); - }, - onSuccess: () => { - modelRefetch(); - toast.success(t("message.item_delete_success")); - }, - }); - const [expanded, setExpanded] = useState(false); - const [payload, setPayload] = - useState(DEFAULT_PAYLOAD); - const reset = () => setPayload(DEFAULT_PAYLOAD); - const handleChange = - (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { - setExpanded(isExpanded ? panel : false); - }; - - useEffect(() => { - if (expanded === false && models?.[0]?.id) setExpanded(models[0].id); - }, [models]); - - return ( - - - {t("title.manage_permissions")} - - - - {data?.role.name} - - {models?.map((model) => { - return ( - - } - sx={{ - backgroundColor: "background.default", - borderRadius: 1, - fontFamily: "inherit", - }} - > - {model.name} - - - - - {model.permissions - ?.map((p) => getPermisionFromCache(p)) - ?.filter( - (permission) => - permission && permission.role === data?.role.id, - ) - .map((p) => p as IPermission) - .map(({ id, action, relation }, index) => { - return ( - <> - {index > 0 && } - - - { - deletePermission(id); - }} - size="small" - > - - - - - {action} - - - {relation} - - - - ); - })} - - - { - if (data?.role.id) - createPermission({ - ...payload, - model: model.id, - role: data.role.id, - }); - reset(); - }} - > - - - - - { - if (e.target.value) - setPayload((currentPayload) => ({ - ...currentPayload, - action: e.target.value, - })); - }} - > - {t("label.create")} - {t("label.read")} - {t("label.update")} - {t("label.delete")} - - - - { - if (e.target.value) - setPayload((currentPayload) => ({ - ...currentPayload, - relation: e.target.value, - })); - }} - > - {t("label.role")} - - - - - - - ); - })} - - - - - - ); -};