diff --git a/src/app/(dashboard)/assign_zone_project/AssignProject.jsx b/src/app/(dashboard)/assign-zone-project/AssignProject.jsx similarity index 100% rename from src/app/(dashboard)/assign_zone_project/AssignProject.jsx rename to src/app/(dashboard)/assign-zone-project/AssignProject.jsx 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..76045feeb37275237416da5c038a4c6d39c8e69c --- /dev/null +++ b/src/app/(dashboard)/assign-zone-project/CompleteAffectation.jsx @@ -0,0 +1,304 @@ +"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) + + console.log("fullAffectations", fullAffectations) + console.log("affectations", affectations) + + + 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("projectss", affectations) + console.log("selected project", selectedProject) + + + const handleAssignProject = async () => { + const finalData = { + id_zone: selectedZone, + id_project: selectedProject.project.id, + jour: selectedProject.jour, + semaine: selectedProject.semaine, + nombre_personnes: selectedProject.nombre_personnes, + 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) + } + + console.log(nbrCollabs) + + 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={`cursor-pointer mx-auto will-change-contents min-h-8 text-sm break-words flex flex-col items-center justify-center duration-150 delay-75 border-2 py-0.5 rounded-md w-fit px-2 text-sushi-600 ${ selectedProject?.project?.id === element.project.id ? "font-semibold border-sushi-400 bg-sushi-100" : "font-medium hover:border-sushi-400 hover:bg-sushi-100 border-sushi-400 bg-white"}`}> +

Projet: {element?.project?.nom} -- Collaborateurs: {element.nbr_personnes_restant}

+

Semaine: {element.semaine} -- Jour: {element.jour}

