From 883b9b6c4b4163869248db0250ed121385b823c0 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Mon, 15 Dec 2025 16:31:41 +0100 Subject: [PATCH 1/9] dynamic display of notifications --- src/components/Header/Header.jsx | 8 +++++--- src/utils/getUserNotifications.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 03a1161..b8f3860 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -2,23 +2,25 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; import { useEffect, useRef, useState } from "react"; import getUserNotifications from "../../utils/getUserNotifications.js"; +import getUser from "../../utils/getUser.js"; function Header() { const [notificationMenu, setnotificationMenu] = useState(false); const [unreadNotification, setunreadNotification] = useState(true); const [mobileMenu, setMobileMenu] = useState(false); - const [notifications, setNotifications] = useState([{"content" : "ok"}, {"content" : "pourquoi pas antoine"}]); + const [notifications, setNotifications] = useState([]); const notificationRef = useRef(null); useEffect(() => { async function loadNotifications() { - const notifData = await getUserNotifications(); + const user = await getUser(); + const notifData = await getUserNotifications(user.id); setNotifications(notifData || []); } - //loadNotifications(); + loadNotifications(); const handleClickOutside = (event) => { if ( notificationRef.current && diff --git a/src/utils/getUserNotifications.js b/src/utils/getUserNotifications.js index 1b93395..8f750ee 100644 --- a/src/utils/getUserNotifications.js +++ b/src/utils/getUserNotifications.js @@ -1,7 +1,7 @@ -export default async function getUserNotifications() { +export default async function getUserNotifications(id) { try { const response = await fetch( - 'http://localhost:80/api/users/1/notifications', + `http://${import.meta.env.VITE_API_URL}/api/users/${id}/notifications`, { method: 'GET', credentials: 'include', From 0aaafed26ae4fb467a2a536acec7d703a3631210 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Wed, 17 Dec 2025 14:16:08 +0100 Subject: [PATCH 2/9] move utils files --- src/components/Header/Header.jsx | 27 +++++++++++++++++-- src/components/SearchBar/SearchBar.jsx | 4 +-- src/components/Task/Task.jsx | 2 +- .../createEventBtn/CreateEventBtn.jsx | 2 +- src/pages/Events/EventsPage.jsx | 2 +- src/utils/{event => events}/createEvent.js | 0 src/utils/{event => events}/getAllEvents.js | 0 src/utils/{event => events}/getEventById.js | 0 src/utils/{ => events}/searchEvents.js | 0 src/utils/filter.js | 4 +-- .../notifications/deleteNotificationUser.js | 26 ++++++++++++++++++ .../getUserNotifications.js | 0 src/utils/{ => users}/searchUsers.js | 0 13 files changed, 58 insertions(+), 9 deletions(-) rename src/utils/{event => events}/createEvent.js (100%) rename src/utils/{event => events}/getAllEvents.js (100%) rename src/utils/{event => events}/getEventById.js (100%) rename src/utils/{ => events}/searchEvents.js (100%) create mode 100644 src/utils/notifications/deleteNotificationUser.js rename src/utils/{ => notifications}/getUserNotifications.js (100%) rename src/utils/{ => users}/searchUsers.js (100%) diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index b8f3860..259bd7d 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,8 +1,9 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; import { useEffect, useRef, useState } from "react"; -import getUserNotifications from "../../utils/getUserNotifications.js"; +import getUserNotifications from "../../utils/notifications/getUserNotifications.js"; import getUser from "../../utils/getUser.js"; +import deleteNotificationUser from "../../utils/notifications/deleteNotificationUser.js"; function Header() { const [notificationMenu, setnotificationMenu] = useState(false); @@ -37,6 +38,28 @@ function Header() { }; }, []); + + + + + async function deleteNotification(notificationId) { + try { + const user = await getUser(); + const result = await deleteNotificationUser(user.id, notificationId); + + console.log(result); + + if (result) { + setNotifications(prev => prev.filter(n => n.id !== notificationId)); + } + } catch (err) { + console.error('Erreur suppression notification :', err); + } + } + + + + return (
@@ -114,7 +137,7 @@ function Header() {

{notification.content}

diff --git a/src/components/SearchBar/SearchBar.jsx b/src/components/SearchBar/SearchBar.jsx index d39f095..6635d98 100644 --- a/src/components/SearchBar/SearchBar.jsx +++ b/src/components/SearchBar/SearchBar.jsx @@ -1,8 +1,8 @@ import styles from "./SearchBar.module.css" import {Link} from "react-router"; import {useEffect, useState} from "react"; -import searchEvents from "../../utils/searchEvents.js"; -import searchUsers from "../../utils/searchUsers.js"; +import searchEvents from "../../utils/events/searchEvents.js"; +import searchUsers from "../../utils/users/searchUsers.js"; import { useLocation } from "react-router"; diff --git a/src/components/Task/Task.jsx b/src/components/Task/Task.jsx index 740aeda..1be1990 100644 --- a/src/components/Task/Task.jsx +++ b/src/components/Task/Task.jsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from "react"; -import getEventById from "../../utils/event/getEventById.js"; +import getEventById from "../../utils/events/getEventById.js"; import styles from "./Task.module.css"; import formatDate from "../../utils/date/formatDate.js"; diff --git a/src/components/createEventBtn/CreateEventBtn.jsx b/src/components/createEventBtn/CreateEventBtn.jsx index 3e638db..cc7677f 100644 --- a/src/components/createEventBtn/CreateEventBtn.jsx +++ b/src/components/createEventBtn/CreateEventBtn.jsx @@ -3,7 +3,7 @@ import { useState } from "react"; import Modal from "../ui/modal/modal.jsx"; import Button from "../ui/button/button.jsx"; import TextInput from "../ui/input/input.jsx"; -import createEvent from "../../utils/event/createEvent.js" +import createEvent from "../../utils/events/createEvent.js" export default function CreateEventBtn() { diff --git a/src/pages/Events/EventsPage.jsx b/src/pages/Events/EventsPage.jsx index 19fa549..4c56864 100644 --- a/src/pages/Events/EventsPage.jsx +++ b/src/pages/Events/EventsPage.jsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Event from "../../components/Event/Event.jsx"; import styles from "./EventsPage.module.css"; import ToolBar from "../../components/ToolBar/ToolBar.jsx"; -import getAllEvents from "../../utils/event/getAllEvents.js"; +import getAllEvents from "../../utils/events/getAllEvents.js"; import filter from "../../utils/filter.js"; diff --git a/src/utils/event/createEvent.js b/src/utils/events/createEvent.js similarity index 100% rename from src/utils/event/createEvent.js rename to src/utils/events/createEvent.js diff --git a/src/utils/event/getAllEvents.js b/src/utils/events/getAllEvents.js similarity index 100% rename from src/utils/event/getAllEvents.js rename to src/utils/events/getAllEvents.js diff --git a/src/utils/event/getEventById.js b/src/utils/events/getEventById.js similarity index 100% rename from src/utils/event/getEventById.js rename to src/utils/events/getEventById.js diff --git a/src/utils/searchEvents.js b/src/utils/events/searchEvents.js similarity index 100% rename from src/utils/searchEvents.js rename to src/utils/events/searchEvents.js diff --git a/src/utils/filter.js b/src/utils/filter.js index 1495a74..0ed30d7 100644 --- a/src/utils/filter.js +++ b/src/utils/filter.js @@ -4,9 +4,9 @@ function filter(elements, filters, type) { const dB = new Date(dateB); if (filters.yearOrder === "asc") { - return dA - dB; // plus ancien -> plus récent + return dA - dB; } else { - return dB - dA; // plus récent -> plus ancien + return dB - dA; } }; diff --git a/src/utils/notifications/deleteNotificationUser.js b/src/utils/notifications/deleteNotificationUser.js new file mode 100644 index 0000000..0b8bef1 --- /dev/null +++ b/src/utils/notifications/deleteNotificationUser.js @@ -0,0 +1,26 @@ +import getXSRFToken from "../getXSRF.js"; + +export default async function deleteNotificationUser(userId, notificationId) { + const csrfToken = await getXSRFToken(); + + try { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}/delete/notification/${notificationId}`, { + method: 'DELETE', + credentials: 'include', + headers: { + 'Accept': 'application/json', + 'X-XSRF-TOKEN': csrfToken + } + }); + + if (!res.ok) { + throw new Error(`Erreur serveur : ${res.status}`); + } + + const data = await res.json(); + return data; + } catch (err) { + console.error('Erreur :', err); + return null; + } +} diff --git a/src/utils/getUserNotifications.js b/src/utils/notifications/getUserNotifications.js similarity index 100% rename from src/utils/getUserNotifications.js rename to src/utils/notifications/getUserNotifications.js diff --git a/src/utils/searchUsers.js b/src/utils/users/searchUsers.js similarity index 100% rename from src/utils/searchUsers.js rename to src/utils/users/searchUsers.js From 21259f2838630efbc5617886fe4929c30fc4f791 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Wed, 17 Dec 2025 17:22:10 +0100 Subject: [PATCH 3/9] update notification routes call --- src/components/Header/Header.jsx | 7 ++----- src/utils/notifications/deleteNotificationUser.js | 4 ++-- src/utils/notifications/getUserNotifications.js | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 259bd7d..7c91356 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -2,7 +2,6 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; import { useEffect, useRef, useState } from "react"; import getUserNotifications from "../../utils/notifications/getUserNotifications.js"; -import getUser from "../../utils/getUser.js"; import deleteNotificationUser from "../../utils/notifications/deleteNotificationUser.js"; function Header() { @@ -16,8 +15,7 @@ function Header() { useEffect(() => { async function loadNotifications() { - const user = await getUser(); - const notifData = await getUserNotifications(user.id); + const notifData = await getUserNotifications(); setNotifications(notifData || []); } @@ -44,8 +42,7 @@ function Header() { async function deleteNotification(notificationId) { try { - const user = await getUser(); - const result = await deleteNotificationUser(user.id, notificationId); + const result = await deleteNotificationUser(notificationId); console.log(result); diff --git a/src/utils/notifications/deleteNotificationUser.js b/src/utils/notifications/deleteNotificationUser.js index 0b8bef1..804c1dd 100644 --- a/src/utils/notifications/deleteNotificationUser.js +++ b/src/utils/notifications/deleteNotificationUser.js @@ -1,10 +1,10 @@ import getXSRFToken from "../getXSRF.js"; -export default async function deleteNotificationUser(userId, notificationId) { +export default async function deleteNotificationUser(notificationId) { const csrfToken = await getXSRFToken(); try { - const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}/delete/notification/${notificationId}`, { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/delete/notification/${notificationId}`, { method: 'DELETE', credentials: 'include', headers: { diff --git a/src/utils/notifications/getUserNotifications.js b/src/utils/notifications/getUserNotifications.js index 8f750ee..491471f 100644 --- a/src/utils/notifications/getUserNotifications.js +++ b/src/utils/notifications/getUserNotifications.js @@ -1,7 +1,7 @@ -export default async function getUserNotifications(id) { +export default async function getUserNotifications() { try { const response = await fetch( - `http://${import.meta.env.VITE_API_URL}/api/users/${id}/notifications`, + `http://${import.meta.env.VITE_API_URL}/api/users/notifications`, { method: 'GET', credentials: 'include', From 42748ad722aa5799a03090727f8fd9add5685ce2 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Thu, 8 Jan 2026 15:00:43 +0100 Subject: [PATCH 4/9] add read notifications route and update notifications behavior --- .env.exemple | 12 +- package-lock.json | 192 ++++++++++++++++++- package.json | 3 + src/components/Header/Header.jsx | 57 +++++- src/utils/echo/initEcho.js | 21 ++ src/utils/notifications/readNotifications.js | 27 +++ 6 files changed, 301 insertions(+), 11 deletions(-) create mode 100644 src/utils/echo/initEcho.js create mode 100644 src/utils/notifications/readNotifications.js diff --git a/.env.exemple b/.env.exemple index 3452b5c..5136f91 100644 --- a/.env.exemple +++ b/.env.exemple @@ -1 +1,11 @@ -VITE_API_URL= \ No newline at end of file +VITE_API_URL= + +BROADCASTER = reverb +REVERB_KEY = local +REVERB_HOST = 127.0.0.1 +REVERB_PORT = 8080 +FORCE_TLS = false +DISABLE_STATS = true +ENCRYPTED = false +CLUSTER = mt1 +ENABLED_TRANSPORTS = [ws, wss] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c714a7e..1381366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,10 @@ "name": "sae-front", "version": "0.0.0", "dependencies": { + "@laravel/echo-react": "^2.2.6", "chart.js": "^4.5.1", + "laravel-echo": "^2.2.7", + "pusher-js": "^8.4.0", "react": "^19.1.1", "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", @@ -1116,6 +1119,20 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@laravel/echo-react": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@laravel/echo-react/-/echo-react-2.2.6.tgz", + "integrity": "sha512-xI5ekwEBhxKfSu/qIv9efXPLoM9RrszI51llWpNY4o71C1l5R7teMV8BbXA+1UbI0ijSyAI7MHxlkP6Cy3AVLw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "pusher-js": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "socket.io-client": "*" + } + }, "node_modules/@mdx-js/react": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", @@ -1535,6 +1552,13 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT", + "peer": true + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -2906,6 +2930,70 @@ "node": ">=14" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -3800,6 +3888,19 @@ "node": ">=6" } }, + "node_modules/laravel-echo": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.2.7.tgz", + "integrity": "sha512-MgD3ZFXqH5OOVdRjxNHPyQ0ijRr5+nLr7MtyF2XP+kRfhl+Qaa7qVzbtCn1HMgXuTn4SWH6ivn4qWVLlvRl8kg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "pusher-js": "*", + "socket.io-client": "*" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4023,7 +4124,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4410,6 +4510,15 @@ "node": ">=6" } }, + "node_modules/pusher-js": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz", + "integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==", + "license": "MIT", + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4788,6 +4897,72 @@ "dev": true, "license": "MIT" }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5205,6 +5380,12 @@ "dev": true, "license": "0BSD" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5638,6 +5819,15 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 996fc4e..9e4e83d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "test": "vitest" }, "dependencies": { + "@laravel/echo-react": "^2.2.6", "chart.js": "^4.5.1", + "laravel-echo": "^2.2.7", + "pusher-js": "^8.4.0", "react": "^19.1.1", "react-chartjs-2": "^5.3.1", "react-dom": "^19.1.1", diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 7c91356..9f7046f 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,25 +1,54 @@ import styles from "./Header.module.css" import { Link, NavLink } from "react-router"; -import { useEffect, useRef, useState } from "react"; +import {useContext, useEffect, useRef, useState} from "react"; import getUserNotifications from "../../utils/notifications/getUserNotifications.js"; import deleteNotificationUser from "../../utils/notifications/deleteNotificationUser.js"; +import initEcho from "../../utils/echo/initEcho.js" +import readNotifications from "../../utils/notifications/readNotifications.js"; +import {AuthContext} from "../../contexts/auth/AuthContext.js"; + function Header() { + const { update } = useContext(AuthContext); const [notificationMenu, setnotificationMenu] = useState(false); - const [unreadNotification, setunreadNotification] = useState(true); + const [unreadNotification, setunreadNotification] = useState(false); const [mobileMenu, setMobileMenu] = useState(false); const [notifications, setNotifications] = useState([]); const notificationRef = useRef(null); + useEffect(() => { + const echo = initEcho(); + + echo.private("admins-private") + .listen(".user.created", (event) => { + const newNotification = { + id: Date.now(), + content: `Nouvel demande d'inscription : ${event.user.name} ${event.user.lastname}`, + is_new: true + }; + setNotifications(prevNotifications => [newNotification, ...prevNotifications]); + setunreadNotification(true); + }); + + return () => { + echo.leave("admins-private"); + }; + }, []); useEffect(() => { + async function loadNotifications() { const notifData = await getUserNotifications(); + const data = notifData || []; setNotifications(notifData || []); + const hasUnread = data.some(n => n.pivot && n.pivot.unread === 1); + + setunreadNotification(hasUnread); } loadNotifications(); + const handleClickOutside = (event) => { if ( notificationRef.current && @@ -40,12 +69,25 @@ function Header() { + const toggleNotificationMenu = async () => { + try { + setnotificationMenu(!notificationMenu); + if (unreadNotification) { + const result = await readNotifications(); + if (result) { + setunreadNotification(false); + update(); + } + } + } catch (err) { + console.error('Erreur suppression notification :', err); + } + } + + async function deleteNotification(notificationId) { try { const result = await deleteNotificationUser(notificationId); - - console.log(result); - if (result) { setNotifications(prev => prev.filter(n => n.id !== notificationId)); } @@ -81,10 +123,7 @@ function Header() {
-
- )) + )}
diff --git a/src/components/Header/Header.module.css b/src/components/Header/Header.module.css index e684823..952529d 100644 --- a/src/components/Header/Header.module.css +++ b/src/components/Header/Header.module.css @@ -150,7 +150,7 @@ .notificationDiv { position: absolute; - width: 250px; + width: 300px; top: 105%; right: 2rem; opacity: 0; @@ -186,16 +186,6 @@ border-radius: 50%; } -.closeBtn { - display: flex; - align-items: center; - justify-content: center; - border: none; - cursor: pointer; - background: none; - margin: 0; - width: fit-content; -} .closeBtn:focus { outline: none; diff --git a/src/components/NotificationCard/NotificationCard.jsx b/src/components/NotificationCard/NotificationCard.jsx new file mode 100644 index 0000000..50774b4 --- /dev/null +++ b/src/components/NotificationCard/NotificationCard.jsx @@ -0,0 +1,34 @@ +import formatDate from "../../utils/date/formatDate.js"; +import formatTime from "../../utils/date/formatTime.js"; + +import styles from "./NotificationCard.module.css" + + +function NotificationCard({notifications, deleteNotification}) { + return ( + notifications.map((notification, index) => ( +
+
+ + Le {formatDate(notification.created_at)} à {formatTime(notification.created_at)} + + +
+ +
+

+ {notification.content} +

+
+
+ )) + ); +} + +export default NotificationCard; \ No newline at end of file diff --git a/src/components/NotificationCard/NotificationCard.module.css b/src/components/NotificationCard/NotificationCard.module.css new file mode 100644 index 0000000..db01e1a --- /dev/null +++ b/src/components/NotificationCard/NotificationCard.module.css @@ -0,0 +1,46 @@ +.notificationCard { + padding: 1.25rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + transition: background 0.2s ease; +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.timeTag { + font-size: 0.9rem; + font-weight: 500; + color: #019AFF; + letter-spacing: 0.2px; +} + +.message { + margin: 0; + font-size: 1.1rem; + line-height: 1.5; + color: #2c3e50; + font-weight: 400; +} + +.closeIconButton { + background: none; + border: none; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 1.1rem; + color: #2c3e50; +} + +.closeIconButton:hover { + color: #cc0000; +} \ No newline at end of file diff --git a/src/utils/date/formatTime.js b/src/utils/date/formatTime.js new file mode 100644 index 0000000..6012db2 --- /dev/null +++ b/src/utils/date/formatTime.js @@ -0,0 +1,8 @@ +export default function formatTime(d) { + const date = new Date(d); + + const hh = String(date.getUTCHours()).padStart(2, '0'); + const min = String(date.getUTCMinutes()).padStart(2, '0'); + + return `${hh}:${min}`; +} \ No newline at end of file diff --git a/src/utils/echo/listeners/userNotificationsListener.js b/src/utils/echo/listeners/userNotificationsListener.js index d51bcee..ef538ba 100644 --- a/src/utils/echo/listeners/userNotificationsListener.js +++ b/src/utils/echo/listeners/userNotificationsListener.js @@ -1,3 +1,5 @@ +import formatDateTime from '../../date/formatTime.js'; + export const userNotificationsListener = (echo, userData, setNotifications, setunreadNotification) => { if (!userData?.id) return null; @@ -8,6 +10,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `L'événement ${event.event.name} a été supprimé ! Vous n'y participez donc plus.`, + created_at: formatDateTime(Date.now()), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -17,6 +20,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `La tâche ${event.task.name} de l'événement ${event.event.name} a été supprimé ! Vous n'y participez donc plus.`, + created_at: formatDateTime(Date.now()), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -26,6 +30,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `Vous avez été assigné à la tâche ${event.task.name} de l'événement ${event.event.name}`, + created_at: formatDateTime(Date.now()), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -35,6 +40,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `Vous avez été désassigné de la tâche ${event.task.name} de l'événement ${event.event.name}`, + created_at: formatDateTime(Date.now()), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); From 69e7fdb35c84741a05a22c571c3f59ad4da79cf6 Mon Sep 17 00:00:00 2001 From: Giovanni-Josserand Date: Sun, 11 Jan 2026 23:24:15 +0100 Subject: [PATCH 9/9] correct created_at attribute of notifications --- src/utils/echo/listeners/userCreatedListener.js | 1 + src/utils/echo/listeners/userNotificationsListener.js | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/utils/echo/listeners/userCreatedListener.js b/src/utils/echo/listeners/userCreatedListener.js index 3cd8e04..dddab10 100644 --- a/src/utils/echo/listeners/userCreatedListener.js +++ b/src/utils/echo/listeners/userCreatedListener.js @@ -6,6 +6,7 @@ export const userCreatedListener = (echo, userData, setNotifications, setunreadN const newNotification = { id: Date.now(), content: `Nouvelle demande d'inscription : ${event.user.name} ${event.user.lastname}`, + created_at: Date.now(), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); diff --git a/src/utils/echo/listeners/userNotificationsListener.js b/src/utils/echo/listeners/userNotificationsListener.js index ef538ba..6b07beb 100644 --- a/src/utils/echo/listeners/userNotificationsListener.js +++ b/src/utils/echo/listeners/userNotificationsListener.js @@ -1,5 +1,3 @@ -import formatDateTime from '../../date/formatTime.js'; - export const userNotificationsListener = (echo, userData, setNotifications, setunreadNotification) => { if (!userData?.id) return null; @@ -10,7 +8,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `L'événement ${event.event.name} a été supprimé ! Vous n'y participez donc plus.`, - created_at: formatDateTime(Date.now()), + created_at: Date.now(), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -20,7 +18,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `La tâche ${event.task.name} de l'événement ${event.event.name} a été supprimé ! Vous n'y participez donc plus.`, - created_at: formatDateTime(Date.now()), + created_at: Date.now(), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -30,7 +28,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `Vous avez été assigné à la tâche ${event.task.name} de l'événement ${event.event.name}`, - created_at: formatDateTime(Date.now()), + created_at: Date.now(), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]); @@ -40,7 +38,7 @@ export const userNotificationsListener = (echo, userData, setNotifications, setu const newNotification = { id: Date.now(), content: `Vous avez été désassigné de la tâche ${event.task.name} de l'événement ${event.event.name}`, - created_at: formatDateTime(Date.now()), + created_at: Date.now(), pivot: { unread: 1 } }; setNotifications(prev => [newNotification, ...prev]);