Refonte du système de notifications : passage aux notifications dérivées

Remplace le système de notifications stockées par un système de providers
qui dérivent les notifications des données existantes (commentaires, réponses,
demandes de projet, demandes de rendez-vous, validations de brief).

- Ajout du NotificationCollector et de l'interface NotificationProvider
- Création de 5 providers : Comment, Reply, ProjectRequest, AppointmentRequest, Content
- Métadonnées de notifications stockées directement sur les entités source
- Nouvelles routes mark-as-read et mark-all-read
- Mise à jour du frontend pour le nouveau système
- Route de migration pour les données existantes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-01-15 10:31:31 +01:00
parent c68b51f639
commit a7d315942a
26 changed files with 1406 additions and 137 deletions

View file

@ -163,6 +163,34 @@ export const useApiStore = defineStore("api", () => {
}
}
/**
* Marque une notification comme lue.
* @param {Object} notification - L'objet notification complet (avec type, id, _file, _projectUri, etc.)
*/
async function markNotificationRead(notification) {
const headers = {
method: "POST",
body: JSON.stringify(notification),
};
try {
const response = await fetch("/mark-notification-read.json", headers);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.status === "error") {
throw new Error(data.message);
}
// Mettre à jour le store local
userStore.markNotificationRead(notification.id, notification.project?.uri || notification._projectUri);
return data;
} catch (error) {
console.error("Erreur lors du marquage de la notification:", error);
throw error;
}
}
// Ancienne fonction gardée pour rétro-compatibilité
async function readNotification(notificationId, projectId) {
const headers = {
method: "POST",
@ -215,6 +243,31 @@ export const useApiStore = defineStore("api", () => {
}
}
/**
* Marque toutes les notifications comme lues (nouveau système).
*/
async function markAllNotificationsRead() {
try {
const response = await fetch("/mark-all-notifications-read.json", {
method: "POST",
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.status === "error") {
throw new Error(data.message);
}
userStore.markAllNotificationsRead();
console.log("Toutes les notifications ont été marquées comme lues.");
return data;
} catch (error) {
console.error("Erreur lors du marquage de toutes les notifications:", error);
throw error;
}
}
// Ancienne fonction gardée pour rétro-compatibilité
async function readAllNotifications() {
try {
const response = await fetch("/read-all-notifications.json");
@ -243,6 +296,10 @@ export const useApiStore = defineStore("api", () => {
updateComment,
deleteComment,
replyComment,
// Nouvelles fonctions
markNotificationRead,
markAllNotificationsRead,
// Anciennes fonctions (rétro-compatibilité)
readNotification,
readAllNotifications,
validateBrief,

View file

@ -11,48 +11,76 @@ export const useUserStore = defineStore('user', () => {
const { projects } = storeToRefs(useProjectsStore());
/**
* Liste des notifications agrégées depuis tous les projets.
* Les notifications sont maintenant dérivées côté backend avec isRead pré-calculé.
*/
const notifications = computed(() => {
return projects.value?.flatMap((project) => {
if (!projects.value || !user.value) return [];
return projects.value.flatMap((project) => {
if (!project.notifications) return [];
return project.notifications
.filter((notification) => notification.author.uuid !== user.value.uuid)
.map((notification) => ({
...notification,
project: project,
isRead: notification.readby?.includes(user.value.uuid),
}));
return project.notifications.map((notification) => ({
...notification,
project: project,
// isRead est maintenant fourni par le backend
}));
});
});
function readNotification(notificationId, projectId) {
console.log('Read notification', notificationId, projectId);
/**
* Marque une notification comme lue dans le store local.
* @param {string} notificationId - L'ID de la notification
* @param {string} projectUri - L'URI du projet (optionnel, pour retrouver le projet)
*/
function markNotificationRead(notificationId, projectUri = null) {
if (!user.value?.uuid) return;
projects.value = projects.value.map((project) => {
// Si projectUri fourni, cibler le bon projet
if (projectUri && project.uri !== projectUri && `/${project.uri}` !== projectUri) {
return project;
}
return {
...project,
notifications: (project.notifications || []).map((notification) =>
notification.id === notificationId
? {
...notification,
isRead: true,
readby: [...new Set([...(notification.readby || []), user.value.uuid])],
}
: notification
),
};
});
}
/**
* Marque toutes les notifications comme lues dans le store local.
*/
function markAllNotificationsRead() {
if (!user.value?.uuid) return;
projects.value = projects.value.map((project) => ({
...project,
notifications:
project.uuid === projectId || project.uri === projectId
? project.notifications.map((notification) =>
notification.id === notificationId
? {
...notification,
readby: [
...new Set([...notification.readby, user.value.uuid]),
],
}
: notification
)
: project.notifications,
notifications: (project.notifications || []).map((notification) => ({
...notification,
isRead: true,
readby: [...new Set([...(notification.readby || []), user.value.uuid])],
})),
}));
}
// Anciennes fonctions gardées pour rétro-compatibilité
function readNotification(notificationId, projectId) {
markNotificationRead(notificationId, projectId);
}
function readAllNotifications() {
projects.value = projects.value.map((project) => ({
...project,
notifications: project.notifications.map((notification) => ({
...notification,
readby: [...new Set([...notification.readby, user.value.uuid])],
})),
}));
markAllNotificationsRead();
}
function canEditComment(comment) {
@ -63,6 +91,10 @@ export const useUserStore = defineStore('user', () => {
user,
isLogged,
notifications,
// Nouvelles fonctions
markNotificationRead,
markAllNotificationsRead,
// Anciennes fonctions (rétro-compatibilité)
readNotification,
readAllNotifications,
canEditComment,

View file

@ -119,14 +119,24 @@ function changeTab(newValue) {
function readAll() {
try {
api.readAllNotifications();
api.markAllNotificationsRead();
} catch (error) {
console.log('Could not read all notifications : ', error);
}
}
// Functions
function handleNotificationClick(notification) {
async function handleNotificationClick(notification) {
// Marquer la notification comme lue
if (!notification.isRead) {
try {
await api.markNotificationRead(notification);
} catch (error) {
console.log('Could not mark notification as read:', error);
}
}
// Naviguer vers la cible
const href =
notification.type === 'appointment-request'
? getHref(notification) + '?tab=designToLight'