From 2aa245e61e05e5b7044e17e151887a3e9d780291 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Fri, 24 May 2024 17:33:13 +0100 Subject: [PATCH 01/10] 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 b0ae3539c76e8af192aa53dd1e6a7f05bb271f6c Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Fri, 24 May 2024 20:43:07 +0100 Subject: [PATCH 02/10] started adding planing added interface for adding semaines jours type de presence --- src/app/lib/fetchRequest.js | 14 +--- src/app/planning/layout.jsx | 23 ++++++ src/app/planning/page.jsx | 1 + .../EntityForm.jsx | 59 ++++++++++++++ .../EntityList.jsx | 41 ++++++++++ .../type-presence-semaines-jours/page.jsx | 77 +++++++++++++++++++ src/app/projects/page.jsx | 2 +- src/app/{projects => ui}/SideBar.jsx | 0 8 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 src/app/planning/layout.jsx create mode 100644 src/app/planning/page.jsx create mode 100644 src/app/planning/type-presence-semaines-jours/EntityForm.jsx create mode 100644 src/app/planning/type-presence-semaines-jours/EntityList.jsx create mode 100644 src/app/planning/type-presence-semaines-jours/page.jsx rename src/app/{projects => ui}/SideBar.jsx (100%) diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js index a47d118..71714c0 100644 --- a/src/app/lib/fetchRequest.js +++ b/src/app/lib/fetchRequest.js @@ -28,10 +28,11 @@ const fetchRequest = async (url, options = {}) => { } } console.log('response', response) + let data = null; // Check if the response has content before parsing it as JSON const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { - const data = await response.json(); + data = await response.json(); return {isSuccess: true, errors: null, data: data, status: response.status}; } // If no JSON content, return null for data @@ -39,14 +40,3 @@ const fetchRequest = async (url, options = {}) => { }; export default fetchRequest; - - - - - - - - - - - diff --git a/src/app/planning/layout.jsx b/src/app/planning/layout.jsx new file mode 100644 index 0000000..6386ecb --- /dev/null +++ b/src/app/planning/layout.jsx @@ -0,0 +1,23 @@ +"use client"; +import SideBar from "@/app/ui/SideBar"; +import ProjectForm from "@/app/projects/ProjectForm"; +import ProjectList from "@/app/projects/ProjectList"; + +// layout for the planning page +export default function PlanningLayout ({ children }) { + return ( +
+
+ +
+ +
+
+ {children} +
+
+
+ ); + +} + diff --git a/src/app/planning/page.jsx b/src/app/planning/page.jsx new file mode 100644 index 0000000..2d4f31d --- /dev/null +++ b/src/app/planning/page.jsx @@ -0,0 +1 @@ +"use client"; \ No newline at end of file diff --git a/src/app/planning/type-presence-semaines-jours/EntityForm.jsx b/src/app/planning/type-presence-semaines-jours/EntityForm.jsx new file mode 100644 index 0000000..04f1769 --- /dev/null +++ b/src/app/planning/type-presence-semaines-jours/EntityForm.jsx @@ -0,0 +1,59 @@ +import { useState, useEffect } from 'react'; +import fetchRequest from "@/app/lib/fetchRequest"; + +const EntityForm = ({ entity, id }) => { + const [nom, setNom] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (id) { + const fetchData = async () => { + const response = await fetchRequest(`/${entity}/${id}/`); + if (response.isSuccess) setNom(response.data.nom); + }; + + fetchData(); + } + }, [entity, id]); + + const handleSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + + const method = id ? 'PUT' : 'POST'; + const url = id ? `/${entity}/${id}/` : `/${entity}/`; + const response = await fetchRequest(url, { + method, + body: JSON.stringify({ nom }), + }); + + setIsLoading(false); + if (response.isSuccess) { + // router.push('/manage'); + } + }; + + return ( +
+
+ + setNom(e.target.value)} + className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md" + required + /> +
+ +
+ ); +}; + +export default EntityForm; diff --git a/src/app/planning/type-presence-semaines-jours/EntityList.jsx b/src/app/planning/type-presence-semaines-jours/EntityList.jsx new file mode 100644 index 0000000..c9f904b --- /dev/null +++ b/src/app/planning/type-presence-semaines-jours/EntityList.jsx @@ -0,0 +1,41 @@ +import Link from "next/link"; + +const EntityList = ({ title, items, setState, handleDelete }) => { + return ( +
+ {/*

{title}

*/} +
+
+ + + + + + + + + {items.map(item => ( + + + + + ))} + +
NomActions
{item.nom} + + +
+
+ ); +}; + +export default EntityList; \ No newline at end of file diff --git a/src/app/planning/type-presence-semaines-jours/page.jsx b/src/app/planning/type-presence-semaines-jours/page.jsx new file mode 100644 index 0000000..17637e0 --- /dev/null +++ b/src/app/planning/type-presence-semaines-jours/page.jsx @@ -0,0 +1,77 @@ +"use client"; + +import {useEffect, useState} from 'react'; +import Link from 'next/link'; +import fetchRequest from "@/app/lib/fetchRequest"; +import EntityList from "@/app/planning/type-presence-semaines-jours/EntityList"; +import EntityForm from "@/app/planning/type-presence-semaines-jours/EntityForm"; + +const ManagePage = () => { + const [typePresences, setTypePresences] = useState([]); + const [semaines, setSemaines] = useState([]); + const [jours, setJours] = useState([]); + + + const fetchData = async () => { + const typePresencesResponse = await fetchRequest('/type-presences/'); + const semainesResponse = await fetchRequest('/semaines/'); + const joursResponse = await fetchRequest('/jours/'); + + if (typePresencesResponse.isSuccess) setTypePresences(typePresencesResponse.data); + if (semainesResponse.isSuccess) setSemaines(semainesResponse.data); + if (joursResponse.isSuccess) setJours(joursResponse.data); + }; + useEffect(() => { + + + fetchData(); + }, []); + + const handleDelete = async (endpoint, id, setState, currentState) => { + const response = await fetchRequest(`/${endpoint}/${id}/`, {method: 'DELETE'}); + if (response.isSuccess) { + setState(currentState.filter(item => item.id !== id)); + } + }; + + return ( + <> +

Manage Entities

+
+
+

Type Presence

+ + +
+
+

Semaines

+ + +
+
+

Jours

+ + +
+
+

Todo: edit each entity + design + compt rendu : base cco ro

+ + ); +}; + +export default ManagePage; diff --git a/src/app/projects/page.jsx b/src/app/projects/page.jsx index adba7fe..c1c93b5 100644 --- a/src/app/projects/page.jsx +++ b/src/app/projects/page.jsx @@ -1,6 +1,6 @@ 'use client'; -import SideBar from "@/app/projects/SideBar"; +import SideBar from "@/app/ui/SideBar"; import {useEffect, useState} from 'react'; import ProjectForm from "@/app/projects/ProjectForm"; import ProjectList from "@/app/projects/ProjectList"; diff --git a/src/app/projects/SideBar.jsx b/src/app/ui/SideBar.jsx similarity index 100% rename from src/app/projects/SideBar.jsx rename to src/app/ui/SideBar.jsx -- GitLab From 0d925863933e4ceb412b66b553f80c42bb587681 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Fri, 24 May 2024 20:44:28 +0100 Subject: [PATCH 03/10] started adding planing added interface for adding semaines jours type de presence --- src/app/planning/type-presence-semaines-jours/page.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/planning/type-presence-semaines-jours/page.jsx b/src/app/planning/type-presence-semaines-jours/page.jsx index 17637e0..645f4d6 100644 --- a/src/app/planning/type-presence-semaines-jours/page.jsx +++ b/src/app/planning/type-presence-semaines-jours/page.jsx @@ -69,7 +69,7 @@ const ManagePage = () => { />
-

Todo: edit each entity + design + compt rendu : base cco ro

+ {/*

Todo: edit each entity + design + compt rendu : base cco ro

*/} ); }; -- GitLab From aac4d4d1b3cd89b7cf87b6a1e34dd8fe302057dd Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 27 May 2024 09:13:28 +0100 Subject: [PATCH 04/10] 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 05/10] 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 Place \ No newline at end of file diff --git a/src/app/table/CreateNewTable.jsx b/src/app/table/CreateNewTable.jsx new file mode 100644 index 0000000..0e8478d --- /dev/null +++ b/src/app/table/CreateNewTable.jsx @@ -0,0 +1,106 @@ +"use client" +import React, { useState, useEffect, useRef } from 'react' +import fetchRequest from '../lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import { Island_Moments } from 'next/font/google' +import { useNotification } from '@/context/NotificationContext' + + +const CreateNewTable = ({ tablesState, zones }) => { + const [error, setError] = useState(null) + const [isLoadingAction, setIsLoadingAction] = useState(false) + const [numeroTable, setNumeroTable] = useState(null) + const [selectedZone, setSelectedZone] = useState(null) + + const inputRef = useRef(null) + const selectRef = useRef(null) + + const { toggleNotification } = useNotification() + + + const handleSubmit = async (event) => { + event.preventDefault() + setIsLoadingAction(true) + const { data, errors, isSuccess } = await fetchRequest("/zoaning/tables/", { + method: "POST", + body: JSON.stringify({ numero: numeroTable, id_zone: selectedZone }) + }) + if (isSuccess) { + setIsLoadingAction(false) + tablesState((prevTableState) => [...prevTableState, {...data, id_zone: zones.find(zone => zone.id === data.id_zone)}]); + inputRef.current.value = "" + selectRef.current.value = "" + setNumeroTable(null) + setSelectedZone(null) + toggleNotification({ + visible: true, + message: "La table a été créer avec succès.", + type: "success" + }) + } else { + setIsLoadingAction(false) + if (errors.type === "ValidationError") { + if (errors.detail.non_field_errors) { + toggleNotification({ + type: "warning", + message: "Le numéro de la table saisie déjà existe.", + visible: true, + }) + } + }else{ + toggleNotification({ + type: "error", + message: "Une erreur s'est produite lors de la création de la table.", + visible: true, + }) + } + console.log(errors) + } + } + + // Handle the name of zone change + const handleChangeTable = (event) => { + setError("") + setNumeroTable(event.target.value) + } + + const handleChangeZone = (event) => { + setError("") + setSelectedZone(event.target.value) + } + + + + + return( +
+

