}
{!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 (
+
+ );
+})
+
+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