From b15878679a2e673314d422a7d42470ef47ddbf6b Mon Sep 17 00:00:00 2001 From: Raed BOUAFIF Date: Wed, 26 Jun 2024 15:42:11 +0100 Subject: [PATCH] reporting feauter per week --- .../(dashboard)/reporting/BubbleStatistic.jsx | 1 - src/app/(dashboard)/reporting/page.jsx | 292 ++++++++++++++++-- src/app/lib/DateHelper.js | 128 ++++++-- 3 files changed, 369 insertions(+), 52 deletions(-) diff --git a/src/app/(dashboard)/reporting/BubbleStatistic.jsx b/src/app/(dashboard)/reporting/BubbleStatistic.jsx index a3d82a4..641e0c6 100644 --- a/src/app/(dashboard)/reporting/BubbleStatistic.jsx +++ b/src/app/(dashboard)/reporting/BubbleStatistic.jsx @@ -21,7 +21,6 @@ ChartJS.register( const BubbleStatistic = React.memo(function BubbleStatistic({ axisX, data, title }) { - const colors = useMemo(() => data ? generateColors(data.length) : [], [data]) const options = { responsive: true, diff --git a/src/app/(dashboard)/reporting/page.jsx b/src/app/(dashboard)/reporting/page.jsx index 22ca155..a4ac8ab 100644 --- a/src/app/(dashboard)/reporting/page.jsx +++ b/src/app/(dashboard)/reporting/page.jsx @@ -1,18 +1,220 @@ -"use client" +// "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'; +// import UserIcon from "@/static/image/svg/user.svg" +// import ProjectIcon from "@/static/image/svg/project.svg" +// import StatisticsIcon from "@/static/image/svg/statistics.svg" +// import DeskIcon from "@/static/image/svg/desk-1.svg" + + +// const Reporting = () => { +// const [chartDataZone, setChartDataZone] = useState(null) +// const [chartDataProject, setChartDataProject] = useState(null) +// const [isLoadingZone, setIsLoadingZone] = useState(false) +// const [isLoadingProject, setIsLoadingProject] = useState(false) +// const [countUsers, setCountUsers] = useState(0) +// const [countProjects, setCountProjects] = useState(0) +// const [countPlaces, setCountPlaces] = useState(0) +// const { toggleNotification } = useNotification() +// const [dates, setDates] = useState({ fromDate: extractDate(subtractDays(new Date(), 4)), toDate: extractDate(new Date()) }) + +// useEffect(() => { +// const getStats = async () => { +// const { data, errors, isSuccess } = await fetchRequest(`/statistics/`, { +// method: "GET", +// }) +// if (isSuccess) { +// setCountUsers(data.user_count) +// setCountProjects(data.project_count) +// setCountPlaces(data.place_count) +// } else { +// console.log(errors); +// toggleNotification({ +// type: "error", +// message: "Internal Server Error", +// visible: true +// }) +// } +// } +// getStats() +// }, []) +// 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) { +// console.log("Zones presences :", data) +// 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) => { +// console.log("week selection",event.target.value) +// 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()) }) +// } +// console.log(dates) +// 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 ( +//
+//
+//
+//

Collaborateurs

+//
+// {countUsers} +//
+// +// +//
+//
+//
+//
+//

Projets

+//
+// {countProjects} +//
+// +// +//
+//
+//
+//
+//

Places

+//
+// {countPlaces} +//
+// +// +//
+//
+//
+//
+//
+// +// +//
+//
+// +// +// {/* */} +//
+//
+//
+// {(isLoadingZone || isLoadingProject) ?
+// +//
:
+// +// +//
} +//
+// ) +// } + +// export default Reporting + +"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'; +import { getWeeksBetween, getWeekRange, extractDate } from '@/app/lib/DateHelper'; import UserIcon from "@/static/image/svg/user.svg" import ProjectIcon from "@/static/image/svg/project.svg" import StatisticsIcon from "@/static/image/svg/statistics.svg" import DeskIcon from "@/static/image/svg/desk-1.svg" - const Reporting = () => { const [chartDataZone, setChartDataZone] = useState(null) const [chartDataProject, setChartDataProject] = useState(null) @@ -22,7 +224,20 @@ const Reporting = () => { const [countProjects, setCountProjects] = useState(0) const [countPlaces, setCountPlaces] = useState(0) const { toggleNotification } = useNotification() - const [dates, setDates] = useState({ fromDate: extractDate(subtractDays(new Date(), 4)), toDate: extractDate(new Date()) }) + + const getCurrentWeekString = () => { + const currentDate = new Date(); + const year = currentDate.getFullYear(); + const week = getWeekNumber(currentDate); + return `${year}-W${week.toString().padStart(2, '0')}`; + } + + const currentWeekString = getCurrentWeekString(); + + const [weeks, setWeeks] = useState({ + fromWeek: getCurrentWeekString(), // Initialize with current week + toWeek: getCurrentWeekString() + }); useEffect(() => { const getStats = async () => { @@ -44,22 +259,22 @@ const Reporting = () => { } getStats() }, []) - console.log(countPlaces) - console.log(countProjects) - console.log(countUsers) useEffect(() => { const getZonesPresenceStatistic = async () => { setIsLoadingZone(true) + const { weekStart: from_date, weekEnd: to_date } = getWeekRange(weeks.fromWeek); + const { weekStart: end_from_date, weekEnd: end_to_date } = getWeekRange(weeks.toWeek); const { data, errors, isSuccess } = await fetchRequest(`/zoaning/zone-presence/`, { method: "POST", body: JSON.stringify({ - from_date: dates.fromDate, - to_date: dates.toDate + from_date: from_date, + to_date: end_to_date }) }) setIsLoadingZone(false) if (isSuccess) { + console.log("zones presences :", data) setChartDataZone(data); } else { console.log(errors); @@ -71,23 +286,23 @@ const Reporting = () => { } } getZonesPresenceStatistic() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dates.fromDate, dates.toDate]) - + }, [weeks.fromWeek, weeks.toWeek]) useEffect(() => { const getProjectsPresenceStatistic = async () => { setIsLoadingProject(true) + const { weekStart: from_date, weekEnd: to_date } = getWeekRange(weeks.fromWeek); + const { weekStart: end_from_date, weekEnd: end_to_date } = getWeekRange(weeks.toWeek); const { data, errors, isSuccess } = await fetchRequest(`/zoaning/project-presence/`, { method: "POST", body: JSON.stringify({ - from_date: dates.fromDate, - to_date: dates.toDate + from_date: from_date, + to_date: end_to_date }) }) setIsLoadingProject(false) if (isSuccess) { - console.log(data); + console.log("projects presences :", data) setChartDataProject(data); } else { console.log(errors); @@ -99,29 +314,31 @@ const Reporting = () => { } } getProjectsPresenceStatistic() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dates.fromDate, dates.toDate]) + }, [weeks.fromWeek, weeks.toWeek]) + const handleDateChange = (event) => { const name = event.target.name const value = event.target.value if (value) { - setDates({ ...dates, [name]: value }) + setWeeks({ ...weeks, [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()) }) + else if (!value && name === "fromWeek") setWeeks({ ...weeks, "fromWeek": currentWeekString }) + else if (!value && name === "toWeek") setWeeks({ ...weeks, "toWeek": currentWeekString }) } - const axisX = useMemo(() => getDateRange(dates.fromDate, dates.toDate), [dates.fromDate, dates.toDate]) + + const axisX = useMemo(() => getWeeksBetween(weeks.fromWeek, weeks.toWeek).map(week => week.axisX), [weeks.fromWeek, weeks.toWeek]); + const processedProjectData = useMemo(() => { return chartDataProject?.map((project, index) => ( { label: project.label, data: [...project.data.map((element) => ( { - x: element.date.split("-").reverse().join("-"), + x: element.start_date_week+' -- '+element.end_date_week, y: element.pourcentage, r: (index * 2) + 6 } - )), ...axisX.filter((element) => !project.data.find((elm) => elm.date.split("-").reverse().join("-") === element)).map((element) => ({ + )), ...axisX.filter((element) => !project.data.find((elm) => elm.start_date_week == element.split('--')[0].trim())).map((element) => ({ x: element, y: 0, r: (index * 2) + 6 @@ -135,16 +352,17 @@ const Reporting = () => { label: "Zone " + zone.label, data: [...zone.data.map((element) => ( { - x: element.date.split("-").reverse().join("-"), + x: element.start_date_week+' -- '+element.end_date_week, y: element.pourcentage, r: (index * 2) + 6 } - )), ...axisX.filter((element) => !zone.data.find((elm) => elm.date.split("-").reverse().join("-") === element)).map((element) => ({ + )), ...axisX.filter((element) => !zone.data.find((elm) => elm.start_date_week == element.split('--')[0].trim())).map((element) => ({ x: element, y: 0, r: (index * 2) + 6 }))] })) }, [chartDataZone, axisX]) + return (
@@ -180,13 +398,12 @@ const Reporting = () => {
- - + +
- - - {/* */} + +
@@ -200,4 +417,17 @@ const Reporting = () => { ) } -export default Reporting \ No newline at end of file +export default Reporting + +/** + * Get the ISO week number of a date. + * @param {Date} date - The date to get the week number for. + * @returns {number} The ISO week number. + */ +const getWeekNumber = (date) => { + const tempDate = new Date(date.getTime()); + tempDate.setHours(0, 0, 0, 0); + tempDate.setDate(tempDate.getDate() + 3 - ((tempDate.getDay() + 6) % 7)); + const week1 = new Date(tempDate.getFullYear(), 0, 4); + return 1 + Math.round(((tempDate.getTime() - week1.getTime()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7); +}; diff --git a/src/app/lib/DateHelper.js b/src/app/lib/DateHelper.js index f4aac76..a056401 100644 --- a/src/app/lib/DateHelper.js +++ b/src/app/lib/DateHelper.js @@ -1,32 +1,120 @@ -export const subtractDays = (date, numberOfDays) => { - const result = new Date(date); - result.setDate(result.getDate() - numberOfDays); - return result; +// 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; +// } + +/** + * Convert a week string (YYYY-W##) to a date object representing the first day of that week. + * @param {string} weekString - The week string in format 'YYYY-W##'. + * @returns {Date} The date object representing the first day of the week. + */ +const getFirstDayOfWeek = (weekString) => { + const [year, week] = weekString.split('-W').map(Number); + const simple = new Date(year, 0, 1 + (week - 1) * 7); + const dow = simple.getDay(); + let ISOweekStart = new Date(simple); + + if (dow <= 4) { + ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1); + } else { + ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay()); + } + + // Adjust for time zone offset to ensure consistency + ISOweekStart.setHours(0, 0, 0, 0); + + return ISOweekStart; +}; +/** + * Get the start and end dates of the week given a week string (YYYY-W##). + * @param {string} weekString - The week string in format 'YYYY-W##'. + * @returns {Object} An object with `weekStart` and `weekEnd` dates. + */ +export const getWeekRange = (weekString) => { + const startDate = getFirstDayOfWeek(weekString); + const endDate = new Date(startDate); + endDate.setDate(startDate.getDate() + 6); + + // Adjust for time zone offset to ensure consistency + endDate.setHours(23, 59, 59, 999); + + return { + weekStart: extractDate(startDate), + weekEnd: extractDate(endDate) + }; }; /** - * date {Date} - * @return 2024-10-12 + * Extract the date in 'YYYY-MM-DD' format. + * @param {Date} date - The date to format. + * @returns {string} The formatted date string. + * @throws {Error} If the input is not an instance of Date. */ 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") -} + if (date instanceof Date) { + return date.toJSON().split("T")[0]; + } else { + throw new Error("date isn't instance of Date in extractDate Util function"); + } +}; +/** + * Get the weeks between two week strings (YYYY-W##). + * @param {string} fromWeek - The start week string in format 'YYYY-W##'. + * @param {string} toWeek - The end week string in format 'YYYY-W##'. + * @returns {Array} An array of objects containing the start and end dates of each week. + */ +export const getWeeksBetween = (fromWeek, toWeek) => { + const fromDate = getFirstDayOfWeek(fromWeek); + const toDate = getFirstDayOfWeek(toWeek); + const weeks = []; -export const getDateRange = (fromDate, toDate) => { - const startDate = new Date(fromDate); - const endDate = new Date(toDate); + let currentDate = fromDate; + while (currentDate <= toDate) { + const weekEnd = new Date(currentDate); + weekEnd.setDate(currentDate.getDate() + 6); - const dateArray = []; + // Adjust for time zone offset to ensure consistency + weekEnd.setHours(23, 59, 59, 999); - let currentDate = startDate; - while (currentDate <= endDate) { - dateArray.push(currentDate.toISOString().split('T')[0].split("-").reverse().join("-")); + const adjustedStartDate = new Date(currentDate); + adjustedStartDate.setDate(currentDate.getDate() + 1); + weeks.push({ + axisX: `${extractDate(adjustedStartDate)} -- ${extractDate(weekEnd)}`, + weekStart: extractDate(currentDate), + weekEnd: extractDate(weekEnd) + }); - currentDate.setDate(currentDate.getDate() + 1); + currentDate.setDate(currentDate.getDate() + 7); } - return dateArray; -} \ No newline at end of file + return weeks; +}; \ No newline at end of file -- GitLab