From 2aa245e61e05e5b7044e17e151887a3e9d780291 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Fri, 24 May 2024 17:33:13 +0100 Subject: [PATCH 1/4] feat: add update role --- src/app/privilege/PrivilegeTableRow.jsx | 68 ++++++----- src/app/role/CreateRoleForm.jsx | 72 ++++++----- src/app/role/RoleTableRows.jsx | 151 ++++++------------------ src/app/role/UpdateRoleForm.jsx | 144 ++++++++++++++++++++++ src/app/role/page.jsx | 24 ++-- src/static/image/svg/add.svg | 1 + 6 files changed, 282 insertions(+), 178 deletions(-) create mode 100644 src/app/role/UpdateRoleForm.jsx create mode 100644 src/static/image/svg/add.svg diff --git a/src/app/privilege/PrivilegeTableRow.jsx b/src/app/privilege/PrivilegeTableRow.jsx index 96f1d1d..7537e45 100644 --- a/src/app/privilege/PrivilegeTableRow.jsx +++ b/src/app/privilege/PrivilegeTableRow.jsx @@ -7,11 +7,16 @@ import EditIcon from "@/static/image/svg/edit.svg" import CancelIcon from "@/static/image/svg/cancel.svg" import CheckIcon from "@/static/image/svg/check.svg" import { useNotification } from '@/context/NotificationContext' +import ConfirmationModal from '../ui/ConfirmationModal' const PrivilegeTableRow = ({ id, name, setPrivileges }) => { const [isUpdating, setIsUpdating] = useState(false) const [privilegeName, setPrivilegeName] = useState(name) const [loadingStatus, setLoadingStatus] = useState(false) const { toggleNotification } = useNotification() + const [isModalOpen, setModalOpen] = useState(false) + const showDeletePopup = () => { + setModalOpen(true); + } useEffect(() => { setPrivilegeName(name) inputRef.current.value = name @@ -87,6 +92,7 @@ const PrivilegeTableRow = ({ id, name, setPrivileges }) => { type: "error" }) } + setModalOpen(false) } const cancelUpdate = () => { setIsUpdating(false) @@ -126,33 +132,41 @@ const PrivilegeTableRow = ({ id, name, setPrivileges }) => { } }, [isUpdating]) return ( - - - setPrivilegeName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> - - - {!isUpdating - ?
- - -
- :
- - -
- } - - + <> + + + setPrivilegeName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> + + + {!isUpdating + ?
+ + +
+ :
+ + +
+ } + + + setModalOpen(false)} + onConfirm={handleDelete} + message={`Voulez-vous vraiment supprimer l'habilitation ?`} + /> + ) } diff --git a/src/app/role/CreateRoleForm.jsx b/src/app/role/CreateRoleForm.jsx index 29e177c..546c0c0 100644 --- a/src/app/role/CreateRoleForm.jsx +++ b/src/app/role/CreateRoleForm.jsx @@ -2,10 +2,9 @@ import Loader from '@/components/Loader/Loader' import React, { useState, useRef, useEffect, useMemo } from 'react' import fetchRequest from '../lib/fetchRequest' import { useNotification } from '@/context/NotificationContext' - -const CreateRoleForm = ({ appendRole }) => { +import CancelIcon from "@/static/image/svg/cancel.svg" +const CreateRoleForm = ({ appendRole, setIsOpen }) => { const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) const [roleName, setRoleName] = useState("") const [privileges, setPrivileges] = useState(null) const [selectedPrivileges, setSelectedPrivileges] = useState([]) @@ -28,7 +27,6 @@ const CreateRoleForm = ({ appendRole }) => { getPrivileges() }, []) const handleRoleNameChange = (event) => { - setError("") setRoleName(event.target.value) } const inputRef = useRef(null) @@ -50,6 +48,7 @@ const CreateRoleForm = ({ appendRole }) => { message: "Le rôle a été créé avec succès", type: "success" }) + setIsOpen(false) } else { setIsLoading(false) if (errors.type === "ValidationError") { @@ -66,6 +65,7 @@ const CreateRoleForm = ({ appendRole }) => { message: "Erreur de validation de rôle", type: "warning" }) + setIsOpen(false) } } else if (status === 409) { @@ -74,12 +74,21 @@ const CreateRoleForm = ({ appendRole }) => { message: "Roles created with 2 or more same privileges", type: "error" }) + setIsOpen(false) + } else if (errors.detail === "Privilege matching query does not exist.") { + toggleNotification({ + visible: true, + message: "Des privilèges que vous avez utilisés ont été supprimés.", + type: "warning" + }) + setIsOpen(false) } else { toggleNotification({ visible: true, message: "Internal Server Error", type: "error" }) + setIsOpen(false) } console.log(errors) } @@ -98,32 +107,37 @@ const CreateRoleForm = ({ appendRole }) => { } var isAllSelected = useMemo(() => privileges ? privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id)) : false, [selectedPrivileges, privileges]) return ( -
-

Ajout de Rôle

-
- - -
-

{error}

- {(privileges) ?
-
- -
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
- -
-
- {privileges?.map((privilege) => { - const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined - return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
- })} -
-
:
} -
- +
+
+ setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> + +

Ajout de Rôle

+
+ + +
+ {(privileges) ?
+
+ +
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
+
+
+ {privileges.length !== 0 ? privileges?.map((privilege) => { + const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined + return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
+ }) :
+

Pas encore des habilitations

+
} +
+
:
} +
+ +
+
- +
) } diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index b6c4ea8..43444b5 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -1,67 +1,19 @@ import React, { useEffect, useRef, useState } from 'react' import fetchRequest from '../lib/fetchRequest' -import Loader from '@/components/Loader/Loader' import DeleteIcon from "@/static/image/svg/delete.svg" import EditIcon from "@/static/image/svg/edit.svg" -import CancelIcon from "@/static/image/svg/cancel.svg" -import CheckIcon from "@/static/image/svg/check.svg" import { useNotification } from '@/context/NotificationContext' -const RoleTableRows = ({ name, setRoles, id, privileges }) => { - const [isUpdating, setIsUpdating] = useState(false) - const [roleName, setRoleName] = useState(name) - const [loadingStatus, setLoadingStatus] = useState(false) +import ConfirmationModal from '../ui/ConfirmationModal' + +const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { const { toggleNotification } = useNotification() + const [isModalOpen, setModalOpen] = useState(false); + const inputRef = useRef(null) useEffect(() => { - setRoleName(name) inputRef.current.value = name }, [name]) - const handleUpdatePrivilege = async () => { - setLoadingStatus(true) - const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { - method: "PATCH", - body: JSON.stringify({ name: roleName }) - }) - setLoadingStatus(false) - if (isSuccess) { - setRoles((roles) => roles.map((element) => element.id === id ? data.data : element)) - setIsUpdating(false) - toggleNotification({ - visible: true, - message: "Le rôle a été modifé avec succès", - type: "success" - }) - } else { - if (errors.type === "ValidationError") { - if (errors.detail.name) { - toggleNotification({ - type: "warning", - message: "Le nom de rôle existe déjà", - visible: true, - }) - } - else { - toggleNotification({ - visible: true, - message: "Erreur de validation de rôle", - type: "warning" - }) - } - } else if (status === 404) { - toggleNotification({ - visible: true, - message: "Le rôle n'a pas été trouvé", - type: "warning" - }) - } else { - toggleNotification({ - visible: true, - message: "Internal Server Error", - type: "error" - }) - } - console.log(errors) - } - + const showDeletePopup = () => { + setModalOpen(true); } const handleDelete = async () => { const { isSuccess, errors, status } = await fetchRequest(`/roles/${id}/`, { method: "DELETE" }) @@ -78,7 +30,14 @@ const RoleTableRows = ({ name, setRoles, id, privileges }) => { message: "Le rôle n'a pas été trouvé", type: "warning" }) - } else { + } else if (errors.detail?.indexOf("Cannot delete some instances of model 'Role'") !== -1) { + toggleNotification({ + visible: true, + message: "Impossible de supprimer ce rôle car il est attribué à des utilisateurs", + type: "warning" + }) + } + else { console.log(errors) toggleNotification({ visible: true, @@ -86,73 +45,35 @@ const RoleTableRows = ({ name, setRoles, id, privileges }) => { type: "error" }) } + setModalOpen(false) console.log(errors) } - const cancelUpdate = () => { - setIsUpdating(false) - setRoleName(name) - inputRef.current.value = name - } - const inputRef = useRef(null) - const rowRef = useRef(null) - const handleUpdateBlur = (event) => { - const eventTarget = event.target - let isInsideRowRef = false; - let element = eventTarget; - while (element !== null) { - if (element === rowRef.current) { - isInsideRowRef = true; - break; - } - if (element.parentElement === null) { - isInsideRowRef = false; - break; - } - element = element.parentElement; - } - if (!isInsideRowRef && element?.classList.contains("privilegeRowSVG")) return; - if (!isInsideRowRef) { - cancelUpdate(); - document.removeEventListener("click", handleUpdateBlur); - } - } - useEffect(() => { - if (isUpdating && inputRef?.current) { - inputRef.current.focus() - document.addEventListener("click", handleUpdateBlur) - } - return () => { - document.removeEventListener("click", handleUpdateBlur) - } - }, [isUpdating]) + return ( - - - setRoleName(event.target.value)} defaultValue={name} type='text' className='disabled:bg-white w-full border-0 rounded-md px-2 enabled:drop-shadow border-none enabled:bg-gray-100 duration-100 h-10 outline-none' /> - - - {!isUpdating - ?
- -
- :
- - -
- } - - + + + setModalOpen(false)} + onConfirm={handleDelete} + message={`Voulez-vous vraiment supprimer le rôle ?`} + /> + + ) } diff --git a/src/app/role/UpdateRoleForm.jsx b/src/app/role/UpdateRoleForm.jsx new file mode 100644 index 0000000..7bf368b --- /dev/null +++ b/src/app/role/UpdateRoleForm.jsx @@ -0,0 +1,144 @@ +import { useNotification } from '@/context/NotificationContext' +import React, { useState, useRef, useEffect, useMemo } from 'react' +import fetchRequest from '../lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import CancelIcon from "@/static/image/svg/cancel.svg" +import { isArray } from '../lib/TypesHelper' +const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePrivileges, name, id }) => { + const { toggleNotification } = useNotification() + const [loadingStatus, setLoadingStatus] = useState(false) + const [roleName, setRoleName] = useState(name) + const [privileges, setPrivileges] = useState(null) + const [selectedPrivileges, setSelectedPrivileges] = useState(isArray(rolePrivileges) ? rolePrivileges : []) + const inputRef = useRef(null) + console.log("les priv de role ", rolePrivileges) + useEffect(() => { + const getPrivileges = async () => { + const { data, errors, isSuccess } = await fetchRequest("/privileges") + if (isSuccess) { + setPrivileges(data) + } else { + setPrivileges([]) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + getPrivileges() + }, []) + const handleSubmit = async (event) => { + event.preventDefault() + setLoadingStatus(true) + const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { + method: "PATCH", + //.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)) + body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.map((element) => element.id) }) + }) + console.log(data) + setLoadingStatus(false) + if (isSuccess) { + setRoles((roles) => roles.map((element) => element.id === id ? data : element)) + toggleNotification({ + visible: true, + message: "Le rôle a été modifé avec succès", + type: "success" + }) + setRoleToUpdate(null) + } else { + if (errors.type === "ValidationError") { + if (errors.detail.name) { + toggleNotification({ + type: "warning", + message: "Le nom de rôle existe déjà", + visible: true, + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de rôle", + type: "warning" + }) + setRoleToUpdate(null) + } + } else if (status === 404) { + toggleNotification({ + visible: true, + message: "Le rôle n'a pas été trouvé", + type: "warning" + }) + setRoleToUpdate(null) + } else if (errors.detail === "Privilege matching query does not exist.") { + toggleNotification({ + visible: true, + message: "Des privilèges que vous avez utilisés ont été supprimés.", + type: "warning" + }) + setRoleToUpdate(null) + } + else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + setRoleToUpdate(null) + } + console.log(errors) + } + } + const handleRoleNameChange = (event) => { + setRoleName(event.target.value) + } + const handlePrivilegeClick = (privilege) => { + if (selectedPrivileges.find((element) => element.id === privilege.id)) { + setSelectedPrivileges(selectedPrivileges.filter((element) => element.id !== privilege.id)) + } else { + setSelectedPrivileges([...selectedPrivileges, privilege]) + } + } + const selectAll = () => { + if (privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id))) + setSelectedPrivileges([]) + else setSelectedPrivileges(privileges) + } + var isAllSelected = useMemo(() => privileges ? privileges.every((element) => selectedPrivileges.find((priv) => priv.id === element.id)) : false, [selectedPrivileges, privileges]) + return ( +
+
+ setRoleToUpdate(null)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> +
+

