diff --git a/src/app/(dashboard)/assign_zone_project/AssignProject.jsx b/src/app/(dashboard)/assign_zone_project/AssignProject.jsx index 60fd26de09e100d0897da893b01156694f9546a5..3c923a83f0492ba33864c5cedcb94cf62bc572dd 100644 --- a/src/app/(dashboard)/assign_zone_project/AssignProject.jsx +++ b/src/app/(dashboard)/assign_zone_project/AssignProject.jsx @@ -1,15 +1,13 @@ "use client" import React, { useState, useEffect, useRef } from 'react' -import Loader from '@/components/Loader/Loader' import { useNotification } from '@/context/NotificationContext' import CancelIcon from "@/static/image/svg/cancel.svg" import UserIcon from "@/static/image/svg/user.svg" import DeskIcon from "@/static/image/svg/study-desk.svg" -import AddIcon from "@/static/image/svg/add.svg" import fetchRequest from "@/app/lib/fetchRequest"; -const AssignProject = ({ setIsOpen, listProjects }) => { +const AssignProject = ({ setIsOpen, listProjects, affectations, mutateProjectAffection }) => { const [loading, setLoading] = useState(false) const [projects, setProjects] = useState([]) const [zones, setZones] = useState([]) @@ -26,7 +24,6 @@ const AssignProject = ({ setIsOpen, listProjects }) => { const { toggleNotification } = useNotification() const attributedCollabsRef = useRef() - useEffect(() => { const fetchProjectsandZones = async () => { setLoading(true) @@ -80,10 +77,16 @@ const AssignProject = ({ setIsOpen, listProjects }) => { if(selectedDay && selectedWeek) fetchProjectsandZones() }, [selectedDay, selectedWeek]) + const handleZoneSelection = async (e) => { const zone_id = e.target.value + const related_affecations = affectations.filter( (element) => element.jour == selectedDay && element.semaine == selectedWeek && element.id_zone.id == zone_id).map(element => element.id) try{ - const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces/${zone_id}`, {method: 'GET'}) + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces`, + {method: 'POST', body: JSON.stringify({ + related_affecations: related_affecations, + zone_id: zone_id + })}) if(isSuccess){ setSelectedZone(zone_id) setPlaces(data.places) @@ -144,9 +147,15 @@ const AssignProject = ({ setIsOpen, listProjects }) => { const handleOtherZoneSelection = async (e) => { const zone_id = e.target.value + const related_affecations = affectations.filter( (element) => element.jour == selectedDay && element.semaine == selectedWeek && element.id_zone.id == zone_id).map(element => element.id) setSelectedOtherZone(zone_id) try{ - const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces/${zone_id}`, {method: 'GET'}) + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces`, + {method: 'POST', body: JSON.stringify({ + related_affecations: related_affecations, + zone_id: zone_id + })} + ) if(isSuccess){ setOtherPlaces(data.places) }else{ @@ -169,14 +178,13 @@ const AssignProject = ({ setIsOpen, listProjects }) => { } }, [collabsAttributed]) - const handleAssignProject = async () => { const finalData = { id_zone: selectedZone, id_project: selectedProject, jour: selectedDay, semaine: selectedWeek, - nombre_personnes: collabsAttributed, + nombre_personnes: nbrCollabs, places_disponibles: (collabsAttributed > places.length) ? 0 : places.length - collabsAttributed, places_occuper: (collabsAttributed > places.length) ? places.length : collabsAttributed, places: (collabsAttributed > places.length) ? places.map( (element) => element.id) : places.map( (element, index) => index < collabsAttributed && element.id).filter(id => id !== false) @@ -192,7 +200,9 @@ const AssignProject = ({ setIsOpen, listProjects }) => { try{ const { isSuccess, errors, data } = await fetchRequest(`/zoaning/affectingProject`, {method: 'POST', body: JSON.stringify(finalData)}) if(isSuccess){ - listProjects( (prevListProjects) => [...prevListProjects, data.main_zone, ...(data.second_zone ? [data.second_zone] : [])]) + const newAffectations = [...affectations, data.main_zone, ...(data.second_zone ? [data.second_zone] : [])] + listProjects(newAffectations) + mutateProjectAffection(newAffectations) toggleNotification({ visible: true, message: "Projet affecté avec succès.", @@ -215,7 +225,6 @@ const AssignProject = ({ setIsOpen, listProjects }) => { type: "error" }) } - } const closePopup = () => { @@ -237,7 +246,6 @@ const AssignProject = ({ setIsOpen, listProjects }) => { } - console.log("other zone ::::" , selectedOtherZone) return (
@@ -355,4 +363,4 @@ const AssignProject = ({ setIsOpen, listProjects }) => { ) } -export default AssignProject \ No newline at end of file +export default AssignProject diff --git a/src/app/(dashboard)/assign_zone_project/CompleteAffectation.jsx b/src/app/(dashboard)/assign_zone_project/CompleteAffectation.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8da3fc7a83fdd5d8db6565eff61cbb1fb9c9eab9 --- /dev/null +++ b/src/app/(dashboard)/assign_zone_project/CompleteAffectation.jsx @@ -0,0 +1,303 @@ +"use client" +import React, { useState, useEffect, useRef } from 'react' +import { useNotification } from '@/context/NotificationContext' +import CancelIcon from "@/static/image/svg/cancel.svg" +import UserIcon from "@/static/image/svg/user.svg" +import DeskIcon from "@/static/image/svg/study-desk.svg" +import AddIcon from "@/static/image/svg/add.svg" +import fetchRequest from "@/app/lib/fetchRequest"; +import Loader from '@/components/Loader/Loader' + + + +const CompleteAffectation = ({ setIsOpen, listAffectationsState, affectations, fullAffectations, mutateProjectAffection }) => { + const [ selectedProject, setSelectedProject ] = useState(null) + const [ zones, setZones ] = useState([]) + const [ loading, setLoading ] = useState(false) + const [ places , setPlaces ] = useState([]) + const [ nbrCollabs, setNbrCollabs ] = useState(0) + const [ collabsAttributed, setCollabsAttributed ] = useState(0) + const { toggleNotification } = useNotification() + const [ selectedZone, setSelectedZone ] = useState(null) + const [ otherPlaces, setOtherPlaces ] = useState([]) + const [ selectedOtherZone, setSelectedOtherZone ] = useState(null) + + + const getZones = async (day, week) => { + console.log("day, week", day, week) + setLoading(true) + try { + const {isSuccess, errors, data} = await fetchRequest(`/zoaning/affectingProject/${day}/${week}`, {method: 'GET'}) + if(isSuccess){ + setCollabsAttributed(0) + setPlaces([]) + if(data.zones && data.zones.length === 0){ + toggleNotification({ + visible: true, + message: "Il y'a pas de zones pour cette semaine et ce jour.", + type: "warning" + }) + setZones([]) + }else{ + console.log("dsqqqqqdsqdqs") + setZones(data.zones) + } + + }else{ + // handle error + setLoading(false) + } + } catch (error) { + console.log(error) + toggleNotification({ title: 'Erreur', content: error.message, type: 'error' }) + } finally { + setLoading(false) + } + } + + const handleProjectSelection = (project) => { + if(selectedProject && selectedProject.project.id === project.project.id){ + setSelectedProject(null) + setZones([]) + setPlaces([]) + setCollabsAttributed(0) + return + } + console.log(project) + setSelectedProject(project) + getZones(project.jour, project.semaine) + setNbrCollabs(project.nbr_personnes_restant) + } + + + useEffect( () => { + if(nbrCollabs > 0 && places.length > 0){ + if( nbrCollabs <= places.length){ + setCollabsAttributed(nbrCollabs) + }else{ + setCollabsAttributed(places.length) + } + } + }, [nbrCollabs, places]) + + const handleSelectionZone = async (e) => { + const zone_id = e.target.value + const related_affecations = fullAffectations.filter( (element) => element.jour == selectedProject.jour && element.semaine == selectedProject.week && element.id_zone.id == zone_id).map(element => element.id) + try{ + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces`, + {method: 'POST', body: JSON.stringify({ + related_affecations: related_affecations, + zone_id: zone_id + })}) + if(isSuccess){ + console.log(data.places) + setSelectedZone(zone_id) + setPlaces(data.places) + setOtherPlaces([]) + setSelectedOtherZone(null) + }else{ + // handle error + setPlaces([]) + } + }catch(error){ + console.log(error) + } + } + + const handleOtherZoneSelection = async (e) => { + const zone_id = e.target.value + const related_affecations = fullAffectations.filter( (element) => element.jour == selectedProject.jour && element.semaine == selectedProject.semaine && element.id_zone.id == zone_id).map(element => element.id) + setSelectedOtherZone(zone_id) + try{ + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/countingPlaces`, + {method: 'POST', body: JSON.stringify({ + related_affecations: related_affecations, + zone_id: zone_id + })} + ) + if(isSuccess){ + setOtherPlaces(data.places) + }else{ + // handle error + } + }catch(error){ + console.log(error) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + const handleAddCollab = () =>{ + setCollabsAttributed(collabsAttributed + 1) + } + + const handleMinusCollab = () =>{ + setCollabsAttributed(collabsAttributed - 1) + } + + console.log("collabsAttributed", collabsAttributed) + console.log("nbrCollabs", nbrCollabs) + console.log("places", places) + + + + const handleAssignProject = async () => { + const finalData = { + id_zone: selectedZone, + id_project: selectedProject.project.id, + jour: selectedProject.jour, + semaine: selectedProject.semaine, + nombre_personnes: nbrCollabs, + places_disponibles: (collabsAttributed > places.length) ? 0 : places.length - collabsAttributed, + places_occuper: (collabsAttributed > places.length) ? places.length : collabsAttributed, + places: (collabsAttributed > places.length) ? places.map( (element) => element.id) : places.map( (element, index) => index < collabsAttributed && element.id).filter(id => id !== false) + } + if( selectedOtherZone && otherPlaces.length > 0){ + finalData.otherZone = { + id_zone: selectedOtherZone, + places_disponibles: ( (collabsAttributed - places.length ) > otherPlaces.length ) ? 0 : otherPlaces.length - (collabsAttributed - places.length), + places_occuper: ( (collabsAttributed - places.length ) > otherPlaces.length ) ? otherPlaces.length : collabsAttributed - places.length, + places: ( (collabsAttributed - places.length ) > otherPlaces.length ) ? otherPlaces.map( (element) => element.id) : otherPlaces.map( (element, index) => (index < (collabsAttributed - places.length)) && element.id).filter(id => id !== false) + } + } + console.log(finalData) + try{ + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/affectingProject`, {method: 'POST', body: JSON.stringify(finalData)}) + if(isSuccess){ + const newAffectations = [...fullAffectations, data.main_zone, ...(data.second_zone ? [data.second_zone] : [])] + listAffectationsState(newAffectations) + mutateProjectAffection(newAffectations) + toggleNotification({ + visible: true, + message: "Projet affecté avec succès.", + type: "success" + }) + closePopup(false) + }else{ + console.log(errors) + toggleNotification({ + visible: true, + message: "Erreur lors de l'affectation du projet", + type: "error" + }) + } + }catch(error){ + console.log(error) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + + + const closePopup = () => { + setIsOpen() + setSelectedProject(null) + setSelectedZone(null) + setCollabsAttributed(0) + setPlaces([]) + setNbrCollabs(0) + setSelectedOtherZone(null) + setOtherPlaces([]) + setZones([]) + setLoading(false) + } + + + + return ( +
+
+

Compléter l'affectation

+ {setIsOpen(false)}} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> +
+
+

Veuillez sélectionner un projet

+
+ {(affectations) && + affectations.map((element, index) => +
handleProjectSelection(element)} key={index} className='border-b border-gray-200'> + Projet: {element?.project?.nom} -- Collaborateurs: {element.nbr_personnes_restant} +
+ ) + } +
+
+
+

Veuillez sélectionner une zone

+
+ {(!loading) ? + (zones && zones.length) ? + + : +
+ Aucune zone disponible +
+ : +
+ +
+ + } +
+

: {places.length}

+
+
+
+ {(collabsAttributed > 0) &&
+

Collaborateurs affectées

+
+
+ {(collabsAttributed) ? collabsAttributed : 0} +
+ + +
+
+
+
+
0) ? "text-red-400" : "text-sushi-500"}`}>{nbrCollabs - collabsAttributed}
+ 0) ? "fill-red-400" : "fill-sushi-500"}`} /> +
+
+
0) ? "text-red-400" : "text-sushi-500"}`}>{(places.length+ otherPlaces.length) - collabsAttributed}
+ 0) ? "fill-red-400" : "fill-sushi-500"}`} /> +
+
+
+
} + {((collabsAttributed - places.length) > 0) &&
+

Veuillez sélectionner une autre zonne pour compléter l'affectation (optionnel)

+
+ +
+

: {otherPlaces.length}

+
+
+
} +
+
+ +
+
+
+ ) +} + +export default CompleteAffectation diff --git a/src/app/(dashboard)/assign_zone_project/page.jsx b/src/app/(dashboard)/assign_zone_project/page.jsx index 1c49160c614f62e7ae5a1f286291846479c80d8c..57a58ec74e6ed8c0bdff3f180a91e98ecad6f2ec 100644 --- a/src/app/(dashboard)/assign_zone_project/page.jsx +++ b/src/app/(dashboard)/assign_zone_project/page.jsx @@ -9,12 +9,14 @@ import EditIcon from "@/static/image/svg/edit.svg"; import DeleteIcon from "@/static/image/svg/delete.svg"; import fetchRequest from "@/app/lib/fetchRequest"; import ConfirmationModal from "@/app/ui/ConfirmationModal"; +import CompleteAffectation from './CompleteAffectation'; const AffectingZoneProject = () => { const [isOpen, setIsOpen] = useState(false) + const [ isOpenCompleteAffectation, setIsOpenCompleteAffectation ] = useState(false) const [ listProjectsAffected, setListProjectsAffected ] = useState([]) const [ isLoadingListProjects, setIsLoadingListProjects ] = useState(false) const { toggleNotification } = useNotification() @@ -22,9 +24,55 @@ const AffectingZoneProject = () => { const [ selectedDay, setSelectedDay ] = useState(null) const [ selectedAffectaionToDelete, setSelectedAffectationToDelete ] = useState(null) const [isModalOpen, setModalOpen] = useState(false); + const [ listProjectsSemiAffected, setListProjectsSemiAffected ] = useState([]) useEffect(() => { + + // this function is to detect if there is a project that is not fully affected and return the corresponding data to use + function filterAndGroupProjects(data) { + // Step 1: Create an object to aggregate data by id_project, semaine, and jour + const aggregatedProjects = {}; + + data.forEach(project => { + const projectId = project.id_project.id; + const week = project.semaine; + const day = project.jour; + const key = `${projectId}-${week}-${day}`; + + if (!aggregatedProjects[key]) { + aggregatedProjects[key] = { + id_project: project.id_project, + semaine: week, + jour: day, + places_disponibles: 0, + places_occuper: 0, + nombre_personnes: project.nombre_personnes + }; + } + + aggregatedProjects[key].places_disponibles += project.places_disponibles; + aggregatedProjects[key].places_occuper += project.places_occuper; + }); + + // Step 2: Filter out projects that don't meet the condition + const filteredProjects = Object.values(aggregatedProjects).filter(project => { + return project.nombre_personnes - project.places_occuper > 0; + }); + + // Step 3: Prepare the final result with additional fields + const result = filteredProjects.map(project => { + return { + project: project.id_project, + semaine: project.semaine, + jour: project.jour, + nbr_personnes_restant: project.nombre_personnes - project.places_occuper + }; + }); + + return result; + } + const getListOfAffectedProjects = async () => { setIsLoadingListProjects(true) try{ @@ -39,6 +87,7 @@ const AffectingZoneProject = () => { } if(isSuccess){ setListProjectsAffected(data) + setListProjectsSemiAffected(filterAndGroupProjects(data)) }else{ toggleNotification({ visible: true, @@ -65,17 +114,22 @@ const AffectingZoneProject = () => { setIsOpen(!isOpen) } + const handleOpenCompleteAffectation = () => { + setIsOpenCompleteAffectation(!isOpenCompleteAffectation) + } + const handleDeleteAffectation = async () => { try{ - console.log("qsdsqdqsdsqdq") - var { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/deteleAffectedProject/${selectedAffectaionToDelete.id}`, {method: 'DELETE'}) + var { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/deteleAffectedProject/${selectedAffectaionToDelete.id}`, {method: 'DELETE'}) if(isSuccess){ toggleNotification({ visible: true, message: "Affectation supprimer avec succès", type: "success" }) - setListProjectsAffected(listProjectsAffected.filter(affected => affected.id !== selectedAffectaionToDelete.id)) + const filteredProjectsAffected = listProjectsAffected.filter(affected => affected.id !== selectedAffectaionToDelete.id) + setListProjectsAffected(filteredProjectsAffected) + mutateProjectsAffectaionCheck(filteredProjectsAffected) }else if(status === 404){ toggleNotification({ visible: true, @@ -106,25 +160,68 @@ const AffectingZoneProject = () => { const handleConfirmDelete = () => { - console.log("qsdsq") handleDeleteAffectation(); - console.log("fdsfsd") setModalOpen(false); - setSelectedAffectationToDelete(null); + setSelectedAffectationToDelete(null); }; + // function to detect if there is a project not fully affected ( outside the use effect to mutate the changement of the state) + function filterAndGroupProjects(data) { + // Step 1: Create an object to aggregate data by id_project, semaine, and jour + const aggregatedProjects = {}; + + data.forEach(project => { + const projectId = project.id_project.id; + const week = project.semaine; + const day = project.jour; + const key = `${projectId}-${week}-${day}`; + + if (!aggregatedProjects[key]) { + aggregatedProjects[key] = { + id_project: project.id_project, + semaine: week, + jour: day, + places_disponibles: 0, + places_occuper: 0, + nombre_personnes: project.nombre_personnes + }; + } + + aggregatedProjects[key].places_disponibles += project.places_disponibles; + aggregatedProjects[key].places_occuper += project.places_occuper; + }); + + // Step 2: Filter out projects that don't meet the condition + const filteredProjects = Object.values(aggregatedProjects).filter(project => { + return project.nombre_personnes - project.places_occuper > 0; + }); + + // Step 3: Prepare the final result with additional fields + const result = filteredProjects.map(project => { + return { + project: project.id_project, + semaine: project.semaine, + jour: project.jour, + nbr_personnes_restant: project.nombre_personnes - project.places_occuper + }; + }); + + return result; + } + + const mutateProjectsAffectaionCheck = (passedData) => { + console.log("mutaion triggered") + setListProjectsSemiAffected(filterAndGroupProjects(passedData)) + } + + return ( -
-
- {isOpen && } +
+
+ {isOpen && } + {(isOpenCompleteAffectation) && }

List des Projets attribuer

-
- Il y a des projets qui ne sont pas complètement affecter. - -
+
onTypePresenceChange(rowIndex, dayIndex, e.target.value)} - className={`w-full h-full py-2 hover:bg-chicago-100/70 ${day.nom ==="Presentiel" ? 'bg-blue-50': - day.nom ==="Teletravail" ? 'bg-lime-50' : 'bg-white' }`} + className={`w-full h-full py-2 hover:bg-chicago-100/70 ${day.nom ==="Presentiel" ? 'bg-indigo-50 hover:bg-indigo-200': + day.nom ==="Teletravail" ? 'bg-teal-50 hover:bg-teal-100' : 'bg-white' }`} > {typePresences.map((typePresence) => ( - + ))} diff --git a/src/app/(dashboard)/privilege/CreatePrivilegeForm.jsx b/src/app/(dashboard)/privilege/CreatePrivilegeForm.jsx index 97c87c08ff9d329c7e61326cb8e94734351b040f..861c65b910f8b4c8b386866ac59fb2e7c9c110ef 100644 --- a/src/app/(dashboard)/privilege/CreatePrivilegeForm.jsx +++ b/src/app/(dashboard)/privilege/CreatePrivilegeForm.jsx @@ -59,7 +59,7 @@ const CreatePrivilegeForm = ({ appendPrivilege }) => { } } return ( -
+

Ajout d'habilitation

diff --git a/src/app/(dashboard)/reservation/PlaceUI.jsx b/src/app/(dashboard)/reservation/PlaceUI.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f3471558563f34ed71d9f6a2147178715fb10023 --- /dev/null +++ b/src/app/(dashboard)/reservation/PlaceUI.jsx @@ -0,0 +1,140 @@ +"use client" +import React, { useContext, useMemo, useState } from 'react' +import { ReservationContext } from './page' +import fetchRequest from '@/app/lib/fetchRequest' +import { useNotification } from '@/context/NotificationContext' +import Cookies from 'js-cookie'; +import { decrypt } from '@/app/lib/session'; +import ConfirmationModal from '@/app/ui/ConfirmationModal' +const PlaceUI = ({ id }) => { + const { allPlaces, selectedDate, bookedPlaces, setBookedPlaces } = useContext(ReservationContext) + const { toggleNotification } = useNotification() + const [isOpenBooking, setIsOpenBooking] = useState(false) + const [isOpenCanceling, setIsOpenCanceling] = useState(false) + const [authenticatedUserData, setAuthenticatedUserData] = useState(null) + + const cookie = Cookies.get("session") + const getUserData = async () => { + try { + if (cookie) { + const data = await decrypt(cookie) + setAuthenticatedUserData(data) + } + } catch (error) { + console.log(error) + } + } + useMemo(getUserData, [cookie]) + const place = allPlaces?.find((place) => place.id === id) + const bookedPlace = bookedPlaces?.find((bookedPlace) => bookedPlace.id_place === id) + const hasPlace = bookedPlaces?.some((p) => p.id_user === authenticatedUserData?.sessionData?.user_id) + const handleBooking = (event) => { + event.stopPropagation() + if (hasPlace) { + toggleNotification({ + visible: true, + message: "Veuillez annuler votre réservation pour réserver une nouvelle place .", + type: "warning" + }) + } + else setIsOpenBooking(true) + } + const handleBookingConfirmation = async () => { + const { isSuccess, errors, data } = await fetchRequest('/zoaning/reservations/', { + method: "POST", body: JSON.stringify( + { + "presence": false, + "date": selectedDate, + "id_place": id + } + ) + }); + if (isSuccess) { + console.log(data); + setBookedPlaces([...bookedPlaces, data]) + toggleNotification({ + visible: true, + message: "La réservation a été enregistrer avec succés", + type: "success" + }) + setIsOpenBooking(false) + } else { + if (errors.type === "ValidationError" && errors.detail?.non_field_errors && errors.detail?.non_field_errors[0]?.indexOf("date, id_place must make a unique set") !== -1) { + toggleNotification({ + visible: true, + message: "La place a été déjà réservée par votre collègue", + type: "warning" + }) + } + else toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + const handleCancelingConfirmation = async (idReservation) => { + const { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/reservations/${idReservation}`, { + method: "DELETE" + }); + if (isSuccess) { + console.log(data); + setBookedPlaces(bookedPlaces.filter((element) => element.id !== idReservation)) + toggleNotification({ + visible: true, + message: "La réservation a été annuler avec succés", + type: "success" + }) + setIsOpenCanceling(false) + } else { + if (status === 404) + toggleNotification({ + visible: true, + message: "La réservation n'existe pas", + type: "warning" + }) + else toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + console.log(errors) + } + } + + const handleCanceling = (event) => { + event.stopPropagation() + setIsOpenCanceling(true) + } + const closeCancelingPopup = (event) => { + event.stopPropagation() + setIsOpenCanceling(false) + } + const closeConfirmationPopup = (event) => { + event.stopPropagation() + setIsOpenBooking(false) + } + + + if (authenticatedUserData) + if (place) + if (bookedPlace) + if (bookedPlace.id_user === authenticatedUserData.sessionData?.user_id) + return
+

{place.project_name || ""}

+ handleCancelingConfirmation(bookedPlace.id)} onClose={closeCancelingPopup} /> +
+ else return
+

{place.project_name || ""}

+
+ else return
+

{place.project_name || ""}

+ +
+ else return
+ else return <> +} + + +export default PlaceUI \ No newline at end of file diff --git a/src/app/(dashboard)/reservation/TableUI.jsx b/src/app/(dashboard)/reservation/TableUI.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8c77ad5b0e81c6cc7e2c99b2e1b91b3ebeb66fe9 --- /dev/null +++ b/src/app/(dashboard)/reservation/TableUI.jsx @@ -0,0 +1,47 @@ +import React from 'react' +import PlaceUI from './PlaceUI'; + +const TableUI = ({ id, numero, places }) => { + function groupConsecutive(arr) { + + arr = arr.sort((a, b) => a.id - b.id) + if (arr.length === 0) { + return []; + } + + const grouped = []; + var counter = 0 + while (counter < arr.length) { + if (counter + 1 < arr.length) { + grouped.push([arr[counter], arr[counter + 1]]) + } + else { + grouped.push([arr[counter]]) + } + counter += 2; + } + + return grouped; + } + const processedPlaces = groupConsecutive(places).reverse() + + + if (!processedPlaces || processedPlaces.length === 0) return <> + return ( +
+ {processedPlaces.map((element, index) => { + return
+
+ +
+
+ {(element.length > 1) &&
+ +
} +
+ })} +
+ ) +} + +export default TableUI \ No newline at end of file diff --git a/src/app/(dashboard)/reservation/ZoneUI.jsx b/src/app/(dashboard)/reservation/ZoneUI.jsx new file mode 100644 index 0000000000000000000000000000000000000000..48ae7d19663b3c0b7c308a4271a2252e047b92c5 --- /dev/null +++ b/src/app/(dashboard)/reservation/ZoneUI.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import TableUI from './TableUI' + +const ZoneUI = ({ id, tables, nom }) => { + return ( +
+

{nom}

+
+ {tables.map((table) => { + return + })} +
+
+ ) +} + +export default ZoneUI \ No newline at end of file diff --git a/src/app/(dashboard)/reservation/page.jsx b/src/app/(dashboard)/reservation/page.jsx new file mode 100644 index 0000000000000000000000000000000000000000..37921d1a0f944967bd26d7a1809c4ddb1d6f247d --- /dev/null +++ b/src/app/(dashboard)/reservation/page.jsx @@ -0,0 +1,215 @@ +'use client' + +import React, { useEffect, useState } from 'react' +import ZoneUI from './ZoneUI' +import fetchRequest from '@/app/lib/fetchRequest' +import Loader from '@/components/Loader/Loader' +import { useNotification } from '@/context/NotificationContext' + +export const ReservationContext = React.createContext() + +const Reservation = () => { + const [isLoadingData, setIsLoadingData] = useState(false) + const { toggleNotification } = useNotification() + const [date, setDate] = useState({ day: null, week: null }) + const [isLoadingSelectsData, setIsLoadingSelectsData] = useState(true) + const [floors, setFloors] = useState([]) + const [projectsData, setProjectsData] = useState([]) + const [currentDateData, setCurrentDateData] = useState(null) + const [datesData, setDatesData] = useState(null) + const [bookedPlaces, setBookedPlaces] = useState([]) + useEffect(() => { + const getPlan = async () => { + try { + const { isSuccess, errors, data } = await fetchRequest('/zoaning/etage-zone-table-place/') + setIsLoadingSelectsData(false) + if (isSuccess) { + setFloors(data) + } else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } catch (error) { + console.log(error) + } + } + getPlan() + }, []) + + + + useEffect(() => { + const getSyncData = async () => { + const getUserPlan = async () => { + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/user-projects-zones-places/${date.week}/${date.day}`) + if (isSuccess) { + setProjectsData(data) + } else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + const getBookedPlaces = async () => { + const { isSuccess, errors, data } = await fetchRequest(`/zoaning/reservations/date/${date.date}/`) + if (isSuccess) { + console.log("booked places : :", data) + setBookedPlaces(data.map((element) => ({ ...element, id_place: element.id_place.id, presence: element.presence, id_user: element.id_user.id }))) + } + else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + setIsLoadingData(true) + await Promise.all([getUserPlan(), getBookedPlaces()]) + setIsLoadingData(false) + } + if (date.week && date.day) getSyncData() + else { + setProjectsData([]) + setBookedPlaces([]) + } + + }, [date.week, date.day]) + + useEffect(() => { + const getCurrentDateData = async () => { + const { isSuccess, errors, data } = await fetchRequest('/zoaning/current-date/'); + if (isSuccess) { + setCurrentDateData(data) + } else { + toggleNotification({ + visible: true, + message: "Failed to fetch current date", + type: "error" + }) + } + } + const YearCalendar = async () => { + const { isSuccess, errors, data } = await fetchRequest('/zoaning/dates/'); + if (isSuccess) { + console.log("dates data", data) + setDatesData(data) + } else { + console.log(errors) + toggleNotification({ + visible: true, + message: "Failed to fetch current date", + type: "error" + }) + } + } + const getSyncData = async () => { + await Promise.all([getCurrentDateData(), YearCalendar()]) + } + getSyncData() + }, []) + + const handleChangeDate = (event) => { + if (event.target.value) { + const dateSelected = JSON.parse(event.target.value); + console.log("weekMonthly", dateSelected.weekMonthly); + console.log("day", dateSelected.day); + setDate({ day: dateSelected.day, week: dateSelected.weekMonthly, date: dateSelected.date }) + } + else { + setDate({ day: null, week: null }) + } + } + + + const concernedZones = projectsData?.map((element) => element.zones.map((zone) => zone.id)).flatMap((element) => element) || [] + const concernedFloors = floors?.filter((element) => element.zones.map((zone) => zone.id).some((element) => concernedZones.includes(element))).map(element => element.id) || [] + const allPlaces = projectsData?.map((project) => project.zones.map((zone) => zone.places.map((place) => ({ ...place, project_name: project.project_name, project_id: project.project_id })))).flat(2) || [] + console.log("all places", allPlaces) + if (isLoadingSelectsData) + return
+ +
+ + const currentMonth = new Date().getMonth() + 1 + // filter dates from now to 2 weeks later + const filteredDatesData = datesData?.filter((element) => { + const date = new Date(element.date) + const month = date.getMonth() + 1 + return (month === currentMonth) && (date.getDate() >= new Date().getDate() && date.getDate() <= new Date().getDate() + 14) + }) + return ( +
+
+ +
+
+
+
+

Autres Projets

+
+
+
+

Disponible

+
+
+
+

Réservé par vous

+
+
+
+

Réservé par un collègue

+
+
+
+

Présent

+
+
+ {(!isLoadingData) + ? <> + {(floors.filter((element) => concernedFloors.includes(element.id))?.length > 0) &&
+ +
} +
+ + {floors.filter((element) => concernedFloors.includes(element.id)).map((floor) => { + return
+

Etage {floor.numero}

+ {floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { + return + })} +
+ })} + {floors.filter((element) => concernedFloors.includes(element.id)).length === 0 + &&
+

Un exemple d'un message à l'utilisateur

+
} +
+
+ + :
+ +
+ } +
+ ) +} + +export default Reservation \ No newline at end of file diff --git a/src/app/ui/ConfirmationModal.jsx b/src/app/ui/ConfirmationModal.jsx index 1c667ac3e0990319217a3c6136941e21731b095b..ad5e6cdd61dac10323164be0121811edb0eb5421 100644 --- a/src/app/ui/ConfirmationModal.jsx +++ b/src/app/ui/ConfirmationModal.jsx @@ -1,8 +1,29 @@ +"use client" + import React from 'react'; -const ConfirmationModal = ({ isOpen, onClose, onConfirm, message }) => { +const ConfirmationModal = ({ isOpen, onClose, onConfirm, message, type }) => { if (!isOpen) return null; - + else if (type === "create") return
+
+

Confirmation

+

{message}

+
+ + +
+
+
return (
@@ -13,13 +34,13 @@ const ConfirmationModal = ({ isOpen, onClose, onConfirm, message }) => { onClick={onClose} className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md" > - Cancel + Annuler
diff --git a/src/app/ui/SideBar.jsx b/src/app/ui/SideBar.jsx index 90f7df69a526a4a13afcb58546935f8216307676..4bab50a6d919bff0a44c48f3424867953bd6e4b7 100644 --- a/src/app/ui/SideBar.jsx +++ b/src/app/ui/SideBar.jsx @@ -25,6 +25,11 @@ const SideBar = () => { link: "/projects" , icon: }, + { + label: "Réservation", + link: "/reservation", + icon: + }, { label: "Planning", link: "/planning"