Ajout d'une table

+
+
+ + +
+
+ + +
+
+

+
+ +
+
+ ) +} + + +export default CreateNewTable \ No newline at end of file diff --git a/src/app/table/RowTable.jsx b/src/app/table/RowTable.jsx new file mode 100644 index 0000000..939d879 --- /dev/null +++ b/src/app/table/RowTable.jsx @@ -0,0 +1,225 @@ +"use client" +import React, { useState, useEffect, useRef } 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' +import ConfirmationModal from "@/app/ui/ConfirmationModal"; + + + + +const RowZone = ({ id, numero, zone, tablesState, zones }) => { + + //states + const [isUpdating, setIsUpdating] = useState(false) + const [tableNum, setTableNum] = useState(numero) + const [selectedZone, setSelectedZone] = useState(zone) + const [loadingStatus, setLoadingStatus] = useState(false) + const [isModalOpen, setModalOpen] = useState(false); + const { toggleNotification } = useNotification() + //refs + const inputRef = useRef(null) + const selectRef = useRef(null) + const rowRef = useRef(null) + + //Logic + useEffect(() => { + setTableNum(numero) + setSelectedZone(zone?.id) + selectRef.current.value = zone?.id + inputRef.current.value = numero + }, [numero, zone]) + + const handleUpdateZone = async () => { + setLoadingStatus(true) + const { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/tables/${id}/`, { + method: "PATCH", + body: JSON.stringify({ numero: tableNum, id_zone: selectedZone }) + }) + setLoadingStatus(false) + if (isSuccess) { + if(data.message === "NO_CHANGES"){ + toggleNotification({ + visible: true, + message: "Aucun changement n'a été effectué.", + type: "warning" + }) + setIsUpdating(false) + return + } + tablesState((prevTableState) => prevTableState.map( (element) => element.id === id ? {...data.data, id_zone: zones.find(zone => zone.id === data.data.id_zone)} : element )) + setIsUpdating(false) + toggleNotification({ + visible: true, + message: "La table a été modifiée avec succès.", + type: "success" + }) + } else { + if (errors.type === "ValidationError") { + if (errors.detail.name) { + toggleNotification({ + type: "warning", + message: "Le numero de la table existe déjà", + visible: true, + }) + }else if (errors.detail.non_field_errors) { + toggleNotification({ + type: "warning", + message: "Le numero de la table saisie déjà existe.", + visible: true, + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de la table", + type: "warning" + }) + } + } else if (status === 404) { + toggleNotification({ + visible: true, + message: "La table n'a pas été trouvé", + type: "warning" + }) + } else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + console.log(errors) + } + + } + + const handleDelete = async () => { + const { isSuccess, errors, status } = await fetchRequest(`/zoaning/tables/${id}/`, { method: "DELETE" }) + if (isSuccess) { + tablesState((prevTableState) => prevTableState.filter((element) => element.id !== id)) + toggleNotification({ + visible: true, + message: "La table a été supprimée avec succès", + type: "success" + }) + } else if (status == 404) { + toggleNotification({ + visible: true, + message: "La table n'a pas été trouvé", + type: "warning" + }) + } else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + + const cancelUpdate = () => { + setIsUpdating(false) + setTableNum(numero) + setSelectedZone(zone.id) + selectRef.current.value = zone.id + inputRef.current.value = numero + } + + 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("tableRowSVG")) return; + if (!isInsideRowRef) { + cancelUpdate(); + document.removeEventListener("click", handleUpdateBlur); + } + } + + useEffect(() => { + if (isUpdating && inputRef?.current && selectRef?.current) { + inputRef.current.focus() + selectRef.current.focus() + document.addEventListener("click", handleUpdateBlur) + } + return () => { + document.removeEventListener("click", handleUpdateBlur) + } + }, [isUpdating]) + + const handleDeleteClick = () => { + setModalOpen(true); + } + + const handleConfirmDelete = () => { + handleDelete(); + setModalOpen(false); + }; + + return( + + + setTableNum(event.target.value)} defaultValue={numero} type='text' className='disabled:bg-white 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={handleConfirmDelete} + message={`Êtes-vous sûr de vouloir supprimer la table numero "${numero}"?`} + /> + + ) +} + + +export default RowZone \ No newline at end of file diff --git a/src/app/table/page.jsx b/src/app/table/page.jsx new file mode 100644 index 0000000..23ad849 --- /dev/null +++ b/src/app/table/page.jsx @@ -0,0 +1,109 @@ +"use client" +import React from 'react' +import fetchRequest from '../lib/fetchRequest' +import { useState, useEffect } from 'react'; +import Loader from '@/components/Loader/Loader' +import CreateNewTable from './CreateNewTable' +import TableIcon from "@/static/image/svg/table.svg" +import { isArray } from '../lib/TypesHelper' +import RowTable from './RowTable' + + +const Table = ()=> { + const [ tables, setTables ] = useState([]) + const [ isLoadingData, setIsLoadingData ] = useState(true) + const [ zones, setZones ] = useState([]) + + + // Fetch data from external API + useEffect(() => { + const getAllTables = async () => { + try{ + const {isSuccess, errors, data} = await fetchRequest('/zoaning/tables/', {method: 'GET'}) + if(isSuccess){ + setTables(data) + }else{ + setTables([]) + } + }catch(error){ + console.log(error) + } + } + const getAllZones = async () => { + try{ + const {isSuccess, errors, data} = await fetchRequest('/zoaning/zones/', {method: 'GET'}) + if(isSuccess){ + setZones(data) + }else{ + setZones([]) + } + }catch(error){ + console.log(error) + } + } + getAllTables() + getAllZones() + setIsLoadingData(false) + }, []) + + + const handleSearchingTable = async (e) => { + const numero= e.target.value + try{ + const {isSuccess, errors, data} = await fetchRequest(`/zoaning/search/table/${numero}`, {method: 'GET'}) + console.log(data) + if(isSuccess){ + setTables(data) + }else{ + setTables([]) + } + }catch(error){ + console.log(error) + } + } + return( +
+
+
+ {!isLoadingData ? + <> + +
+

List des Tables

+
+
+ +
+ {handleSearchingTable(e)}} id="simple-search" class=" text-gray-900 text-sm block w-full ps-10 p-2.5 rounded-md px-3 duration-150 delay-75 focus:ring ring-offset-1 ring-sushi-200 border h-10 border-neutral-300 outline-none " placeholder="Chercher des tables..." required /> +
+
+ + {isArray(tables) && tables?.length !== 0 && isArray(zones) && zones?.length !== 0 ? +
+ + + + + + + {tables?.map((element) => { + return + })} +
TableZone-EtageAction
+
+ : +
+

Pas encore des tables

+
} + + : +
+ } +
+
+
+ + ) +} + +export default Table \ No newline at end of file diff --git a/src/app/zone/CreateNewZone.jsx b/src/app/zone/CreateNewZone.jsx index d02c3f5..76b78c6 100644 --- a/src/app/zone/CreateNewZone.jsx +++ b/src/app/zone/CreateNewZone.jsx @@ -43,7 +43,7 @@ const CreateNewZone = ({ zoneState, etages }) => { if (errors.detail.non_field_errors) { toggleNotification({ type: "warning", - message: "Le nom de la zone saisie existe déjà dans l'étage sélectionné.", + message: "Le nom de la zone saisie déjà existe.", visible: true, }) } diff --git a/src/app/zone/RowZone.jsx b/src/app/zone/RowZone.jsx index 5ef8172..baebd3a 100644 --- a/src/app/zone/RowZone.jsx +++ b/src/app/zone/RowZone.jsx @@ -41,6 +41,16 @@ const RowZone = ({ id, nom, etage, zonesState, etages }) => { }) setLoadingStatus(false) if (isSuccess) { + console.log(data) + if(data.message === "NO_CHANGES"){ + toggleNotification({ + visible: true, + message: "Aucun changement n'a été effectué.", + type: "warning" + }) + setIsUpdating(false) + return + } console.log(data.data) zonesState((prevZonesValue) => prevZonesValue.map( (element) => element.id === id ? {...data.data, id_etage: etages.find(etage => etage.id === data.data.id_etage)} : element )) setIsUpdating(false) @@ -51,7 +61,7 @@ const RowZone = ({ id, nom, etage, zonesState, etages }) => { }) } else { if (errors.type === "ValidationError") { - if (errors.detail.name) { + if (errors.detail.nom) { toggleNotification({ type: "warning", message: "Le nom de la zone existe déjà", @@ -60,7 +70,7 @@ const RowZone = ({ id, nom, etage, zonesState, etages }) => { }else if (errors.detail.non_field_errors) { toggleNotification({ type: "warning", - message: "Le nom de la zone saisie existe déjà dans l'étage sélectionné.", + message: "Le nom de la zone saisie déjà existe.", visible: true, }) } diff --git a/src/app/zone/page.jsx b/src/app/zone/page.jsx index 5fc1e53..1e3c032 100644 --- a/src/app/zone/page.jsx +++ b/src/app/zone/page.jsx @@ -3,12 +3,12 @@ import React from 'react' import fetchRequest from '../lib/fetchRequest' import { useState, useEffect } from 'react'; import Loader from '@/components/Loader/Loader' -import CreateNewZone from './CreateNewZone' 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 { isArray } from '../lib/TypesHelper' +import CreateNewZone from './CreateNewZone' import RowZone from './RowZone' @@ -73,7 +73,7 @@ const Zone = ()=> {
:
-

Pas encore des habilitations

+

Pas encore des zones

} : diff --git a/src/static/image/svg/place.svg b/src/static/image/svg/place.svg new file mode 100644 index 0000000..cd980fb --- /dev/null +++ b/src/static/image/svg/place.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/static/image/svg/table.svg b/src/static/image/svg/table.svg new file mode 100644 index 0000000..0d56955 --- /dev/null +++ b/src/static/image/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file -- GitLab From a00b8c1d1e707099a37ebfc19505d7d807f993cd Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Tue, 28 May 2024 09:19:28 +0100 Subject: [PATCH 07/10] added planning interfaces --- .idea/misc.xml | 1 - src/app/planning/PlanningTable.jsx | 32 +++++++++ src/app/planning/page.jsx | 58 ++++++++++++++++- .../EntityForm.jsx | 49 +++++++++----- .../EntityList.jsx | 36 +++++++--- .../type-presence-semaines-jours/page.jsx | 65 +++++++++++++------ src/app/projects/ProjectForm.jsx | 26 +++++--- src/app/projects/page.jsx | 23 +++++++ src/app/ui/Dropdown.js | 20 ++++++ src/app/ui/LogoutButton.js | 6 +- 10 files changed, 260 insertions(+), 56 deletions(-) create mode 100644 src/app/planning/PlanningTable.jsx create mode 100644 src/app/ui/Dropdown.js diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d..6e86672 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/src/app/planning/PlanningTable.jsx b/src/app/planning/PlanningTable.jsx new file mode 100644 index 0000000..40e3b7a --- /dev/null +++ b/src/app/planning/PlanningTable.jsx @@ -0,0 +1,32 @@ +import React from 'react'; + +const PlanningTable = ({ data }) => { + return ( +
+ + + + + + + + + + + + + {data.map((row, rowIndex) => ( + + + {row.days.map((day, dayIndex) => ( + + ))} + + ))} + +
Semaine / JourJ1J2J3J4J5
{row.week}{day}
+
+ ); +}; + +export default PlanningTable; diff --git a/src/app/planning/page.jsx b/src/app/planning/page.jsx index 2d4f31d..3467ce4 100644 --- a/src/app/planning/page.jsx +++ b/src/app/planning/page.jsx @@ -1 +1,57 @@ -"use client"; \ No newline at end of file +'use client'; +import { useState } from 'react'; +import Dropdown from "@/app/ui/Dropdown"; +import PlanningTable from "@/app/planning/PlanningTable"; + +const PlanningPage = () => { + const [selectedProject, setSelectedProject] = useState(''); + const [selectedWeek, setSelectedWeek] = useState(''); + const [selectedDay, setSelectedDay] = useState(''); + const [planningData, setPlanningData] = useState([ + { week: 'Semaine 1', days: ['', '', '', '', ''] }, + { week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel'] }, + { week: 'Semaine 3', days: ['', '', '', '', ''] }, + { week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', ''] }, + ]); + + const handleProjectChange = (e) => setSelectedProject(e.target.value); + const handleWeekChange = (e) => setSelectedWeek(e.target.value); + const handleDayChange = (e) => setSelectedDay(e.target.value); + + const fetchPlanningData = async () => { + // Fetch planning data from your API based on selectedProject, selectedWeek, and selectedDay + // Here we'll use dummy data for demonstration purposes + const data = [ + { week: 'Semaine 1', days: ['', '', '', '', ''] }, + { week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel'] }, + { week: 'Semaine 3', days: ['', '', '', '', ''] }, + { week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', ''] }, + ]; + setPlanningData(data); + }; + + return ( +
+

Planning

+
+ +
+ +
+ +
+
+ ); +}; + +export default PlanningPage; diff --git a/src/app/planning/type-presence-semaines-jours/EntityForm.jsx b/src/app/planning/type-presence-semaines-jours/EntityForm.jsx index 04f1769..83f4dfc 100644 --- a/src/app/planning/type-presence-semaines-jours/EntityForm.jsx +++ b/src/app/planning/type-presence-semaines-jours/EntityForm.jsx @@ -1,18 +1,21 @@ import { useState, useEffect } from 'react'; import fetchRequest from "@/app/lib/fetchRequest"; +import {useNotification} from "@/context/NotificationContext"; -const EntityForm = ({ entity, id }) => { +const EntityForm = ({ entity, id, onSaved, onCancel }) => { const [nom, setNom] = useState(''); const [isLoading, setIsLoading] = useState(false); + const { toggleNotification } = useNotification() + const fetchData = async () => { + const response = await fetchRequest(`/${entity}/${id}/`); + if (response.isSuccess) setNom(response.data.nom); + }; useEffect(() => { if (id) { - const fetchData = async () => { - const response = await fetchRequest(`/${entity}/${id}/`); - if (response.isSuccess) setNom(response.data.nom); - }; - fetchData(); + } else { + setNom(''); } }, [entity, id]); @@ -29,12 +32,19 @@ const EntityForm = ({ entity, id }) => { setIsLoading(false); if (response.isSuccess) { - // router.push('/manage'); + toggleNotification({ + visible: true, + message: `${nom} a été ${id ? 'modifié' : 'créé'} avec succès`, + type: "success" + }) + setNom(''); + onSaved(); + onCancel(); } }; return ( -
+
{ required />
- +
+ + +
); }; diff --git a/src/app/planning/type-presence-semaines-jours/EntityList.jsx b/src/app/planning/type-presence-semaines-jours/EntityList.jsx index c9f904b..a829627 100644 --- a/src/app/planning/type-presence-semaines-jours/EntityList.jsx +++ b/src/app/planning/type-presence-semaines-jours/EntityList.jsx @@ -1,11 +1,23 @@ -import Link from "next/link"; +import React, { useState } from 'react'; +import ConfirmationModal from '@/app/ui/ConfirmationModal'; + +const EntityList = ({ title, items, setState, handleDelete, handleEdit }) => { + const [isModalOpen, setModalOpen] = useState(false); + const [entityToDelete, setEntityToDelete] = useState(null); + + const handleDeleteClick = (entity) => { + setEntityToDelete(entity); + setModalOpen(true); + }; + + const handleConfirmDelete = () => { + handleDelete(title.toLowerCase(), entityToDelete.id, setState, items); + setModalOpen(false); + setEntityToDelete(null); + }; -const EntityList = ({ title, items, setState, handleDelete }) => { return (
- {/*

{title}

*/} -
-
@@ -19,12 +31,13 @@ const EntityList = ({ title, items, setState, handleDelete }) => {
{item.nom}
+ + setModalOpen(false)} + onConfirm={handleConfirmDelete} + message={`Are you sure you want to delete this ${title.toLowerCase()} "${entityToDelete?.nom}"?`} + />
); }; -export default EntityList; \ No newline at end of file +export default EntityList; diff --git a/src/app/planning/type-presence-semaines-jours/page.jsx b/src/app/planning/type-presence-semaines-jours/page.jsx index 645f4d6..f6d566c 100644 --- a/src/app/planning/type-presence-semaines-jours/page.jsx +++ b/src/app/planning/type-presence-semaines-jours/page.jsx @@ -1,16 +1,17 @@ "use client"; import {useEffect, useState} from 'react'; -import Link from 'next/link'; import fetchRequest from "@/app/lib/fetchRequest"; import EntityList from "@/app/planning/type-presence-semaines-jours/EntityList"; import EntityForm from "@/app/planning/type-presence-semaines-jours/EntityForm"; +import {useNotification} from "@/context/NotificationContext"; const ManagePage = () => { const [typePresences, setTypePresences] = useState([]); const [semaines, setSemaines] = useState([]); const [jours, setJours] = useState([]); - + const [editingEntity, setEditingEntity] = useState({ entity: null, id: null }); + const { toggleNotification } = useNotification() const fetchData = async () => { const typePresencesResponse = await fetchRequest('/type-presences/'); @@ -21,9 +22,8 @@ const ManagePage = () => { if (semainesResponse.isSuccess) setSemaines(semainesResponse.data); if (joursResponse.isSuccess) setJours(joursResponse.data); }; - useEffect(() => { - + useEffect(() => { fetchData(); }, []); @@ -31,45 +31,68 @@ const ManagePage = () => { const response = await fetchRequest(`/${endpoint}/${id}/`, {method: 'DELETE'}); if (response.isSuccess) { setState(currentState.filter(item => item.id !== id)); + toggleNotification({ + visible: true, + message: `${currentState.find(item => item.id === id).nom} a été supprimé avec succès`, + type: "success" + }) } + await fetchData(); }; return ( <>

Manage Entities

-
+

Type Presence

- + setEditingEntity({ entity: null, id: null })} + /> setEditingEntity({ entity: 'type-presences', id })} />

Semaines

- - + setEditingEntity({ entity: null, id: null })} + /> + setEditingEntity({ entity: 'semaines', id })} + />

Jours

- - + setEditingEntity({ entity: null, id: null })} + /> + setEditingEntity({ entity: 'jours', id })} + />
- {/*

Todo: edit each entity + design + compt rendu : base cco ro

*/} ); }; diff --git a/src/app/projects/ProjectForm.jsx b/src/app/projects/ProjectForm.jsx index 350d3d0..0c4f1d1 100644 --- a/src/app/projects/ProjectForm.jsx +++ b/src/app/projects/ProjectForm.jsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import fetchRequest from '@/app/lib/fetchRequest'; -const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { +const ProjectForm = ({ onAddProject, onEditProject, editingProject, setEditingProject }) => { const [projectName, setProjectName] = useState(''); const [clientName, setClientName] = useState(''); const [dateDebut, setDateDebut] = useState(''); @@ -66,15 +66,15 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => { e.preventDefault(); if (!validateForm()) return; - const project = { nom: projectName, nomClient: clientName, dateDebut, dateFin, - users: userIds.map(user => user.id), + user_ids: userIds.map(user => user.id), }; - + console.log(project) + console.log(project.users) if (editingProject) { onEditProject(editingProject.id, project); } else { @@ -148,11 +148,11 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => {
  • !isUserChosen(user) && handleUserSelect(user)} - className={`px-3 py-2 border-b border-chicago-200 cursor-pointer ${isUserChosen(user) ? 'bg-gray-200 cursor-not-allowed' : 'hover:bg-sushi-200'}`} + className={`px-3 py-2 border-b border-chicago-200 ${isUserChosen(user) ? 'bg-gray-200 cursor-not-allowed' : 'hover:bg-sushi-200 cursor-pointer'}`} > {/*add tick is chosen*/} {isUserChosen(user) && '✔ '} - {user.username} ({user.email}) {isUserChosen(user) && '(selectionné)'} + {user.first_name} {user.last_name} ({user.role.name}) {user.email}
  • ))} @@ -162,7 +162,7 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => {
    @@ -178,9 +178,19 @@ const ProjectForm = ({ onAddProject, onEditProject, editingProject }) => {
    {errors.userIds &&

    {errors.userIds}

    }
    - + {/* cancel*/} + {editingProject && ( + + )} ); }; diff --git a/src/app/projects/page.jsx b/src/app/projects/page.jsx index c1c93b5..98cf397 100644 --- a/src/app/projects/page.jsx +++ b/src/app/projects/page.jsx @@ -5,6 +5,7 @@ import {useEffect, useState} from 'react'; import ProjectForm from "@/app/projects/ProjectForm"; import ProjectList from "@/app/projects/ProjectList"; import fetchRequest from "@/app/lib/fetchRequest"; +import {useNotification} from "@/context/NotificationContext"; const Projects = () => { const [pageUrl, setPageUrl] = useState('/projects/'); @@ -13,6 +14,8 @@ const Projects = () => { {/* errors from request*/} const [errors, setErrors] = useState(); const [loading, setLoading] = useState(false); + const { toggleNotification } = useNotification() + const fetchProjects = async () => { const { isSuccess, errors , data } = await fetchRequest(pageUrl); if (isSuccess) { @@ -31,6 +34,9 @@ const Projects = () => { // pageURL const handleAddProject = async (project) => { + console.log("proj",project) + console.log("proj strinjify",JSON.stringify(project)) + setLoading(true); const { isSuccess, errors , data } = await fetchRequest('/projects/', { method: 'POST', @@ -38,6 +44,11 @@ const Projects = () => { }); if (isSuccess) { + toggleNotification({ + visible: true, + message: `${project.nom} a été ajouté avec succès`, + type: "success" + }) setProjects([...projects, data]); setEditingProject(null); setErrors(null); @@ -56,6 +67,11 @@ const Projects = () => { }); if (isSuccess) { + toggleNotification({ + visible: true, + message: `${updatedProject.nom} a été modifié avec succès`, + type: "success" + }) const updatedProjects = projects.map((project) => project.id === id ? data : project ); @@ -70,6 +86,7 @@ const Projects = () => { }; const handleEditClick = (project) => { + console.log(project) setEditingProject(project); }; @@ -80,6 +97,11 @@ const Projects = () => { }); if (isSuccess) { + toggleNotification({ + visible: true, + message: `${project.nom} a été supprimé avec succès`, + type: "success" + }) // setProjects(projects.filter((p) => p.id !== project.id)); await fetchProjects(); setEditingProject(null); @@ -105,6 +127,7 @@ const Projects = () => { onAddProject={handleAddProject} onEditProject={handleEditProject} editingProject={editingProject} + setEditingProject={setEditingProject} /> {/*errors from request*/} {errors && errors.detail && Object.keys(errors.detail).map((key) => ( diff --git a/src/app/ui/Dropdown.js b/src/app/ui/Dropdown.js new file mode 100644 index 0000000..53c7dad --- /dev/null +++ b/src/app/ui/Dropdown.js @@ -0,0 +1,20 @@ +import React from 'react'; + +const Dropdown = ({ label, options, value, onChange }) => { + return ( +
    + + +
    + ); +}; + +export default Dropdown; diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js index b219fc4..3f9f7e0 100644 --- a/src/app/ui/LogoutButton.js +++ b/src/app/ui/LogoutButton.js @@ -6,7 +6,8 @@ import fetchRequest from "@/app/lib/fetchRequest"; const LogoutButton = () => { const logout = async () => { const response = await fetchRequest(`/logout`, { - method: 'GET'}); + method: 'GET' + }); console.log(response); if (response.isSuccess) { console.log('logout successful'); @@ -17,7 +18,8 @@ const LogoutButton = () => { }; return ( - ); -- GitLab From 7620c15a67933199b0bccfefaff6af0681e3eea2 Mon Sep 17 00:00:00 2001 From: Oussama El Benney Date: Tue, 28 May 2024 11:32:24 +0100 Subject: [PATCH 08/10] adding planning --- src/app/planning/PlanningTable.jsx | 46 +++++++++++-- src/app/planning/page.jsx | 69 +++++++++++++++---- .../type-presence-semaines-jours/page.jsx | 66 +++++++++--------- 3 files changed, 127 insertions(+), 54 deletions(-) diff --git a/src/app/planning/PlanningTable.jsx b/src/app/planning/PlanningTable.jsx index 40e3b7a..4ddc99b 100644 --- a/src/app/planning/PlanningTable.jsx +++ b/src/app/planning/PlanningTable.jsx @@ -1,10 +1,34 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; +import fetchRequest from "@/app/lib/fetchRequest"; +import {useNotification} from "@/context/NotificationContext"; + +const PlanningTable = ({ data, onTypePresenceChange }) => { + // fetch type presence + const [typePresences, setTypePresences] = useState([]); + const [errors, setErrors] = useState(); + const [loading, setLoading] = useState(false); + const {toggleNotification} = useNotification() + const [selectedTypePresence, setSelectedTypePresence] = useState(); + + const fetchTypePresences = async () => { + const { isSuccess, errors, data } = await fetchRequest('/type-presences/'); + if (isSuccess) { + setTypePresences(data); + setErrors(null); + } + else { + console.error("Failed to fetch type presences"); + setErrors(errors) + } + } + useEffect(() => { + fetchTypePresences() + }, []); -const PlanningTable = ({ data }) => { return (
    - - +
    + @@ -16,10 +40,20 @@ const PlanningTable = ({ data }) => { {data.map((row, rowIndex) => ( - + {row.days.map((day, dayIndex) => ( - + ))} ))} diff --git a/src/app/planning/page.jsx b/src/app/planning/page.jsx index 3467ce4..42a6408 100644 --- a/src/app/planning/page.jsx +++ b/src/app/planning/page.jsx @@ -1,46 +1,85 @@ 'use client'; -import { useState } from 'react'; +import {useEffect, useState} from 'react'; import Dropdown from "@/app/ui/Dropdown"; import PlanningTable from "@/app/planning/PlanningTable"; +import fetchRequest from "@/app/lib/fetchRequest"; +import {useNotification} from "@/context/NotificationContext"; const PlanningPage = () => { + const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState(''); - const [selectedWeek, setSelectedWeek] = useState(''); - const [selectedDay, setSelectedDay] = useState(''); const [planningData, setPlanningData] = useState([ - { week: 'Semaine 1', days: ['', '', '', '', ''] }, - { week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel'] }, - { week: 'Semaine 3', days: ['', '', '', '', ''] }, - { week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', ''] }, + {week: 'Semaine 1', days: ['', '', '', '', '']}, + {week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel']}, + {week: 'Semaine 3', days: ['', '', '', '', '']}, + {week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', '']}, + {week: 'Semaine 5', days: ['Présentiel', 'Présentiel', '', '', '']}, ]); + const [errors, setErrors] = useState(); + const [loading, setLoading] = useState(false); + const {toggleNotification} = useNotification() + + const fetchProjects = async () => { + const {isSuccess, errors, data} = await fetchRequest('/projects/'); + if (isSuccess) { + setProjects(data); + setErrors(null); + } else { + console.error("Failed to fetch projects"); + setErrors(errors) + } + }; + useEffect(() => { + + fetchProjects(); + }, []); const handleProjectChange = (e) => setSelectedProject(e.target.value); - const handleWeekChange = (e) => setSelectedWeek(e.target.value); - const handleDayChange = (e) => setSelectedDay(e.target.value); const fetchPlanningData = async () => { // Fetch planning data from your API based on selectedProject, selectedWeek, and selectedDay // Here we'll use dummy data for demonstration purposes const data = [ - { week: 'Semaine 1', days: ['', '', '', '', ''] }, - { week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel'] }, - { week: 'Semaine 3', days: ['', '', '', '', ''] }, - { week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', ''] }, + {week: 'Semaine 1', days: ['', '', '', '', '']}, + {week: 'Semaine 2', days: ['Présentiel', '', 'Présentiel', '', 'Présentiel']}, + {week: 'Semaine 3', days: ['', '', '', '', '']}, + {week: 'Semaine 4', days: ['Présentiel', 'Présentiel', '', '', '']}, + {week: 'Semaine 5', days: ['Présentiel', 'Présentiel', '', '', '']}, ]; setPlanningData(data); }; + const handleTypePresenceChange = (weekIndex, dayIndex, value) => { + const updatedData = [...planningData]; + updatedData[weekIndex].days[dayIndex] = value; + setPlanningData(updatedData); + }; + return (

    Planning

    + {/*project using select*/} +
    - +
    ); diff --git a/src/app/planning/type-presence-semaines-jours/page.jsx b/src/app/planning/type-presence-semaines-jours/page.jsx index f6d566c..c29923c 100644 --- a/src/app/planning/type-presence-semaines-jours/page.jsx +++ b/src/app/planning/type-presence-semaines-jours/page.jsx @@ -44,7 +44,7 @@ const ManagePage = () => { <>

    Manage Entities

    -
    +

    Type Presence

    { handleEdit={id => setEditingEntity({ entity: 'type-presences', id })} />
    -
    -

    Semaines

    - setEditingEntity({ entity: null, id: null })} - /> - setEditingEntity({ entity: 'semaines', id })} - /> -
    -
    -

    Jours

    - setEditingEntity({ entity: null, id: null })} - /> - setEditingEntity({ entity: 'jours', id })} - /> -
    + {/*
    */} + {/*

    Semaines

    */} + {/* setEditingEntity({ entity: null, id: null })}*/} + {/* />*/} + {/* setEditingEntity({ entity: 'semaines', id })}*/} + {/* />*/} + {/*
    */} + {/*
    */} + {/*

    Jours

    */} + {/* setEditingEntity({ entity: null, id: null })}*/} + {/* />*/} + {/* setEditingEntity({ entity: 'jours', id })}*/} + {/* />*/} + {/*
    */}
    ); -- GitLab From 33e29d8ab1ab76204c6b7ad1e0c97fa77e21280d Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Tue, 28 May 2024 11:34:06 +0100 Subject: [PATCH 09/10] feature: add & update user --- src/app/role/RoleTableRows.jsx | 4 +- src/app/role/page.jsx | 8 +- src/app/user/CreateUserForm.jsx | 216 ++++++++++++++++++++++++++++++++ src/app/user/UpdateUserForm.jsx | 9 ++ src/app/user/UserTableRow.jsx | 88 +++++++++++++ src/app/user/page.jsx | 77 ++++++++++++ 6 files changed, 397 insertions(+), 5 deletions(-) create mode 100644 src/app/user/CreateUserForm.jsx create mode 100644 src/app/user/UpdateUserForm.jsx create mode 100644 src/app/user/UserTableRow.jsx create mode 100644 src/app/user/page.jsx diff --git a/src/app/role/RoleTableRows.jsx b/src/app/role/RoleTableRows.jsx index 7acdf0b..5c44906 100644 --- a/src/app/role/RoleTableRows.jsx +++ b/src/app/role/RoleTableRows.jsx @@ -62,10 +62,10 @@ const RoleTableRows = ({ name, setRoles, id, privileges, setRoleToUpdate }) => {
    diff --git a/src/app/role/page.jsx b/src/app/role/page.jsx index 67c4bc1..411c32f 100644 --- a/src/app/role/page.jsx +++ b/src/app/role/page.jsx @@ -7,11 +7,13 @@ import fetchRequest from '../lib/fetchRequest' import { isArray } from '../lib/TypesHelper' import AddIcon from "@/static/image/svg/add.svg" import UpdateRoleForm from './UpdateRoleForm' +import { useNotification } from '@/context/NotificationContext' const Role = () => { const [roles, setRoles] = useState([]) const [isLoading, setIsLoading] = useState(true) const [openCreatePopup, setOpenCreatePopup] = useState(null) const [roleToUpdate, setRoleToUpdate] = useState(null) + const { toggleNotification } = useNotification() useEffect(() => { const getRoles = async () => { const { data, errors, isSuccess } = await fetchRequest("/roles") @@ -46,15 +48,15 @@ const Role = () => {

    Rôle

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

    Pas encore des roles

    :
    Semaine / Jour J1
    {row.week}{day} + +
    - + diff --git a/src/app/user/CreateUserForm.jsx b/src/app/user/CreateUserForm.jsx new file mode 100644 index 0000000..f06c5ca --- /dev/null +++ b/src/app/user/CreateUserForm.jsx @@ -0,0 +1,216 @@ +import React, { useState, useEffect } from 'react' +import Loader from '@/components/Loader/Loader' +import fetchRequest from '../lib/fetchRequest' +import { useNotification } from '@/context/NotificationContext' +import CancelIcon from "@/static/image/svg/cancel.svg" + + + + +function generateRandomPassword() { + const length = Math.floor(Math.random() * 3) + 8; // Length between 8 and 10 + const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const lowercase = 'abcdefghijklmnopqrstuvwxyz'; + const numbers = '0123456789'; + const symbols = '!@#$%^&*()_+[]{}|;:,.<>?'; + + let password = ''; + + password += uppercase[Math.floor(Math.random() * uppercase.length)]; + password += numbers[Math.floor(Math.random() * numbers.length)]; + password += symbols[Math.floor(Math.random() * symbols.length)]; + + const allCharacters = uppercase + lowercase + numbers + symbols; + + for (let i = 3; i < length; i++) { + const randomIndex = Math.floor(Math.random() * allCharacters.length); + password += allCharacters[randomIndex]; + } + + password = password.split('').sort(() => 0.5 - Math.random()).join(''); + + return password; +} +const CreateUserForm = ({ setIsOpen, appendUser }) => { + const [isLoading, setIsLoading] = useState(false) + const [selectedRole, setSelectedRole] = useState(null) + const { toggleNotification } = useNotification() + const [selectProjects, setSelectedProjects] = useState([]) + + const [roles, setRoles] = useState(null) + const [projects, setProjects] = useState(null) + useEffect(() => { + const getRoles = async () => { + const { data, errors, isSuccess } = await fetchRequest("/roles/") + if (isSuccess) { + setRoles(data) + } else { + setRoles([]) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + const getProjects = async () => { + const { isSuccess, errors, data } = await fetchRequest("/projects/"); + if (isSuccess) { + setProjects(data); + } else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + getRoles() + getProjects() + }, []) + const handleFieldChange = (event) => setUserData({ ...userData, [event.target.name]: event.target.value }) + + const [userData, setUserData] = useState({ email: "", password: generateRandomPassword(), first_name: "", last_name: "" }) + const handleSubmit = async (event) => { + event.preventDefault() + setIsLoading(true) + const { data, errors, isSuccess, status } = await fetchRequest("/users/", { + method: "POST", + body: JSON.stringify({ ...userData, username: userData.email, role: selectedRole.id, project_ids: selectProjects.map((element) => element.id) }) + }) + if (isSuccess) { + setIsLoading(false) + setSelectedRole(null) + appendUser(data.data) + toggleNotification({ + visible: true, + message: "Le rôle a été créé avec succès", + type: "success" + }) + setIsOpen(false) + } else { + setIsLoading(false) + if (errors.type === "ValidationError") { + if (errors.detail.name) { + toggleNotification({ + visible: true, + message: "Le rôle existe déjà", + type: "warning" + }) + } + else { + toggleNotification({ + visible: true, + message: "Erreur de validation de rôle", + type: "warning" + }) + setIsOpen(false) + } + } + else if (status === 409) { + toggleNotification({ + visible: true, + message: "role 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) + } + } + const handleRoleClick = (newRole) => { + if (selectedRole?.id === newRole.id) { + setSelectedRole(null) + } else { + setSelectedRole(newRole) + } + } + const handleProjectClick = (project) => { + if (selectProjects.find((element) => element.id === project.id)) { + setSelectedProjects(selectProjects.filter((element) => element.id !== project.id)) + } else { + setSelectedProjects([...selectProjects, project]) + } + } + return ( +
    +
    + setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> +
    +

    Ajout d utilisateur

    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + +
    + {(roles) ?
    +
    + +
    +
    + {roles.length !== 0 ? roles?.map((role) => { + const isSelected = selectedRole?.id === role.id + return
    handleRoleClick(role)} key={role.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`}> + {role.name} +
    + }) :
    +

    Pas encore des habilitations

    +
    } +
    +
    :
    } + {(projects) ?
    +
    + +
    +
    + {projects.length !== 0 ? projects?.map((project) => { + const isSelected = selectProjects.find((element) => element.id === project.id) !== undefined + return
    handleProjectClick(project)} key={project.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`}> + {project.nom} +
    + }) :
    +

    Pas encore des projets

    +
    } +
    +
    :
    } +
    + +
    + +
    +
    + ) +} + +export default CreateUserForm \ No newline at end of file diff --git a/src/app/user/UpdateUserForm.jsx b/src/app/user/UpdateUserForm.jsx new file mode 100644 index 0000000..d214f18 --- /dev/null +++ b/src/app/user/UpdateUserForm.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +const UpdateUserForm = () => { + return ( +
    UpdateUserForm
    + ) +} + +export default UpdateUserForm \ No newline at end of file diff --git a/src/app/user/UserTableRow.jsx b/src/app/user/UserTableRow.jsx new file mode 100644 index 0000000..1860ee8 --- /dev/null +++ b/src/app/user/UserTableRow.jsx @@ -0,0 +1,88 @@ +import React, { useState } from 'react' +import DeleteIcon from "@/static/image/svg/delete.svg" +import EditIcon from "@/static/image/svg/edit.svg" +import ConfirmationModal from '../ui/ConfirmationModal' +import { useNotification } from '@/context/NotificationContext' +import fetchRequest from '../lib/fetchRequest' + +const UserTableRow = ({ email, last_name, first_name, id, setUsers, role, projects }) => { + const { toggleNotification } = useNotification() + const [isModalOpen, setModalOpen] = useState(false); + const showDeletePopup = () => { + setModalOpen(true); + } + const handleDelete = async () => { + const { isSuccess, errors, status } = await fetchRequest(`/users/${id}/`, { method: "DELETE" }) + if (isSuccess) { + setUsers((users) => users.filter((element) => element.id !== id)) + toggleNotification({ + visible: true, + message: "L'utilisateur a été supprimé avec succès", + type: "success" + }) + } else if (status == 404) { + toggleNotification({ + visible: true, + message: "L'utilisateur n'a pas été trouvé", + type: "warning" + }) + } else if (errors.detail?.indexOf("Cannot delete some instances of model 'Role'") !== -1) { + toggleNotification({ + visible: true, + message: "Impossible de supprimer cet utilisateur car il est attribué à des utilisateurs", + type: "warning" + }) + } + else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + setModalOpen(false) + console.log(errors) + } + return ( + <> + + + + + + + + setModalOpen(false)} + onConfirm={handleDelete} + message={`Voulez-vous vraiment supprimer le rôle ?`} + /> + + ) +} + +export default UserTableRow \ No newline at end of file diff --git a/src/app/user/page.jsx b/src/app/user/page.jsx new file mode 100644 index 0000000..ff8ef67 --- /dev/null +++ b/src/app/user/page.jsx @@ -0,0 +1,77 @@ +'use client'; +import React, { useEffect, useState } from 'react' +import CreateUserForm from './CreateUserForm' +import UpdateUserForm from './UpdateUserForm' +import AddIcon from "@/static/image/svg/add.svg" +import Loader from '@/components/Loader/Loader' +import fetchRequest from '../lib/fetchRequest'; +import { isArray } from '../lib/TypesHelper'; +import { useNotification } from '@/context/NotificationContext'; +import UserTableRow from './UserTableRow'; + +const UserPage = () => { + const [users, setUsers] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [openCreatePopup, setOpenCreatePopup] = useState(null) + const [userToUpdate, setUserToUpdate] = useState(null) + const { toggleNotification } = useNotification() + + useEffect(() => { + const getUsers = async () => { + const { data, errors, isSuccess } = await fetchRequest("/users") + setIsLoading(false) + if (isSuccess) { + console.log(data) + setUsers(data) + } else { + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + getUsers() + }, []) + const appendUser = (newUser) => { + setUsers([newUser, ...users]) + } + return (
    +
    +
    + {openCreatePopup && } + {userToUpdate && } +
    +

    List des Utilisateurs

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

    Pas encore des utilisateurs

    +
    + :
    +
    rôleRôle Habilitations Action
    +

    {first_name} {last_name}

    +
    +

    {email}

    +
    +

    {role?.name || ""}

    +
    +
      + {(!projects || projects.length === 0) &&

      -

      } + {projects?.map((project) => { + return
    • {project.nom}
    • + })} +
    +
    +
    + + +
    +
    + + + + + + + + {users?.map((element) => { + return + })} +
    NomEmailRôleProjectsAction
    +
    + }} +
    +
    +
    + ) +} + +export default UserPage \ No newline at end of file -- GitLab From d6ebca05d349f81b58cde78cd943cefbd8152eb2 Mon Sep 17 00:00:00 2001 From: Raed BOUAFIF Date: Tue, 28 May 2024 11:36:20 +0100 Subject: [PATCH 10/10] Feature zoanning CRUD Compeleted_1 --- src/app/place/CreateNewPlace.jsx | 2 +- src/app/table/CreateNewTable.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/place/CreateNewPlace.jsx b/src/app/place/CreateNewPlace.jsx index 1d6342d..8036b0a 100644 --- a/src/app/place/CreateNewPlace.jsx +++ b/src/app/place/CreateNewPlace.jsx @@ -79,7 +79,7 @@ const CreateNewPlace = ({placesState, tables}) => {

    Ajout d'une place

    - +
    diff --git a/src/app/table/CreateNewTable.jsx b/src/app/table/CreateNewTable.jsx index 0e8478d..fc2f3cf 100644 --- a/src/app/table/CreateNewTable.jsx +++ b/src/app/table/CreateNewTable.jsx @@ -78,7 +78,7 @@ const CreateNewTable = ({ tablesState, zones }) => {
    - +
    -- GitLab