Modification de Rôle

+
+ + +
+ {(privileges) ?
+
+ +
{!isAllSelected ? "Sélectionner tout" : "désélectionner"}
+
+
+ {privileges.length !== 0 ? privileges?.map((privilege) => { + const isSelected = selectedPrivileges.find((element) => element.id === privilege.id) !== undefined + return
handlePrivilegeClick(privilege)} key={privilege.id} className={`${!isSelected ? 'text-neutral-400 hover:border-neutral-400 hover:text-neutral-500 border-neutral-300 bg-neutral-100' : 'text-indigo-500 hover:border-indigo-500 hover:text-indigo-500 border-indigo-400 bg-indigo-100'} will-change-contents h-6 text-sm flex items-center justify-center duration-150 delay-75 cursor-pointer text-semibold leading-[0] border-2 py-0.5 rounded-full w-fit px-2`}>{privilege.name}
+ }) :
+

Pas encore des habilitations

+
} +
+
:
} +
+ +
+
+
+
+ ) +} + +export default UpdateRoleForm \ No newline at end of file diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index 78c99ed..a2c5f38 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -5,10 +5,13 @@ import Loader from '@/components/Loader/Loader' import RoleTableRows from './RoleTableRows' import fetchRequest from '../lib/fetchRequest' import { isArray } from '../lib/TypesHelper' - +import AddIcon from "@/static/image/svg/add.svg" +import UpdateRoleForm from './UpdateRoleForm' const Role = () => { const [roles, setRoles] = useState([]) const [isLoading, setIsLoading] = useState(true) + const [openCreatePopup, setOpenCreatePopup] = useState(null) + const [roleToUpdate, setRoleToUpdate] = useState(null) useEffect(() => { const getRoles = async () => { const { data, errors, isSuccess } = await fetchRequest("/roles") @@ -34,21 +37,28 @@ const Role = () => {
- -

List des Roles

+ {openCreatePopup && } + {roleToUpdate && } +
+

List des Roles

+ +
{isLoading &&
} {!isLoading && <> {(!isArray(roles) || roles?.length === 0) ?

Pas encore des roles

- :
+ :
- + - {roles.map((element) => { - return + {roles?.map((element) => { + return })}
Rôlerôle Action
diff --git a/src/static/image/svg/add.svg b/src/static/image/svg/add.svg new file mode 100644 index 0000000..37eaf78 --- /dev/null +++ b/src/static/image/svg/add.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From aac4d4d1b3cd89b7cf87b6a1e34dd8fe302057dd Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 27 May 2024 09:13:28 +0100 Subject: [PATCH 2/4] fix: create role with not found privilege --- src/app/role/UpdateRoleForm.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/role/UpdateRoleForm.jsx b/src/app/role/UpdateRoleForm.jsx index 7bf368b..e980dbf 100644 --- a/src/app/role/UpdateRoleForm.jsx +++ b/src/app/role/UpdateRoleForm.jsx @@ -34,8 +34,7 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv setLoadingStatus(true) const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { method: "PATCH", - //.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)) - body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.map((element) => element.id) }) + body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)).map((element) => element.id) }) }) console.log(data) setLoadingStatus(false) -- GitLab From 499c3bbcb1db5810753dee5c4c50a01087041a23 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 27 May 2024 09:41:56 +0100 Subject: [PATCH 3/4] fix: display privileges for each role --- src/app/role/RoleTableRows.jsx | 14 +++++++++----- src/app/role/page.jsx | 5 +++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index 43444b5..7acdf0b 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -8,10 +8,7 @@ import ConfirmationModal from '../ui/ConfirmationModal' const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { const { toggleNotification } = useNotification() const [isModalOpen, setModalOpen] = useState(false); - const inputRef = useRef(null) - useEffect(() => { - inputRef.current.value = name - }, [name]) + const showDeletePopup = () => { setModalOpen(true); } @@ -53,7 +50,14 @@ const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => { <> - +

{name}

+ + +
+ {privileges?.map((element, index) => { + return
{element.name}
+ })} +
diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index a2c5f38..67c4bc1 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -39,7 +39,7 @@ const Role = () => {
{openCreatePopup && } {roleToUpdate && } -
+

List des Roles

+
+ ) +} + +export default UserPage \ No newline at end of file -- GitLab