+
+ ) + } +
+
+
+

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 new file mode 100644 index 0000000000000000000000000000000000000000..51c353754c604b197ef490a74857747bda96038f --- /dev/null +++ b/src/app/(dashboard)/assign-zone-project/page.jsx @@ -0,0 +1,338 @@ +"use client" +import React, { useEffect, useState } from 'react'; +import AddIcon from "@/static/image/svg/add.svg"; +import AssignProject from './AssignProject'; +import { useNotification } from '@/context/NotificationContext' + +import Loader from '@/components/Loader/Loader' +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() + const [selectedWeek, setSelectedWeek] = useState(null) + 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.id_project.users.length + }; + } + + 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.id_project.users.length - 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.id_project.users.length - project.places_occuper, + nombre_personnes: project.id_project.users.length + }; + }); + + return result; + } + + const getListOfAffectedProjects = async () => { + setIsLoadingListProjects(true) + try { + if (selectedDay && selectedWeek) { + var { isSuccess, errors, data } = await fetchRequest(`/zoaning/getListAffectedProjects/${selectedDay}/${selectedWeek}/`, { method: 'GET' }) + } else if (selectedWeek) { + var { isSuccess, errors, data } = await fetchRequest(`/zoaning/getListAffectedProjectsByWeek/${selectedWeek}/`, { method: 'GET' }) + } else if (selectedDay) { + var { isSuccess, errors, data } = await fetchRequest(`/zoaning/getListAffectedProjectsByDay/${selectedDay}/`, { method: 'GET' }) + } else { + var { isSuccess, errors, data } = await fetchRequest(`/zoaning/getListAffectedProjects/`, { method: 'GET' }) + } + if (isSuccess) { + setListProjectsAffected(data) + console.log("this is our ", data); + setListProjectsSemiAffected(filterAndGroupProjects(data)) + } else { + toggleNotification({ + visible: true, + message: errors[0].message, + type: "error" + }) + } + setIsLoadingListProjects(false) + } catch (error) { + setIsLoadingListProjects(false) + console.log(error) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + getListOfAffectedProjects() + }, [selectedDay, selectedWeek]) + + + const handleOpenAssignProject = () => { + setIsOpen(!isOpen) + } + + const handleOpenCompleteAffectation = () => { + setIsOpenCompleteAffectation(!isOpenCompleteAffectation) + } + + const handleDeleteAffectation = async () => { + try { + 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" + }) + const filteredProjectsAffected = listProjectsAffected.filter(affected => affected.id !== selectedAffectaionToDelete.id) + setListProjectsAffected(filteredProjectsAffected) + mutateProjectsAffectaionCheck(filteredProjectsAffected) + } else if (status === 404) { + toggleNotification({ + visible: true, + message: "Affectation introuvable", + type: "error" + }) + } else { + toggleNotification({ + visible: true, + message: errors[0].message, + type: "error" + }) + } + } catch (error) { + console.log(error) + toggleNotification({ + visible: true, + message: "Internal Server Error", + type: "error" + }) + } + } + + const handleDeleteClick = (element) => { + setSelectedAffectationToDelete(element) + setModalOpen(true); + } + + + const handleConfirmDelete = () => { + handleDeleteAffectation(); + setModalOpen(false); + 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.id_project.users.length + }; + } + + 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.id_project.users.length - 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.id_project.users.length - project.places_occuper, + nombre_personnes: project.id_project.users.length + }; + }); + + return result; + } + + const mutateProjectsAffectaionCheck = (passedData) => { + setListProjectsSemiAffected(filterAndGroupProjects(passedData)) + } + + + + return ( +
+
+ {isOpen && } + {(isOpenCompleteAffectation) && } +

List des Projets attribuer

+
+ +
+ +
+
+ +
+ {(!isLoadingListProjects) ? + (listProjectsAffected && listProjectsAffected.length > 0) ? + + + + + + + + + + + + + + {(listProjectsAffected.map((element, index) => + + {/* */} + + + + + + + + + ))} + +
+ Date + + Plateau + + Projet + + Places occupées + + Nombre des personnes + + Places disponible + + Actions +
+ Semaine: {element.semaine} - Jour: {element.jour} + + {element.id_zone.nom}-{element.id_zone.id_etage.numero} + + {element.id_project.nom} + + {element.places_occuper} + + {element.nombre_personnes} + + {element.places_disponibles} + +
handleDeleteClick(element)} class="font-medium text-blue-600 dark:text-blue-500 hover:underline">
+
+ : +
+ Aucun projet affecter +
+ : +
+ } + { + (listProjectsSemiAffected && listProjectsSemiAffected.length) ?
+ +
+ : + "" + } + setModalOpen(false)} + onConfirm={handleConfirmDelete} + message={`Êtes-vous sûr de vouloir supprimer l'affectation "${selectedAffectaionToDelete?.id_project.nom}"?`} + /> +
+ + ); +} + + +export default AffectingZoneProject; diff --git a/src/app/(dashboard)/privilege/page.jsx b/src/app/(dashboard)/privilege/page.jsx index 3cea73fcfb6563c9c68631c99616527b7f488d5b..f10039bf8b06739cf424d8b4c759122b37c03223 100644 --- a/src/app/(dashboard)/privilege/page.jsx +++ b/src/app/(dashboard)/privilege/page.jsx @@ -26,7 +26,7 @@ const Privilege = () => { return (
-

List des habilitations

+

Liste des habilitations

{isLoading &&
} {!isLoading && <> {(!isArray(privileges) || privileges?.length === 0) ?
diff --git a/src/app/(dashboard)/reporting/BubbleStatistic.jsx b/src/app/(dashboard)/reporting/BubbleStatistic.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a3d82a4170b120a41d02a142c15a0ae90842637a --- /dev/null +++ b/src/app/(dashboard)/reporting/BubbleStatistic.jsx @@ -0,0 +1,91 @@ +"use client" +import React, { memo, useMemo, useState } from 'react' +import { Bubble } from 'react-chartjs-2'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + Tooltip, + Legend +} from 'chart.js'; +import { generateColors } from '@/app/lib/colorsGenerator'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + Tooltip, + Legend +); + + +const BubbleStatistic = React.memo(function BubbleStatistic({ axisX, data, title }) { + + const colors = useMemo(() => data ? generateColors(data.length) : [], [data]) + const options = { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + type: 'category', + labels: axisX, + }, + y: { + type: 'linear', + min: 0, + max: 125, + ticks: { + stepSize: 25, + callback: function (value) { + return value.toFixed(2).toString() + '%'; + } + }, + title: { + display: true, + text: 'Présence', + color: '#333', + font: { + size: 14, + weight: 'bold' + } + } + } + }, + plugins: { + tooltip: { + callbacks: { + label: function (context) { + const data = context.dataset.data[context.dataIndex]; + const label = context.dataset.label || ''; + return `${label}: (${data.x}, ${data.y.toFixed(2)}%)`; + } + } + } + } + }; + + const chartData = useMemo(() => { + if (!data) return { + datasets: [] + } + return { + datasets: data.map((element, index) => ({ + ...element, + backgroundColor: colors[index].backgroundColor, + borderColor: colors[index].borderColor, + borderWidth: 1 + })) + }; + }, [data, colors]) + return ( +
+

{title}

+
+ +
+
+ ); +}) + +export default BubbleStatistic; \ No newline at end of file diff --git a/src/app/(dashboard)/reporting/page.jsx b/src/app/(dashboard)/reporting/page.jsx new file mode 100644 index 0000000000000000000000000000000000000000..524f1e8cd9d79386e1a36e0a0ac2995a56ede2fe --- /dev/null +++ b/src/app/(dashboard)/reporting/page.jsx @@ -0,0 +1,139 @@ +"use client" + + +import React, { useState, useEffect, useRef, useMemo } from 'react' +import BubbleStatistic from './BubbleStatistic'; +import fetchRequest from '@/app/lib/fetchRequest'; +import Loader from '@/components/Loader/Loader'; +import { useNotification } from '@/context/NotificationContext'; +import { extractDate, getDateRange, subtractDays } from '@/app/lib/DateHelper'; + + +const Reporting = () => { + const [chartDataZone, setChartDataZone] = useState(null) + const [chartDataProject, setChartDataProject] = useState(null) + const [isLoadingZone, setIsLoadingZone] = useState(false) + const [isLoadingProject, setIsLoadingProject] = useState(false) + const { toggleNotification } = useNotification() + const [dates, setDates] = useState({ fromDate: extractDate(subtractDays(new Date(), 4)), toDate: extractDate(new Date()) }) + useEffect(() => { + const getZonesPresenceStatistic = async () => { + setIsLoadingZone(true) + const { data, errors, isSuccess } = await fetchRequest(`/zoaning/zone-presence/`, { + method: "POST", + body: JSON.stringify({ + from_date: dates.fromDate, + to_date: dates.toDate + }) + }) + setIsLoadingZone(false) + if (isSuccess) { + setChartDataZone(data); + } else { + console.log(errors); + toggleNotification({ + type: "error", + message: "Internal Server Error", + visible: true + }) + } + } + getZonesPresenceStatistic() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dates.fromDate, dates.toDate]) + + + useEffect(() => { + const getProjectsPresenceStatistic = async () => { + setIsLoadingProject(true) + const { data, errors, isSuccess } = await fetchRequest(`/zoaning/project-presence/`, { + method: "POST", + body: JSON.stringify({ + from_date: dates.fromDate, + to_date: dates.toDate + }) + }) + setIsLoadingProject(false) + if (isSuccess) { + console.log(data); + setChartDataProject(data); + } else { + console.log(errors); + toggleNotification({ + type: "error", + message: "Internal Server Error", + visible: true + }) + } + } + getProjectsPresenceStatistic() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dates.fromDate, dates.toDate]) + const handleDateChange = (event) => { + const name = event.target.name + const value = event.target.value + if (value) { + setDates({ ...dates, [name]: value }) + return; + } + else if (!value && name === "fromDate") setDates({ ...dates, "fromDate": extractDate(subtractDays(dates.toDate, 5)) }) + else if (!value && name === "toDate") setDates({ ...dates, "toDate": extractDate(new Date()) }) + } + const axisX = useMemo(() => getDateRange(dates.fromDate, dates.toDate), [dates.fromDate, dates.toDate]) + const processedProjectData = useMemo(() => { + return chartDataProject?.map((project, index) => ( + { + label: project.label, + data: [...project.data.map((element) => ( + { + x: element.date.split("-").reverse().join("-"), + y: element.pourcentage, r: (index * 2) + 6 + } + )), ...axisX.filter((element) => !project.data.find((elm) => elm.date.split("-").reverse().join("-") === element)).map((element) => ({ + x: element, + y: 0, + r: (index * 2) + 6 + }))] + })) + }, [chartDataProject, axisX]) + + const processedZoneData = useMemo(() => { + return chartDataZone?.map((zone, index) => ( + { + label: "Zone " + zone.label, + data: [...zone.data.map((element) => ( + { + x: element.date.split("-").reverse().join("-"), + y: element.pourcentage, r: (index * 2) + 6 + } + )), ...axisX.filter((element) => !zone.data.find((elm) => elm.date.split("-").reverse().join("-") === element)).map((element) => ({ + x: element, + y: 0, + r: (index * 2) + 6 + }))] + })) + }, [chartDataZone, axisX]) + return ( +
+
+
+ + +
+
+ + + {/* */} +
+
+ {(isLoadingZone || isLoadingProject) ?
+ +
:
+ + +
} +
+ ) +} + +export default Reporting \ No newline at end of file diff --git a/src/app/(dashboard)/role/page.jsx b/src/app/(dashboard)/role/page.jsx index 7e682d2905b647ab1d5de30aeed5354f4ac24a67..dfca5e0015cdcda426b78d1e4bf8375ba0de8701 100644 --- a/src/app/(dashboard)/role/page.jsx +++ b/src/app/(dashboard)/role/page.jsx @@ -40,7 +40,7 @@ const Role = () => { {openCreatePopup && } {roleToUpdate && }
-

List des Roles

+

Liste des Rôles

-
+ +
: "" } diff --git a/src/app/lib/DateHelper.js b/src/app/lib/DateHelper.js new file mode 100644 index 0000000000000000000000000000000000000000..f4aac7651c2645bb2c14673c40a1bfa538c50039 --- /dev/null +++ b/src/app/lib/DateHelper.js @@ -0,0 +1,32 @@ +export const subtractDays = (date, numberOfDays) => { + const result = new Date(date); + result.setDate(result.getDate() - numberOfDays); + return result; +}; + +/** + * date {Date} + * @return 2024-10-12 + */ +export const extractDate = (date) => { + if (date instanceof Date) + return date.toJSON().split("T")[0] + else throw new Error("date isn't instance of Date in extractDate Util function") +} + + +export const getDateRange = (fromDate, toDate) => { + const startDate = new Date(fromDate); + const endDate = new Date(toDate); + + const dateArray = []; + + let currentDate = startDate; + while (currentDate <= endDate) { + dateArray.push(currentDate.toISOString().split('T')[0].split("-").reverse().join("-")); + + currentDate.setDate(currentDate.getDate() + 1); + } + + return dateArray; +} \ No newline at end of file diff --git a/src/app/lib/colorsGenerator.js b/src/app/lib/colorsGenerator.js new file mode 100644 index 0000000000000000000000000000000000000000..ba53b5cea8bee55a479ff6657317c444926dad27 --- /dev/null +++ b/src/app/lib/colorsGenerator.js @@ -0,0 +1,5 @@ +import chroma from 'chroma-js'; + +export const generateColors = (numColors) => { + return chroma.scale(["#6574cd", "#f6ad55", "#e53e3e", "#cbd5e0", "#68D391"]).mode('lch').colors(numColors).map(color => ({ backgroundColor: chroma(color).alpha(0.3).css(), borderColor: chroma(color).alpha(0.6).css() })); +}; \ No newline at end of file diff --git a/src/app/no-access/page.jsx b/src/app/no-access/page.jsx new file mode 100644 index 0000000000000000000000000000000000000000..df868666c1e21c24fe3e5e04a536d78f87f145bb --- /dev/null +++ b/src/app/no-access/page.jsx @@ -0,0 +1,43 @@ +"use client"; +import { useRouter } from 'next/navigation'; +import {useEffect, useState} from 'react'; + +export default function NoAccess() { + const router = useRouter(); + const [countdown, setCountdown] = useState(5); + + useEffect(() => { + const interval = setInterval(() => { + setCountdown(prevCountdown => prevCountdown - 1); + }, 1000); + + const timeout = setTimeout(() => { + router.push('/'); // Redirect to homepage or a specific page after 5 seconds + }, 5000); + + return () => { + clearInterval(interval); + clearTimeout(timeout); + }; + }, [router]); + + return ( +
+
+

Access Denied

+

+ You do not have the necessary privileges to access this page. +

+

+ You will be redirected in {countdown} seconds... +

+ +
+
+ ); +} \ No newline at end of file diff --git a/src/app/ui/SideBar.jsx b/src/app/ui/SideBar.jsx index 6c3d3318c3f1511117035dbd36a78aa80cf10eb5..04c5c04e77381a7f70d1d793478b1c60fd863111 100644 --- a/src/app/ui/SideBar.jsx +++ b/src/app/ui/SideBar.jsx @@ -1,86 +1,75 @@ +"use client"; import SideBarLink from "./SideBarLink"; import RoleIcon from "@/static/image/svg/role.svg" import UserIcon from "@/static/image/svg/user.svg" import LogoutButton from "./LogoutButton"; +import isAuthenticated from "@/app/lib/isAuthenticated"; +import {useEffect, useState} from "react"; +import { usePathname } from "next/navigation"; const SideBar = () => { + const [isAuth, setIsAuth] = useState(false); + const [sessionData, setSessionData] = useState(null); + const pathname = usePathname(); + useEffect(() => { + const checkAuth = async () => { + const authResult = await isAuthenticated(); + setIsAuth(authResult.isAuth); + setSessionData(authResult.sessionData); + }; + + checkAuth(); + }, []); + + if (!isAuth || !sessionData) { + return ( + + ); + } const AdminLinks = [ - { - label: "Utilisateurs", - link: "/user", - icon: - }, - { - label: "Habilitations", - link: "/privilege", - icon: - }, - { - label: "Rôles", - link: "/role", - icon: - }, - { - label: "Projets", - link: "/projects" - , icon: - }, - { - label: "Réservation", - link: "/reservation", - icon: - }, - { - label: "Type de Presence", - link: "/planning/type-presence" - , icon: - }, - { - label: "Planning", - link: "/planning" - , icon: - }, - { - label: "Etage", - link: "/etage" - , icon: - }, - { - label: "Zone", - link: "/zone" - , icon: - }, - { - label: "Tables", - link: "/table" - , icon: - }, - { - label: "Places", - link: "/place" - , icon: - }, - { - label: "Gestion des zones", - link: "/assign_zone_project" - , icon: - }, + { label: "Utilisateurs", link: "/user", icon: , privilege: "user" }, + { label: "Habilitations", link: "/privilege", icon: , privilege: "privilege" }, + { label: "Rôles", link: "/role", icon: , privilege: "role" }, + { label: "Projets", link: "/projects", icon: , privilege: "projects" }, + { label: "Réservation", link: "/reservation", icon: , privilege: "reservation" }, + { label: "Type de Presence", link: "/planning/type-presence", icon: , privilege: "planning/type-presence" }, + { label: "Planning", link: "/planning", icon: , privilege: "planning" }, + { label: "Etage", link: "/etage", icon: , privilege: "etage" }, + { label: "Zone", link: "/zone", icon: , privilege: "zone" }, + { label: "Tables", link: "/table", icon: , privilege: "table" }, + { label: "Places", link: "/place", icon: , privilege: "place" }, + { label: "Gestion des zones", link: "/assign-zone-project", icon: , privilege: "assign-zone-project" }, + { label: "Consulter les réservations", link: "/consultation-reservations", icon: , privilege: "consultation-reservations" }, + ]; + + console.log('sessionDataSideBar', sessionData) + const filteredLinks = AdminLinks.filter(link => sessionData?.privileges.includes(link.privilege)); + console.log('filteredLinks', filteredLinks) + + const ConsultationLinks = [ { label: "Consulter les réservations", - link: "/consultation-reservations" - , icon: - }, + link: "/consultation-reservations", + icon: + } ] + return ( -