From b15878679a2e673314d422a7d42470ef47ddbf6b Mon Sep 17 00:00:00 2001 From: Raed BOUAFIF Date: Wed, 26 Jun 2024 15:42:11 +0100 Subject: [PATCH 01/14] 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 From 5fbca0c79a7c21d6ae4c97284e4c9e75f54732fd Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Wed, 26 Jun 2024 16:13:11 +0100 Subject: [PATCH 02/14] update user management pop-up design --- package-lock.json | 22 +++++ package.json | 1 + .../privilege/PrivilegesFilter.jsx | 2 +- src/app/(dashboard)/privilege/page.jsx | 2 +- src/app/(dashboard)/role/CreateRoleForm.jsx | 87 +++++++++++-------- src/app/(dashboard)/role/RolesFilter.jsx | 2 +- src/app/(dashboard)/role/UpdateRoleForm.jsx | 87 +++++++++++-------- src/app/(dashboard)/role/page.jsx | 2 +- src/app/(dashboard)/user/CreateUserForm.jsx | 40 +++++---- src/app/(dashboard)/user/UpdateUserForm.jsx | 35 ++++---- src/app/(dashboard)/user/UsersFilter.jsx | 2 +- src/app/(dashboard)/user/page.jsx | 2 +- tailwind.config.js | 2 +- 13 files changed, 174 insertions(+), 112 deletions(-) diff --git a/package-lock.json b/package-lock.json index 459b222..269508a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@svgr/webpack": "^8.1.0", + "@tailwindcss/forms": "^0.5.7", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", @@ -2813,6 +2814,18 @@ "tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" } }, + "node_modules/@tailwindcss/forms": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "dev": true, + "dependencies": { + "mini-svg-data-uri": "^1.2.3" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -5836,6 +5849,15 @@ "node": ">=8.6" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", diff --git a/package.json b/package.json index f9e94e3..f52a15c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@svgr/webpack": "^8.1.0", + "@tailwindcss/forms": "^0.5.7", "eslint": "^8", "eslint-config-next": "14.2.3", "postcss": "^8", diff --git a/src/app/(dashboard)/privilege/PrivilegesFilter.jsx b/src/app/(dashboard)/privilege/PrivilegesFilter.jsx index 306083f..d251f0e 100644 --- a/src/app/(dashboard)/privilege/PrivilegesFilter.jsx +++ b/src/app/(dashboard)/privilege/PrivilegesFilter.jsx @@ -19,7 +19,7 @@ const PrivilegesFilter = memo(function page({ setFilter, filter }) {
- diff --git a/src/app/(dashboard)/privilege/page.jsx b/src/app/(dashboard)/privilege/page.jsx index f7e7966..9871dcd 100644 --- a/src/app/(dashboard)/privilege/page.jsx +++ b/src/app/(dashboard)/privilege/page.jsx @@ -36,7 +36,7 @@ const Privilege = () => {

Liste des habilitations

- diff --git a/src/app/(dashboard)/role/CreateRoleForm.jsx b/src/app/(dashboard)/role/CreateRoleForm.jsx index 3ba4a85..2e5e4ce 100644 --- a/src/app/(dashboard)/role/CreateRoleForm.jsx +++ b/src/app/(dashboard)/role/CreateRoleForm.jsx @@ -2,13 +2,13 @@ import Loader from '@/components/Loader/Loader' import React, { useState, useRef, useEffect, useMemo } from 'react' import fetchRequest from '@/app/lib/fetchRequest' import { useNotification } from '@/context/NotificationContext' -import CancelIcon from "@/static/image/svg/cancel.svg" const CreateRoleForm = ({ appendRole, setIsOpen }) => { const [isLoading, setIsLoading] = useState(false) const [roleName, setRoleName] = useState("") const [privileges, setPrivileges] = useState(null) - const [selectedPrivileges, setSelectedPrivileges] = useState([]) + const [checkedValues, setCheckedValues] = useState([]); const { toggleNotification } = useNotification() + const [selectAll, setSelectAll] = useState(false); useEffect(() => { const getPrivileges = async () => { const { data, errors, isSuccess } = await fetchRequest("/privileges") @@ -35,12 +35,12 @@ const CreateRoleForm = ({ appendRole, setIsOpen }) => { setIsLoading(true) const { data, errors, isSuccess, status } = await fetchRequest("/roles/", { method: "POST", - body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.map((element) => element.id) }) + body: JSON.stringify({ name: roleName, privileges: checkedValues }) }) if (isSuccess) { setIsLoading(false) inputRef.current.value = "" - setSelectedPrivileges([]) + setCheckedValues([]) setRoleName("") appendRole(data) toggleNotification({ @@ -93,46 +93,63 @@ const CreateRoleForm = ({ appendRole, setIsOpen }) => { console.log(errors) } } - const handlePrivilegeClick = (privilege) => { - if (selectedPrivileges.find((element) => element.id === privilege.id)) { - setSelectedPrivileges(selectedPrivileges.filter((element) => element.id !== privilege.id)) + + + const handleCheckboxChange = (event) => { + const { value, checked } = event.target; + if (value === "selectAll") { + setSelectAll(checked); + if (checked) { + setCheckedValues(privileges.map(element => element.id)); + } else { + setCheckedValues([]); + } } else { - setSelectedPrivileges([...selectedPrivileges, privilege]) + const updatedValues = checked + ? [...checkedValues, value] + : checkedValues.filter(val => val != value); + + setCheckedValues(updatedValues); + setSelectAll(updatedValues.length === privileges.length); } - } - 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 (
-
- setIsOpen(false)} className="h-8 w-8 cursor-pointer absolute top-2 right-2 fill-neutral-600" /> - {(privileges) ?
-

Ajout de Rôle

-
- +
+ {(privileges) ? +

Ajouter rôle

+
+
-
-
- -
{!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

-
} +
+ + +
+
+ {privileges.length !== 0 + ? privileges?.map((privilege) => { + return
+ element == privilege.id) != undefined} type='checkbox' onChange={handleCheckboxChange} className='form-checkbox rounded-sm border-2 border-[#323232] focus:ring-0 focus:ring-transparent cursor-pointer h-4 w-4 text-[#323232]' name={"privilege_" + privilege.id} id={"privilege_" + privilege.id} /> + +
+ }) + :
+

Pas encore des habilitations

+
}
-
- +
:
} diff --git a/src/app/(dashboard)/role/RolesFilter.jsx b/src/app/(dashboard)/role/RolesFilter.jsx index 25fc2cd..59a3d7f 100644 --- a/src/app/(dashboard)/role/RolesFilter.jsx +++ b/src/app/(dashboard)/role/RolesFilter.jsx @@ -77,7 +77,7 @@ const RolesFilter = memo(function Page({ setFilter, filter }) {
- diff --git a/src/app/(dashboard)/role/UpdateRoleForm.jsx b/src/app/(dashboard)/role/UpdateRoleForm.jsx index 4b35ead..c52bd74 100644 --- a/src/app/(dashboard)/role/UpdateRoleForm.jsx +++ b/src/app/(dashboard)/role/UpdateRoleForm.jsx @@ -1,17 +1,19 @@ import { useNotification } from '@/context/NotificationContext' -import React, { useState, useRef, useEffect, useMemo } from 'react' +import React, { useState, useRef, useEffect } from 'react' import fetchRequest from '@/app/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 [checkedValues, setCheckedValues] = useState(isArray(rolePrivileges) ? rolePrivileges.map(element => element.id) : []); + const [selectAll, setSelectAll] = useState(false); const inputRef = useRef(null) - console.log("les priv de role ", rolePrivileges) + useEffect(() => { + setSelectAll(isArray(checkedValues) && privileges ? checkedValues.length === privileges.length : false) + }, [privileges]) useEffect(() => { const getPrivileges = async () => { const { data, errors, isSuccess } = await fetchRequest("/privileges") @@ -34,9 +36,8 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv setLoadingStatus(true) const { isSuccess, errors, data, status } = await fetchRequest(`/roles/${id}/`, { method: "PATCH", - body: JSON.stringify({ name: roleName, privileges: selectedPrivileges.filter((element) => privileges.find((prvElement) => prvElement.id === element.id)).map((element) => element.id) }) + body: JSON.stringify({ name: roleName, privileges: checkedValues }) }) - console.log(data) setLoadingStatus(false) if (isSuccess) { setRoles((roles) => roles.map((element) => element.id === id ? data : element)) @@ -92,46 +93,60 @@ const UpdateRoleForm = ({ setRoleToUpdate, setRoles, roles, privileges: rolePriv 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)) + const handleCheckboxChange = (event) => { + const { value, checked } = event.target; + if (value === "selectAll") { + setSelectAll(checked); + if (checked) { + setCheckedValues(privileges.map(element => element.id)); + } else { + setCheckedValues([]); + } } else { - setSelectedPrivileges([...selectedPrivileges, privilege]) + const updatedValues = checked + ? [...checkedValues, value] + : checkedValues.filter(val => val != value); + + setCheckedValues(updatedValues); + setSelectAll(updatedValues.length === privileges.length); } - } - 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" /> - {(privileges) ?
-

Modification de Rôle

-
+
+ {(privileges) ? +

Modifier rôle

+
-
-
- -
{!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

-
} +
+ + +
+
+ {privileges.length !== 0 + ? privileges?.map((privilege) => { + return
+ element == privilege.id) != undefined} type='checkbox' onChange={handleCheckboxChange} className='form-checkbox rounded-sm border-2 border-[#323232] focus:ring-0 focus:ring-transparent cursor-pointer h-4 w-4 text-[#323232]' name={"privilege_" + privilege.id} id={"privilege_" + privilege.id} /> + +
+ }) + :
+

Pas encore des habilitations

+
}
-
- +
:
} diff --git a/src/app/(dashboard)/role/page.jsx b/src/app/(dashboard)/role/page.jsx index ef605d2..36a3810 100644 --- a/src/app/(dashboard)/role/page.jsx +++ b/src/app/(dashboard)/role/page.jsx @@ -50,7 +50,7 @@ const Role = () => { {roleToUpdate && }

Liste des Rôles

- diff --git a/src/app/(dashboard)/user/CreateUserForm.jsx b/src/app/(dashboard)/user/CreateUserForm.jsx index d4089e8..a7d15dd 100644 --- a/src/app/(dashboard)/user/CreateUserForm.jsx +++ b/src/app/(dashboard)/user/CreateUserForm.jsx @@ -159,33 +159,33 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => { return (
- setIsOpen(false)} className="h-8 w-8 cursor-pointer md:absolute fixed top-2 right-2 fill-neutral-600" /> - {(roles && projects) ?
-

Ajout d'utilisateur

-
+ {(roles && projects) ? +

Ajouter utilisateur

+
- +

{errors.last_name}

- +

{errors.first_name}

-
- - +
+ +

{errors.email}

-
- +
+ +

-
+
- +
{projects.length !== 0 ? @@ -234,11 +234,12 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => { :

Pas encore des projets

} +

-
+
- +
{roles.length !== 0 ? @@ -258,9 +259,12 @@ const CreateUserForm = ({ setIsOpen, appendUser }) => {
}
-
- +
:
} diff --git a/src/app/(dashboard)/user/UpdateUserForm.jsx b/src/app/(dashboard)/user/UpdateUserForm.jsx index ff36c1e..54ce180 100644 --- a/src/app/(dashboard)/user/UpdateUserForm.jsx +++ b/src/app/(dashboard)/user/UpdateUserForm.jsx @@ -134,29 +134,28 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => { return (
- setUserToUpdate(null)} className="h-8 w-8 cursor-pointer md:absolute fixed top-2 right-2 fill-neutral-600" /> - {(roles && projects) ?
-

Modification d'utilisateur

-
+ {(roles && projects) ? +

Modifier d'utilisateur

+
- +

{errors.last_name}

- +

{errors.first_name}

-
- - +
+ +

{errors.email}

-
+
- +
{projects.length !== 0 ? @@ -206,11 +205,12 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => { :

Pas encore des projets

} +

-
+
- +
{roles.length !== 0 ? @@ -230,9 +230,12 @@ const UpdateUserForm = ({ setUserToUpdate, userToUpdate, setUsers }) => {
}
-
- +
:
} diff --git a/src/app/(dashboard)/user/UsersFilter.jsx b/src/app/(dashboard)/user/UsersFilter.jsx index ac52b93..c284fe7 100644 --- a/src/app/(dashboard)/user/UsersFilter.jsx +++ b/src/app/(dashboard)/user/UsersFilter.jsx @@ -31,7 +31,7 @@ const UsersFilter = memo(function page({ setFilter, filter }) {
- diff --git a/src/app/(dashboard)/user/page.jsx b/src/app/(dashboard)/user/page.jsx index 726ea0d..e1c84e4 100644 --- a/src/app/(dashboard)/user/page.jsx +++ b/src/app/(dashboard)/user/page.jsx @@ -62,7 +62,7 @@ const UserPage = () => { {userToUpdate && }

Liste des utilisateurs

- diff --git a/tailwind.config.js b/tailwind.config.js index bb1243c..e19d38d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -52,5 +52,5 @@ module.exports = { lg: '976px', xl: '1440px', }, - plugins: [require('@tailwindcss/aspect-ratio')], + plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/forms')({ strategy: "class" })], }; -- GitLab From ba33742b1a15bc2931e898740728ee1174fdfdf5 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Mon, 1 Jul 2024 15:21:57 +0100 Subject: [PATCH 03/14] update reservation UI --- .../ConsultationReservationFilters.jsx | 31 +++ .../consultation-reservations/ZoneUI.jsx | 5 +- .../consultation-reservations/page.jsx | 177 +++++++++--------- src/app/(dashboard)/privilege/page.jsx | 2 +- src/app/(dashboard)/reservation/PlaceUI.jsx | 54 ++++-- .../reservation/PresenceButton.jsx | 26 +-- src/app/(dashboard)/reservation/TableUI.jsx | 15 +- src/app/(dashboard)/reservation/ZoneUI.jsx | 11 +- src/app/(dashboard)/reservation/page.jsx | 137 ++++++++------ src/app/(dashboard)/role/page.jsx | 2 +- src/app/(dashboard)/user/page.jsx | 4 +- src/app/globals.css | 17 +- src/app/ui/ReservationConfirmation.jsx | 42 +++++ src/static/image/svg/PlaceRed.svg | 22 +++ src/static/image/svg/placeBlue.svg | 22 +++ src/static/image/svg/placeBooked.svg | 22 +++ src/static/image/svg/placePresent.svg | 22 +++ src/static/image/svg/placeUnavailable.svg | 22 +++ tailwind.config.js | 6 +- 19 files changed, 447 insertions(+), 192 deletions(-) create mode 100644 src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx create mode 100644 src/app/ui/ReservationConfirmation.jsx create mode 100644 src/static/image/svg/PlaceRed.svg create mode 100644 src/static/image/svg/placeBlue.svg create mode 100644 src/static/image/svg/placeBooked.svg create mode 100644 src/static/image/svg/placePresent.svg create mode 100644 src/static/image/svg/placeUnavailable.svg diff --git a/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx b/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx new file mode 100644 index 0000000..570cb61 --- /dev/null +++ b/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx @@ -0,0 +1,31 @@ +import React, { memo } from 'react' +import ResetIcon from "@/static/image/svg/reset.svg" +const ConsultationReservationFilters = memo(function page({ setFilter, filter }) { + const handleChangeFilter = (event) => { + const name = event.target.name + const value = event.target.value + setFilter((filter) => ({ ...filter, [name]: value })) + } + const handleResetFilters = () => { + setFilter({ privilege: "" }) + } + return ( +
+

Recherche

+
+
+ + +
+
+
+ +
+
+ ) +}) + +export default ConsultationReservationFilters \ No newline at end of file diff --git a/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx b/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx index 3ea1b5c..7d0b297 100644 --- a/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx +++ b/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx @@ -3,10 +3,9 @@ import TableUI from './TableUI' const ZoneUI = ({ id, tables, nom }) => { return ( -
-

Zone {nom}

+
- {tables.map((table) => { + {tables.map((table) => { return })}
diff --git a/src/app/(dashboard)/consultation-reservations/page.jsx b/src/app/(dashboard)/consultation-reservations/page.jsx index 253e302..8911f79 100644 --- a/src/app/(dashboard)/consultation-reservations/page.jsx +++ b/src/app/(dashboard)/consultation-reservations/page.jsx @@ -5,6 +5,7 @@ import ZoneUI from './ZoneUI' import fetchRequest from '@/app/lib/fetchRequest' import Loader from '@/components/Loader/Loader' import { useNotification } from '@/context/NotificationContext' +import SubSideBar from '@/app/ui/SubSideBar' export const ReservationContext = React.createContext() @@ -18,12 +19,12 @@ const Reservation = () => { const [currentDateData, setCurrentDateData] = useState(null) const [datesData, setDatesData] = useState(null) const [bookedPlaces, setBookedPlaces] = useState([]) - const [ etages, setEtages ] = useState([]) - const [ isLoadingEtages, setIsLoadingEtages ] = useState(true) - const [ selectedEtage, setSelectedEtage ] = useState(null) - const [ zones, setZones ] = useState([]) - const [ isLoadingZones, setIsLoadingZones ] = useState(true) - const [ selectedZone, setSelectedZone ] = useState(null) + const [etages, setEtages] = useState([]) + const [isLoadingEtages, setIsLoadingEtages] = useState(true) + const [selectedEtage, setSelectedEtage] = useState(null) + const [zones, setZones] = useState([]) + const [isLoadingZones, setIsLoadingZones] = useState(true) + const [selectedZone, setSelectedZone] = useState(null) useEffect(() => { const getPlan = async () => { @@ -54,6 +55,7 @@ const Reservation = () => { setIsLoadingEtages(false) if (isSuccess) { setEtages(data) + if (data && data.length) setSelectedEtage(data[0].id) } else { setEtages([]) } @@ -66,9 +68,9 @@ const Reservation = () => { }, []) useEffect(() => { - if(selectedEtage){ - const concernedEtage = floors.find((element) => element.id == selectedEtage) - setZones(concernedEtage.zones) + if (selectedEtage) { + const concernedEtage = floors.find((element) => element.id == selectedEtage) + setZones(concernedEtage?.zones || []) } }, [selectedEtage]) @@ -167,18 +169,18 @@ const Reservation = () => { } } - const handleChangeEtage = (event) =>{ + const handleChangeEtage = (event) => { const etage_id = event.target.value - if(selectedEtage !== etage_id){ + if (selectedEtage !== etage_id) { setSelectedEtage(etage_id) setSelectedZone(null) setSelectedZone(null) } } - const handleChangeZone = (event) =>{ + const handleChangeZone = (event) => { const zone_id = event.target.value - if(selectedZone !== zone_id){ + if (selectedZone !== zone_id) { setSelectedZone(zone_id) } } @@ -208,82 +210,87 @@ const Reservation = () => { const month = date.getMonth() + 1 return (month === currentMonth) && (date.getDate() >= new Date().getDate() && date.getDate() <= new Date().getDate() + 14) }) - + if (isLoadingEtages) return
+ +
return ( -
-
- {(filteredDatesData && filteredDatesData.length) && } - - + <> +
+ {etages.map((element, index) => { + if (element.id !== selectedEtage) + return
setSelectedEtage(element.id)} className='cursor-pointer px-3 h-full flex items-center justify-center'> +

Etage {element.numero}

+
+ return
setSelectedEtage(element.id)} className='cursor-pointer px-3 h-full relative flex items-center justify-center before:content-[""] before:absolute before:bottom-0 before:left-0 before:bg-sushi-500 before:w-full before:h-0.5'> +

Etage {element.numero}

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

Etage {floor.numero}

- {(selectedZone)? - floor.zones.filter((element) => - {if (concernedZones.includes(element.id) && element.id == selectedZone) {return element}} - ).map((zone, index) => { - return - }) - : - floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { +
+
+ {(filteredDatesData && filteredDatesData.length) && } + +
+ {(!isLoadingData) + ? <> +
+ + { + (selectedEtage) ? + floors.filter((element) => { if (concernedFloors.includes(element.id) && element.id == selectedEtage) { return element } } + ).map((floor) => { + return
+ {/*

Etage {floor.numero}

*/} + {(selectedZone) ? + floor.zones.filter((element) => { if (concernedZones.includes(element.id) && element.id == selectedZone) { return element } } + ).map((zone, index) => { + return + }) + : + floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { + return + }) + } +
+ }) + : + 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)).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 - &&
-

Vous êtes en télétravail aujourd'hui.

-
} -
+ })} +
+ }) + } + {floors.filter((element) => concernedFloors.includes(element.id)).length === 0 + &&
+

Vous êtes en télétravail aujourd'hui.

+
} + +
+ + :
+
- - :
- -
- } -
+ } +
+ + ) } diff --git a/src/app/(dashboard)/privilege/page.jsx b/src/app/(dashboard)/privilege/page.jsx index 9871dcd..e138621 100644 --- a/src/app/(dashboard)/privilege/page.jsx +++ b/src/app/(dashboard)/privilege/page.jsx @@ -33,7 +33,7 @@ const Privilege = () => { <> -
+

Liste des habilitations

} else if (geolocationError === "The request to get user location timed out.") { return

Autorisez la géolocalisation pour enregistrer votre présence

-
} else if (geolocationError === "Location information is unavailable.") { - return } else if (isInside) - return else return

Vous n'êtes pas géolocalisé à l'intérieur de Teamwill.

-
} diff --git a/src/app/(dashboard)/reservation/TableUI.jsx b/src/app/(dashboard)/reservation/TableUI.jsx index bf4e5e6..6ec02e0 100644 --- a/src/app/(dashboard)/reservation/TableUI.jsx +++ b/src/app/(dashboard)/reservation/TableUI.jsx @@ -28,16 +28,15 @@ const TableUI = ({ places }) => { if (!processedPlaces || processedPlaces.length === 0) return <> return ( -
+
{processedPlaces.map((element, index) => { - return
-
- + return
+
+ +
+
+ {(element.length > 1) && }
-
- {(element.length > 1) &&
- -
}
})}
diff --git a/src/app/(dashboard)/reservation/ZoneUI.jsx b/src/app/(dashboard)/reservation/ZoneUI.jsx index 42538fd..467b09f 100644 --- a/src/app/(dashboard)/reservation/ZoneUI.jsx +++ b/src/app/(dashboard)/reservation/ZoneUI.jsx @@ -3,13 +3,10 @@ import TableUI from './TableUI' const ZoneUI = ({ id, tables, nom }) => { return ( -
-

Zone {nom}

-
- {tables.map((table) => { - return - })} -
+
+ {tables.map((table) => { + return + })}
) } diff --git a/src/app/(dashboard)/reservation/page.jsx b/src/app/(dashboard)/reservation/page.jsx index e77c657..20c5e99 100644 --- a/src/app/(dashboard)/reservation/page.jsx +++ b/src/app/(dashboard)/reservation/page.jsx @@ -8,6 +8,7 @@ import { useNotification } from '@/context/NotificationContext' import PresenceButton from './PresenceButton' import Cookies from 'js-cookie'; import { decrypt } from '@/app/lib/session'; +import ReservationConfirmation from '@/app/ui/ReservationConfirmation' export const ReservationContext = React.createContext() @@ -26,6 +27,8 @@ const Reservation = () => { const [authenticatedUserData, setAuthenticatedUserData] = useState(null) + const [selectedZone, setSelectedZone] = useState({ floorId: null, zoneId: null }) + const cookie = Cookies.get("session") const getUserData = async () => { try { @@ -160,6 +163,8 @@ const Reservation = () => { const { isSuccess, errors, data } = await fetchRequest(`/zoaning/user-projects-zones-places/${date.week}/${date.day}`) if (isSuccess) { setProjectsData(data) + if (data && data.length && data[0].zones.length) setSelectedZone({ floorId: data[0].zones[0].id_etage.id, zoneId: data[0].zones[0].id }) + else setSelectedZone({ floorId: null, zoneId: null }) } else { console.log(errors) toggleNotification({ @@ -245,7 +250,6 @@ const Reservation = () => { 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) || [] const hasPlace = bookedPlaces?.find((p) => p.id_user === authenticatedUserData?.sessionData?.user_id) - const dateRef = useRef(null) useEffect(() => { if (dateRef.current && currentDateData?.date) { @@ -265,67 +269,84 @@ const Reservation = () => { const month = date.getMonth() + 1 return (month === currentMonth) && (date.getDate() >= new Date().getDate() && date.getDate() <= new Date().getDate() + 14) }) + const selectedZoneData = floors.find((element) => element.id == selectedZone.floorId)?.zones.find((zone) => zone.id === selectedZone.zoneId) return ( -
-
- {(filteredDatesData && filteredDatesData.length) && } -
-
-
-
-

Autres Projets

-
-
-
-

Disponible

-
-
-
-

Réservé par vous

-
-
-
-

Réservé par un collègue

+ <> +
+
+

Réservation place

+ {(filteredDatesData && filteredDatesData.length !== 0) && }
-
-
-

Présent

-
-
- {(!isLoadingData) - ? - - <> - {(floors.filter((element) => concernedFloors.includes(element.id))?.length > 0 && hasPlace && !hasPlace.presence && currentDate && currentDate === date?.date) &&
- -
} -
- {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 - })} + {(selectedZone.floorId && !isLoadingData) &&
+
+ {floors.filter((element) => concernedFloors.includes(element.id)).map((floor) => { + return floor.zones?.map((zone, index) => { + if (selectedZone.zoneId == zone.id && selectedZone.floorId == floor.id) return
setSelectedZone({ floorId: floor.id, zoneId: zone.id })} key={index} className='rounded-md not-first:-left-2 text-white relative whitespace-nowrap flex items-center justify-center border border-[#B2CEDE] text-xs md:text-sm lg:text-base px-5 lg:px-10 h-9 bg-[#B2CEDE]'> +

Etage {floor.numero} - Zone {zone.nom}

+
+ return
setSelectedZone({ floorId: floor.id, zoneId: zone.id })} className='cursor-pointer not-first:-left-2 hover:bg-neutral-100 duration-300 rounded-md text-[#8E8D8D] relative whitespace-nowrap border flex items-center justify-center border-[#D9D9D9] text-xs md:text-sm lg:text-base lg:px-10 px-5 h-9 bg-white'> +

Etage {floor.numero} - Zone {zone.nom}

- })} - {!isLoadingData && floors.filter((element) => concernedFloors.includes(element.id)).length === 0 - &&
-

Vous êtes en télétravail aujourd'hui.

-
} + }) + })} +
+
} +
+
+
+
+

Autres Projets

- - - :
- -
- } -
+
+
+

Disponible

+
+
+
+

Réservé par vous

+
+
+
+

Réservé par un collègue

+
+
+
+

Présent

+
+
+ {(!isLoadingData) + ? + + <> +
+ concernedFloors.includes(element.id))?.length === 0 || !hasPlace || hasPlace.presence || !currentDate || currentDate !== date?.date} isInside={isInside} geolocationError={geolocationError} processUserLocation={processUserLocation} /> +
+
+ {(selectedZoneData) && +
+ +
} + {(!isLoadingData && !selectedZoneData) + &&
+

Vous êtes en télétravail aujourd'hui.

+
} +
+ +
+ :
+ +
+ } +
+
+ + ) } diff --git a/src/app/(dashboard)/role/page.jsx b/src/app/(dashboard)/role/page.jsx index 36a3810..1980baa 100644 --- a/src/app/(dashboard)/role/page.jsx +++ b/src/app/(dashboard)/role/page.jsx @@ -45,7 +45,7 @@ const Role = () => { <> -
+
{openCreatePopup && } {roleToUpdate && }
diff --git a/src/app/(dashboard)/user/page.jsx b/src/app/(dashboard)/user/page.jsx index e1c84e4..0f2acb5 100644 --- a/src/app/(dashboard)/user/page.jsx +++ b/src/app/(dashboard)/user/page.jsx @@ -57,7 +57,7 @@ const UserPage = () => { <> -
+
{openCreatePopup && } {userToUpdate && }
@@ -89,7 +89,7 @@ const UserPage = () => {
} } -
+
{(paginationData) &&
{(paginationData.currentPage > 1) &&
getUsers(paginationData.currentPage - 1)} className='flex cursor-pointer hover:bg-neutral-200 duration-150 delay-75 h-8 w-9 items-center justify-center'> diff --git a/src/app/globals.css b/src/app/globals.css index d341d36..3d466fe 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -9,10 +9,9 @@ } } -/* Customize the scrollbar */ ::-webkit-scrollbar { width: 6px; - /* Set the width of the scrollbar */ + height: 6px; } .hideScroll::-webkit-scrollbar { @@ -213,4 +212,18 @@ .has-tooltip:hover .tooltip { @apply visible z-50; +} + + +.triangle { + width: 0; + height: 0; + border-left: 12px solid transparent; + border-right: 12px solid transparent; + border-bottom: 18px solid black; + transform: rotate(-90deg); +} + +.rotateProjectName { + transform: rotate(-90deg); } \ No newline at end of file diff --git a/src/app/ui/ReservationConfirmation.jsx b/src/app/ui/ReservationConfirmation.jsx new file mode 100644 index 0000000..41ad0a0 --- /dev/null +++ b/src/app/ui/ReservationConfirmation.jsx @@ -0,0 +1,42 @@ +import Loader from '@/components/Loader/Loader'; +import React from 'react' + +const ReservationConfirmation = ({ isOpen, onClose, onConfirm, isLoading, reservationData, type }) => { + if (!isOpen) return null; + if (type === "create") + return ( +
+
+
+

Confirmation de Réservation Place

+

Êtes-vous sûr de vouloir confirmer la réservation de la place {reservationData.placeName || ""} à la table {reservationData.tableName || ""} dans la zone {reservationData.zoneName || ""}

+

Merci de confirmer votre présence le {reservationData.date.split("-").reverse().join("/") || ""}

+
+ + +
+
+
+
+ ) + else if (type === "location") return
+
+
+

Alerte de Localisation Non Conforme

+

Votre localisation actuelle n'est pas conforme aux exigences.

+

Veuillez vérifier et mettre à jour votre position, puis reconfirmer votre présence.

+
+ +
+
+
+
+} + +export default ReservationConfirmation \ No newline at end of file diff --git a/src/static/image/svg/PlaceRed.svg b/src/static/image/svg/PlaceRed.svg new file mode 100644 index 0000000..81aad7f --- /dev/null +++ b/src/static/image/svg/PlaceRed.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/image/svg/placeBlue.svg b/src/static/image/svg/placeBlue.svg new file mode 100644 index 0000000..7694452 --- /dev/null +++ b/src/static/image/svg/placeBlue.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/image/svg/placeBooked.svg b/src/static/image/svg/placeBooked.svg new file mode 100644 index 0000000..b6aec6d --- /dev/null +++ b/src/static/image/svg/placeBooked.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/image/svg/placePresent.svg b/src/static/image/svg/placePresent.svg new file mode 100644 index 0000000..6b2f082 --- /dev/null +++ b/src/static/image/svg/placePresent.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/static/image/svg/placeUnavailable.svg b/src/static/image/svg/placeUnavailable.svg new file mode 100644 index 0000000..54b0475 --- /dev/null +++ b/src/static/image/svg/placeUnavailable.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/tailwind.config.js b/tailwind.config.js index e19d38d..ab534a7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -52,5 +52,9 @@ module.exports = { lg: '976px', xl: '1440px', }, - plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/forms')({ strategy: "class" })], + plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/forms')({ strategy: "class" }), + function ({ addVariant }) { + addVariant('not-first', '&:not(:first-child)'); + }, + ], }; -- GitLab From 9ae3dfc2ead2e1cd7071180b631459b325100b53 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Tue, 2 Jul 2024 11:49:16 +0100 Subject: [PATCH 04/14] change presence button logic --- .../ConsultationReservationFilters.jsx | 31 ---- .../consultation-reservations/PlaceUI.jsx | 6 +- .../consultation-reservations/TableUI.jsx | 4 +- .../consultation-reservations/ZoneUI.jsx | 5 +- .../consultation-reservations/page.jsx | 153 ++++++++--------- src/app/(dashboard)/reservation/PlaceUI.jsx | 8 +- .../reservation/PresenceButton.jsx | 159 +++++++++++------- src/app/(dashboard)/reservation/page.jsx | 94 +---------- src/app/ui/ReservationConfirmation.jsx | 56 ++++-- src/middleware.js | 8 +- src/static/image/svg/warning.svg | 3 + 11 files changed, 233 insertions(+), 294 deletions(-) delete mode 100644 src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx create mode 100644 src/static/image/svg/warning.svg diff --git a/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx b/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx deleted file mode 100644 index 570cb61..0000000 --- a/src/app/(dashboard)/consultation-reservations/ConsultationReservationFilters.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { memo } from 'react' -import ResetIcon from "@/static/image/svg/reset.svg" -const ConsultationReservationFilters = memo(function page({ setFilter, filter }) { - const handleChangeFilter = (event) => { - const name = event.target.name - const value = event.target.value - setFilter((filter) => ({ ...filter, [name]: value })) - } - const handleResetFilters = () => { - setFilter({ privilege: "" }) - } - return ( -
-

Recherche

-
-
- - -
-
-
- -
-
- ) -}) - -export default ConsultationReservationFilters \ No newline at end of file diff --git a/src/app/(dashboard)/consultation-reservations/PlaceUI.jsx b/src/app/(dashboard)/consultation-reservations/PlaceUI.jsx index 0f8bedf..36f08f2 100644 --- a/src/app/(dashboard)/consultation-reservations/PlaceUI.jsx +++ b/src/app/(dashboard)/consultation-reservations/PlaceUI.jsx @@ -14,7 +14,7 @@ const getColorForProject = (projectId) => { return colors[index]; }; -const PlaceUI = ({ id, isTop }) => { +const PlaceUI = ({ id, isTop }) => { const { allPlaces, bookedPlaces } = useContext(ReservationContext); const [showTooltip, setShowTooltip] = useState(false); const place = allPlaces?.find((place) => place.id === id); @@ -25,7 +25,7 @@ const PlaceUI = ({ id, isTop }) => { return (
setShowTooltip(true)} onMouseLeave={() => setShowTooltip(false)} @@ -49,7 +49,7 @@ const PlaceUI = ({ id, isTop }) => { ); } else { return ( -
+
); } }; diff --git a/src/app/(dashboard)/consultation-reservations/TableUI.jsx b/src/app/(dashboard)/consultation-reservations/TableUI.jsx index ce702a2..ac8ac87 100644 --- a/src/app/(dashboard)/consultation-reservations/TableUI.jsx +++ b/src/app/(dashboard)/consultation-reservations/TableUI.jsx @@ -1,7 +1,7 @@ import React from 'react' import PlaceUI from './PlaceUI'; -const TableUI = ({id, numero, places}) => { +const TableUI = ({ id, numero, places }) => { function groupConsecutive(arr) { arr = arr.sort((a, b) => a.id - b.id) @@ -49,4 +49,4 @@ const TableUI = ({id, numero, places}) => { ) } -export default TableUI \ No newline at end of file +export default TableUI diff --git a/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx b/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx index 7d0b297..7ecd851 100644 --- a/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx +++ b/src/app/(dashboard)/consultation-reservations/ZoneUI.jsx @@ -3,7 +3,8 @@ import TableUI from './TableUI' const ZoneUI = ({ id, tables, nom }) => { return ( -
+
+

Zone {nom}

{tables.map((table) => { return @@ -13,4 +14,4 @@ const ZoneUI = ({ id, tables, nom }) => { ) } -export default ZoneUI \ No newline at end of file +export default ZoneUI diff --git a/src/app/(dashboard)/consultation-reservations/page.jsx b/src/app/(dashboard)/consultation-reservations/page.jsx index 8911f79..e8777cb 100644 --- a/src/app/(dashboard)/consultation-reservations/page.jsx +++ b/src/app/(dashboard)/consultation-reservations/page.jsx @@ -5,7 +5,6 @@ import ZoneUI from './ZoneUI' import fetchRequest from '@/app/lib/fetchRequest' import Loader from '@/components/Loader/Loader' import { useNotification } from '@/context/NotificationContext' -import SubSideBar from '@/app/ui/SubSideBar' export const ReservationContext = React.createContext() @@ -55,7 +54,6 @@ const Reservation = () => { setIsLoadingEtages(false) if (isSuccess) { setEtages(data) - if (data && data.length) setSelectedEtage(data[0].id) } else { setEtages([]) } @@ -70,7 +68,7 @@ const Reservation = () => { useEffect(() => { if (selectedEtage) { const concernedEtage = floors.find((element) => element.id == selectedEtage) - setZones(concernedEtage?.zones || []) + setZones(concernedEtage.zones) } }, [selectedEtage]) @@ -210,88 +208,81 @@ const Reservation = () => { const month = date.getMonth() + 1 return (month === currentMonth) && (date.getDate() >= new Date().getDate() && date.getDate() <= new Date().getDate() + 14) }) - if (isLoadingEtages) return
- -
+ return ( - <> -
- {etages.map((element, index) => { - if (element.id !== selectedEtage) - return
setSelectedEtage(element.id)} className='cursor-pointer px-3 h-full flex items-center justify-center'> -

Etage {element.numero}

-
- return
setSelectedEtage(element.id)} className='cursor-pointer px-3 h-full relative flex items-center justify-center before:content-[""] before:absolute before:bottom-0 before:left-0 before:bg-sushi-500 before:w-full before:h-0.5'> -

Etage {element.numero}

-
- })} +
+
+ {(filteredDatesData && filteredDatesData.length) && } + +
-
-
- {(filteredDatesData && filteredDatesData.length) && } - -
- {(!isLoadingData) - ? <> -
- - { - (selectedEtage) ? - floors.filter((element) => { if (concernedFloors.includes(element.id) && element.id == selectedEtage) { return element } } - ).map((floor) => { - return
- {/*

Etage {floor.numero}

*/} - {(selectedZone) ? - floor.zones.filter((element) => { if (concernedZones.includes(element.id) && element.id == selectedZone) { return element } } - ).map((zone, index) => { - return - }) - : - floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { - return - }) - } -
- }) - : - floors.filter((element) => concernedFloors.includes(element.id)).map((floor) => { - return
-

Etage {floor.numero}

- {floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { + {(!isLoadingData) + ? <> +
+ + { + (selectedEtage) ? + floors.filter((element) => { if (concernedFloors.includes(element.id) && element.id == selectedEtage) { return element } } + ).map((floor) => { + return
+

Etage {floor.numero}

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

Vous êtes en télétravail aujourd'hui.

-
} -
-
- - :
- + }) + : + floor.zones.filter((element) => concernedZones.includes(element.id)).map((zone, index) => { + return + }) + } +
+ }) + : + 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 + &&
+

Vous êtes en télétravail aujourd'hui.

+
} +
- } -
- - + + :
+ +
+ } +
) } -export default Reservation \ No newline at end of file +export default Reservation diff --git a/src/app/(dashboard)/reservation/PlaceUI.jsx b/src/app/(dashboard)/reservation/PlaceUI.jsx index 1f91e58..69ea5d7 100644 --- a/src/app/(dashboard)/reservation/PlaceUI.jsx +++ b/src/app/(dashboard)/reservation/PlaceUI.jsx @@ -3,7 +3,6 @@ import React, { useContext, useState } from 'react' import { ReservationContext } from './page' import fetchRequest from '@/app/lib/fetchRequest' import { useNotification } from '@/context/NotificationContext' -import ConfirmationModal from '@/app/ui/ConfirmationModal' import PlaceUnavailableIcon from "@/static/image/svg/placeUnavailable.svg" import PlaceRedIcon from "@/static/image/svg/placeRed.svg" import PlacePresent from "@/static/image/svg/placePresent.svg" @@ -16,7 +15,7 @@ const PlaceUI = ({ id, rotate, numero, id_table: table }) => { const [isOpenBooking, setIsOpenBooking] = useState(false) const [isOpenCanceling, setIsOpenCanceling] = useState(false) const [isLoadingBooking, setIsLoadingBooking] = useState(false) - + const [isLoadingCanceling, setIsLoadingCanceling] = useState(false) const place = allPlaces?.find((place) => place.id === id) const bookedPlace = bookedPlaces?.find((bookedPlace) => bookedPlace.id_place === id) const handleBooking = (event) => { @@ -69,11 +68,12 @@ const PlaceUI = ({ id, rotate, numero, id_table: table }) => { } } const handleCancelingConfirmation = async (idReservation) => { + setIsLoadingCanceling(true) const { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/reservations/${idReservation}`, { method: "DELETE" }); + setIsLoadingCanceling(false) if (isSuccess) { - console.log(data); setBookedPlaces(bookedPlaces.filter((element) => element.id !== idReservation)) toggleNotification({ visible: true, @@ -126,7 +126,7 @@ const PlaceUI = ({ id, rotate, numero, id_table: table }) => {
- handleCancelingConfirmation(bookedPlace.id)} onClose={closeCancelingPopup} /> + handleCancelingConfirmation(bookedPlace.id)} onClose={closeCancelingPopup} />
else return
diff --git a/src/app/(dashboard)/reservation/PresenceButton.jsx b/src/app/(dashboard)/reservation/PresenceButton.jsx index c17d9fa..45683e3 100644 --- a/src/app/(dashboard)/reservation/PresenceButton.jsx +++ b/src/app/(dashboard)/reservation/PresenceButton.jsx @@ -4,26 +4,107 @@ import Loader from '@/components/Loader/Loader' import { useNotification } from '@/context/NotificationContext' import React, { useContext, useEffect, useState } from 'react' import { ReservationContext } from './page' +import ReservationConfirmation from '@/app/ui/ReservationConfirmation' -const PresenceButton = ({ processUserLocation, geolocationError, isInside, isDisabled, isConfirmed }) => { +const PresenceButton = ({ isDisabled, isConfirmed }) => { const { toggleNotification } = useNotification() const [isLoading, setIsLoading] = useState(false) const { setBookedPlaces, hasPlace } = useContext(ReservationContext) + const [confirmationPopup, setConfirmationPopup] = useState({ isOpen: false, message: "", type: "", title: "" }) + + const processUserLocation = () => { + const companyLatitude = 36.8402141; // Example: Company's latitude + const companyLongitude = 10.2432573; // Example: Company's longitude + const allowedRadius = 300; // Example: Radius in meters + + function getDistanceFromLatLonInMeters(lat1, lon1, lat2, lon2) { + const R = 6371000; // Radius of the Earth in meters + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLon = (lon2 - lon1) * Math.PI / 180; + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c; // Distance in meters + return distance; + } + + return new Promise((resolve) => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + function (position) { + const userLatitude = position.coords.latitude; + const userLongitude = position.coords.longitude; + + const distance = getDistanceFromLatLonInMeters( + userLatitude, + userLongitude, + companyLatitude, + companyLongitude + ); + + const isInside = distance <= allowedRadius; + resolve({ isInside, geolocationError: null }); + }, + function (error) { + let geolocationError; + switch (error.code) { + case error.PERMISSION_DENIED: + geolocationError = "Vous avez refusé la demande de géolocalisation."; + break; + case error.POSITION_UNAVAILABLE: + geolocationError = "Les informations de localisation sont indisponibles."; + break; + case error.TIMEOUT: + geolocationError = "La demande de localisation a expiré."; + break; + case error.UNKNOWN_ERROR: + geolocationError = "Une erreur inconnue est survenue."; + break; + } + resolve({ isInside: false, geolocationError }); + } + ); + } else { + resolve({ + isInside: false, + geolocationError: "La géolocalisation n'est pas supportée par ce navigateur." + }); + } + }); + }; + + useEffect(() => { + navigator.permissions.query({ name: 'geolocation' }).then((permissionStatus) => { + }).catch((error) => { + console.error('Failed to query geolocation permission:', error); + }); + return () => { + navigator.permissions.query({ name: 'geolocation' }).then((permissionStatus) => { + permissionStatus.onchange = null; + }); + }; + }, []) + const handlePresenceSave = async () => { - if (hasPlace) { - setIsLoading(true) + setIsLoading(true) + const userLocation = await processUserLocation() + if (userLocation.geolocationError) { + setIsLoading(false) + setConfirmationPopup({ type: "location", message: userLocation.geolocationError, isOpen: true, title: "Alerte de Localisation" }) + } else if (!userLocation.isInside) { + setIsLoading(false) + setConfirmationPopup({ type: "location", message: "Votre localisation actuelle n'est pas conforme aux exigences.", title: "Alerte de Localisation Non Conforme", isOpen: true }) + } + else if (hasPlace) { const { isSuccess, errors, data, status } = await fetchRequest(`/zoaning/reservations/${hasPlace.id}/`, { method: "PATCH", body: JSON.stringify({ presence: true }) }) setIsLoading(false) if (isSuccess) { - toggleNotification({ - visible: true, - message: "Votre présence a été enregistrée avec succès.", - type: "success" - }) - console.log(data) + setConfirmationPopup({ type: "presence" }) setBookedPlaces((elements) => elements.map((element) => element.id === data.data?.id ? data.data : element)) } else { @@ -43,62 +124,12 @@ const PresenceButton = ({ processUserLocation, geolocationError, isInside, isDis } } } - useEffect(() => { - if ("User denied the request for Geolocation." === geolocationError) { - toggleNotification({ - visible: true, - message: "Vous devez activer la géolocalisation pour enregistrer votre présence.", - type: "warning" - }) - } - else if (geolocationError === "The request to get user location timed out.") { - toggleNotification({ - visible: true, - message: "Vous devez activer la géolocalisation pour enregistrer votre présence.", - type: "warning" - }) - } - else if (geolocationError === "Location information is unavailable.") { - toggleNotification({ - visible: true, - message: "Erreur de géolocation", - type: "error" - }) - } - }, [geolocationError]) - - - if ("User denied the request for Geolocation." === geolocationError) { - return
-

Autorisez la géolocalisation pour enregistrer votre présence

- -
- } - else if (geolocationError === "The request to get user location timed out.") { - return
-

Autorisez la géolocalisation pour enregistrer votre présence

- -
- } - else if (geolocationError === "Location information is unavailable.") { - return - } - else if (isInside) - return - else return
-

Vous n'êtes pas géolocalisé à l'intérieur de Teamwill.

- -
+ setConfirmationPopup({ isOpen: false, message: "", type: "", title: "" })} {...confirmationPopup} /> + } diff --git a/src/app/(dashboard)/reservation/page.jsx b/src/app/(dashboard)/reservation/page.jsx index 20c5e99..c13bfa8 100644 --- a/src/app/(dashboard)/reservation/page.jsx +++ b/src/app/(dashboard)/reservation/page.jsx @@ -42,97 +42,6 @@ const Reservation = () => { } useMemo(getUserData, [cookie]) - const processUserLocation = () => { - const companyLatitude = 36.8402141; // Example: Company's latitude - const companyLongitude = 10.2432573; // Example: Company's longitude - const allowedRadius = 300; // Example: Radius in meters - - - function getDistanceFromLatLonInMeters(lat1, lon1, lat2, lon2) { - const R = 6371000; // Radius of the Earth in meters - const dLat = (lat2 - lat1) * Math.PI / 180; - const dLon = (lon2 - lon1) * Math.PI / 180; - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * - Math.sin(dLon / 2) * Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - const distance = R * c; // Distance in meters - return distance; - } - - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - function (position) { - const userLatitude = position.coords.latitude; - const userLongitude = position.coords.longitude; - - const distance = getDistanceFromLatLonInMeters( - userLatitude, - userLongitude, - companyLatitude, - companyLongitude - ); - - if (distance <= allowedRadius) { - setIsInside(true) - } else { - setIsInside(false) - } - setGeolocationError(null) - }, - function (error) { - switch (error.code) { - case error.PERMISSION_DENIED: - setGeolocationError("User denied the request for Geolocation."); - break; - case error.POSITION_UNAVAILABLE: - setGeolocationError("Location information is unavailable."); - break; - case error.TIMEOUT: - setGeolocationError("The request to get user location timed out."); - break; - case error.UNKNOWN_ERROR: - setGeolocationError("An unknown error occurred."); - break; - } - } - ); - } else { - toggleNotification({ - message: "La géolocalisation n'est pas supporté par votre navigateur.", - type: "warning", - visible: true - }) - } - } - - - useEffect(() => { - const handlePermissionChange = (permissionStatus) => { - if (permissionStatus.state === 'granted' || permissionStatus.state === 'prompt') { - processUserLocation(); - } else { - setGeolocationError("User denied the request for Geolocation."); - } - }; - - navigator.permissions.query({ name: 'geolocation' }).then((permissionStatus) => { - handlePermissionChange(permissionStatus); - - permissionStatus.onchange = () => { - handlePermissionChange(permissionStatus); - }; - }).catch((error) => { - console.error('Failed to query geolocation permission:', error); - }); - return () => { - navigator.permissions.query({ name: 'geolocation' }).then((permissionStatus) => { - permissionStatus.onchange = null; - }); - }; - }, []) - useEffect(() => { const getPlan = async () => { try { @@ -257,6 +166,7 @@ const Reservation = () => { setDate({ day: currentDateData.day, week: currentDateData.weekMonthly, date: currentDateData.date }) } }, [dateRef.current, currentDateData?.date]) + if (isLoadingSelectsData) return
@@ -325,7 +235,7 @@ const Reservation = () => { <>
- concernedFloors.includes(element.id))?.length === 0 || !hasPlace || hasPlace.presence || !currentDate || currentDate !== date?.date} isInside={isInside} geolocationError={geolocationError} processUserLocation={processUserLocation} /> + concernedFloors.includes(element.id))?.length === 0 || !hasPlace || hasPlace.presence || !currentDate || currentDate !== date?.date} />
{(selectedZoneData) && diff --git a/src/app/ui/ReservationConfirmation.jsx b/src/app/ui/ReservationConfirmation.jsx index 41ad0a0..7ec4532 100644 --- a/src/app/ui/ReservationConfirmation.jsx +++ b/src/app/ui/ReservationConfirmation.jsx @@ -1,15 +1,15 @@ import Loader from '@/components/Loader/Loader'; import React from 'react' - -const ReservationConfirmation = ({ isOpen, onClose, onConfirm, isLoading, reservationData, type }) => { +import WarningIcon from "@/static/image/svg/warning.svg" +const ReservationConfirmation = ({ isOpen, onClose, onConfirm, isLoading, reservationData, type, message, title }) => { if (!isOpen) return null; if (type === "create") return (
-
-

Confirmation de Réservation Place

-

Êtes-vous sûr de vouloir confirmer la réservation de la place {reservationData.placeName || ""} à la table {reservationData.tableName || ""} dans la zone {reservationData.zoneName || ""}

+
+

Confirmation de Réservation Place

+

Êtes-vous sûr de vouloir confirmer la réservation de la place {reservationData.placeName || ""} à la table {reservationData.tableName || ""} dans la zone {reservationData.zoneName || ""}

Merci de confirmer votre présence le {reservationData.date.split("-").reverse().join("/") || ""}

) + else if (type === "cancel") return ( +
+
+
+

Confirmation d'annulation de Réservation

+

Êtes-vous sûr de vouloir annuler la réservation de la place {reservationData.placeName || ""} à la table {reservationData.tableName || ""} dans la zone {reservationData.zoneName || ""}

+
+ + +
+
+
+
) else if (type === "location") return
-
-

Alerte de Localisation Non Conforme

-

Votre localisation actuelle n'est pas conforme aux exigences.

-

Veuillez vérifier et mettre à jour votre position, puis reconfirmer votre présence.

+
+
+ +

{title}

+
+

{message}

+

Veuillez vérifier et mettre à jour votre position, puis reconfirmer votre présence.

-
+ else if (type === "presence") return ( +
+
+
+

Validation de votre présence

+

Votre présence est validée avec succès.

+
+ +
+
+
+
) } export default ReservationConfirmation \ No newline at end of file diff --git a/src/middleware.js b/src/middleware.js index 98b3441..c746b76 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -28,10 +28,10 @@ export default async function middleware(req) { const hasPrivileges = userPrivileges.some(privilege => path.startsWith(`/${privilege}`)); console.log('hasPrivileges', hasPrivileges) - if (isProtectedRoute && !hasPrivileges) { - console.log('.') - return NextResponse.redirect(new URL('/no-access', req.nextUrl)); - } + // if (isProtectedRoute && !hasPrivileges) { + // console.log('.') + // return NextResponse.redirect(new URL('/no-access', req.nextUrl)); + // } // 7. Redirect to /dashboard if the user is authenticated if ( diff --git a/src/static/image/svg/warning.svg b/src/static/image/svg/warning.svg new file mode 100644 index 0000000..1aefbbd --- /dev/null +++ b/src/static/image/svg/warning.svg @@ -0,0 +1,3 @@ + + + -- GitLab From 5fef6083a7be373dd5a1eb4665eb013ff49e7178 Mon Sep 17 00:00:00 2001 From: Baligh ZOGHLAMI Date: Tue, 2 Jul 2024 17:38:19 +0100 Subject: [PATCH 05/14] update side bar UI --- src/app/(dashboard)/layout.jsx | 4 +- src/app/(dashboard)/privilege/page.jsx | 3 - src/app/(dashboard)/reservation/PlaceUI.jsx | 34 ++-- src/app/(dashboard)/reservation/TableUI.jsx | 8 +- src/app/(dashboard)/role/page.jsx | 3 - src/app/(dashboard)/user/page.jsx | 3 - src/app/ui/SideBar.jsx | 160 ++++++++++-------- src/app/ui/SideBarLink.jsx | 7 +- src/app/ui/SubSideBar.jsx | 33 +++- src/static/image/svg/Icone_acceuil_gris.svg | 3 + .../image/svg/Icone_administration_gris.svg | 3 + .../image/svg/Icone_consultation_gris.svg | 3 + src/static/image/svg/Icone_parametre_gris.svg | 3 + src/static/image/svg/Vector.svg | 3 + 14 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 src/static/image/svg/Icone_acceuil_gris.svg create mode 100644 src/static/image/svg/Icone_administration_gris.svg create mode 100644 src/static/image/svg/Icone_consultation_gris.svg create mode 100644 src/static/image/svg/Icone_parametre_gris.svg create mode 100644 src/static/image/svg/Vector.svg diff --git a/src/app/(dashboard)/layout.jsx b/src/app/(dashboard)/layout.jsx index a0a0e46..9d000d4 100644 --- a/src/app/(dashboard)/layout.jsx +++ b/src/app/(dashboard)/layout.jsx @@ -2,14 +2,16 @@ import React from 'react' import SideBar from '../ui/SideBar' import Header from '../ui/Header' import Footer from '../ui/Footer' +import SubSideBar from '../ui/SubSideBar' const layout = ({ children }) => { return ( <>
-
+ + {children}
diff --git a/src/app/(dashboard)/privilege/page.jsx b/src/app/(dashboard)/privilege/page.jsx index e138621..eccc3d8 100644 --- a/src/app/(dashboard)/privilege/page.jsx +++ b/src/app/(dashboard)/privilege/page.jsx @@ -6,7 +6,6 @@ import { isArray } from '../../lib/TypesHelper' import PrivilegeTableRows from './PrivilegeTableRow' import Loader from '@/components/Loader/Loader' import PrivilegesFilter from "./PrivilegesFilter" -import SubSideBar from '@/app/ui/SubSideBar' import AddIcon from "@/static/image/svg/add.svg" const Privilege = () => { const [privileges, setPrivileges] = useState([]) @@ -28,10 +27,8 @@ const Privilege = () => { const appendPrivilege = (privilege) => { setPrivileges((data) => [privilege, ...data]) } - const subLinks = [{ label: "Utilisateurs", link: "/user" }, { label: "Rôles", link: "/role" }, { label: "Habilitations", link: "/privilege" }, { label: "Projets", link: "/projects" }] return ( <> -
diff --git a/src/app/(dashboard)/reservation/PlaceUI.jsx b/src/app/(dashboard)/reservation/PlaceUI.jsx index 69ea5d7..d38a108 100644 --- a/src/app/(dashboard)/reservation/PlaceUI.jsx +++ b/src/app/(dashboard)/reservation/PlaceUI.jsx @@ -113,37 +113,41 @@ const PlaceUI = ({ id, rotate, numero, id_table: table }) => { if (place) if (bookedPlace) if (bookedPlace.id_user === authenticatedUserData.sessionData?.user_id) - if (bookedPlace.presence) return
+ if (bookedPlace.presence) return

{place.project_name || ""}

- else return
-
-

{place.project_name || ""}

+ else return <> +
+
+

{place.project_name || ""}

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

{place.project_name || ""}

- else return
-
-

{place.project_name || ""}

+ else return <> +
+
+

{place.project_name || ""}

+
+
+
-
- -
- else return
+ + else return
diff --git a/src/app/(dashboard)/reservation/TableUI.jsx b/src/app/(dashboard)/reservation/TableUI.jsx index 6ec02e0..8a12657 100644 --- a/src/app/(dashboard)/reservation/TableUI.jsx +++ b/src/app/(dashboard)/reservation/TableUI.jsx @@ -31,12 +31,8 @@ const TableUI = ({ places }) => {
{processedPlaces.map((element, index) => { return
-
- -
-
- {(element.length > 1) && } -
+ + {(element.length > 1) && }
})}
diff --git a/src/app/(dashboard)/role/page.jsx b/src/app/(dashboard)/role/page.jsx index 1980baa..9251cac 100644 --- a/src/app/(dashboard)/role/page.jsx +++ b/src/app/(dashboard)/role/page.jsx @@ -8,7 +8,6 @@ import { isArray } from '../../lib/TypesHelper' import AddIcon from "@/static/image/svg/add.svg" import UpdateRoleForm from './UpdateRoleForm' import { useNotification } from '@/context/NotificationContext' -import SubSideBar from '@/app/ui/SubSideBar' import RolesFilter from "./RolesFilter" const Role = () => { const [roles, setRoles] = useState([]) @@ -40,10 +39,8 @@ const Role = () => { const appendRole = (newRole) => { setRoles([newRole, ...roles]) } - const subLinks = [{ label: "Utilisateurs", link: "/user" }, { label: "Rôles", link: "/role" }, { label: "Habilitations", link: "/privilege" }, { label: "Projets", link: "/projects" }] return ( <> -
{openCreatePopup && } diff --git a/src/app/(dashboard)/user/page.jsx b/src/app/(dashboard)/user/page.jsx index 0f2acb5..8380c85 100644 --- a/src/app/(dashboard)/user/page.jsx +++ b/src/app/(dashboard)/user/page.jsx @@ -11,7 +11,6 @@ import UserTableRow from './UserTableRow'; import { PAGINATION_SIZE } from '../../lib/constants'; import ArrowRightIcon from "@/static/image/svg/chevron-right.svg" import ArrowLeftIcon from "@/static/image/svg/chevron-left.svg" -import SubSideBar from '@/app/ui/SubSideBar'; import UsersFilter from './UsersFilter'; const UserPage = () => { const [users, setUsers] = useState([]) @@ -52,10 +51,8 @@ const UserPage = () => { setUsers([newUser, ...users]) } - const subLinks = [{ label: "Utilisateurs", link: "/user" }, { label: "Rôles", link: "/role" }, { label: "Habilitations", link: "/privilege" }, { label: "Projets", link: "/projects" }] return ( <> -
{openCreatePopup && } diff --git a/src/app/ui/SideBar.jsx b/src/app/ui/SideBar.jsx index 9534806..2ae44f2 100644 --- a/src/app/ui/SideBar.jsx +++ b/src/app/ui/SideBar.jsx @@ -1,12 +1,38 @@ "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"; import './SideBar.css'; +import Link from "next/link"; +import Icone_consultation_gris from "@/static/image/svg/Icone_consultation_gris.svg" +import Icone_administration_gris from "@/static/image/svg/Icone_administration_gris.svg" +import Icone_parametre_gris from "@/static/image/svg/Icone_parametre_gris.svg" +import Icone_acceuil_gris from "@/static/image/svg/Vector.svg" + + + + +export const AdminLinks = { + "Gestion": [ + { label: "Utilisateurs", link: "/user", privilege: "user" }, + { label: "Rôles", link: "/role", privilege: "role" }, + { label: "Habilitations", link: "/privilege", privilege: "privilege" }, + { label: "Projets", link: "/projects", privilege: "projects" }, + ], + "Planning": [ + { label: "Planning", link: "/planning", privilege: "planning" }, + ], + "Zones": [ + { label: "Etage", link: "/etage", privilege: "etage" }, + { label: "Zone", link: "/zone", privilege: "zone" }, + { label: "Tables", link: "/table", privilege: "table" }, + { label: "Places", link: "/place", privilege: "place" }, + { label: "Affectation projects", link: "/assign-zone-project", privilege: "assign-zone-project" }, + ] +} + const SideBar = () => { const [isAuth, setIsAuth] = useState(false); @@ -23,6 +49,14 @@ const SideBar = () => { checkAuth(); }, []); + const AdministrationLinks = { ...AdminLinks } + + Object.keys(AdministrationLinks).forEach((key) => { + const newArray = AdministrationLinks[key].filter((link) => sessionData?.privileges.includes(link.privilege)) + if (!newArray.length) delete AdministrationLinks[key] + else AdministrationLinks[key] = newArray + }); + if (!isAuth || !sessionData) { return ( ); } - const AdminLinks = [ - { label: "Utilisateurs", link: "/user", privilege: "user" }, - { label: "Habilitations", link: "/privilege", privilege: "privilege" }, - { label: "Rôles", link: "/role", privilege: "role" }, - { label: "Projets", link: "/projects", privilege: "projects" }, - { label: "Réservation", link: "/reservation", privilege: "reservation" }, - { label: "Type de Presence", link: "/planning/type-presence", privilege: "planning/type-presence" }, - { label: "Planning", link: "/planning", privilege: "planning" }, - { label: "Etage", link: "/etage", privilege: "etage" }, - { label: "Zone", link: "/zone", privilege: "zone" }, - { label: "Tables", link: "/table", privilege: "table" }, - { label: "Places", link: "/place", privilege: "place" }, - { label: "Gestion des zones", link: "/assign-zone-project", privilege: "assign-zone-project" }, - { label: "Consulter les réservations", link: "/consultation-reservations", privilege: "consultation-reservations" }, - { label: "Reporting", link: "/reporting", privilege: "reporting" }, - ]; - - 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", - - } - ] - + const adminSectionOpen = Object.values(AdministrationLinks).flatMap((element) => element).find((element) => element.link === pathname) !== undefined return ( +
); } export default SideBar; \ No newline at end of file diff --git a/src/app/ui/SideBarLink.jsx b/src/app/ui/SideBarLink.jsx index 387cd82..cf7e0ab 100644 --- a/src/app/ui/SideBarLink.jsx +++ b/src/app/ui/SideBarLink.jsx @@ -17,14 +17,13 @@ const SideBarLink = ({ link, label }) => { }; const isActive = pathname === link; - return (
-
+