diff --git a/components/calendar/calendar-widget.tsx b/components/calendar/calendar-widget.tsx index a3ae9a7..49a7c77 100644 --- a/components/calendar/calendar-widget.tsx +++ b/components/calendar/calendar-widget.tsx @@ -117,28 +117,33 @@ export function CalendarWidget() { if (currentEventCount !== lastEventCountRef.current) { lastEventCountRef.current = currentEventCount; - // Prepare notification items - const notificationItems = upcomingEvents.map(event => ({ - id: event.id, - title: event.title, - message: event.isAllDay - ? `Aujourd'hui (toute la journée)` - : `Le ${format(event.start, 'dd/MM à HH:mm', { locale: fr })}`, - link: '/agenda', - timestamp: event.start, - metadata: { - calendarId: event.calendarId, - calendarName: event.calendarName, - isAllDay: event.isAllDay, - }, - })); + // Always prepare notification items (even if count hasn't changed) + const notificationItems = upcomingEvents.map(event => ({ + id: event.id, + title: event.title, + message: event.isAllDay + ? `Aujourd'hui (toute la journée)` + : `Le ${format(event.start, 'dd/MM à HH:mm', { locale: fr })}`, + link: '/agenda', + timestamp: event.start, + metadata: { + calendarId: event.calendarId, + calendarName: event.calendarName, + isAllDay: event.isAllDay, + }, + })); - // Trigger notification update - await triggerNotification({ - source: 'calendar', - count: currentEventCount, - items: notificationItems, - }); + // Always trigger notification update to keep count fresh in Redis + // This ensures the count doesn't expire even if it hasn't changed + await triggerNotification({ + source: 'calendar', + count: currentEventCount, + items: notificationItems, + }); + + // Update last count reference + if (currentEventCount !== lastEventCountRef.current) { + lastEventCountRef.current = currentEventCount; } setEvents(upcomingEvents.slice(0, 5)); // Keep only 5 for display diff --git a/components/email.tsx b/components/email.tsx index f2cb0bf..39283d2 100644 --- a/components/email.tsx +++ b/components/email.tsx @@ -195,36 +195,38 @@ export function Email() { lastUnreadCountRef.current = currentUnreadCount; isInitializedRef.current = true; } else { - // Trigger notification if count changed or new emails detected - if (currentUnreadCount !== lastUnreadCountRef.current || hasNewEmails) { - const previousCount = lastUnreadCountRef.current; + // Update count if it changed + if (currentUnreadCount !== lastUnreadCountRef.current) { lastUnreadCountRef.current = currentUnreadCount; - - // Prepare notification items (unread emails only, max 10) - const notificationItems = transformedEmails - .filter(e => !e.read) - .slice(0, 10) - .map(email => { - const account = accountMap.get((email as any).accountId); - return { - id: email.id, - title: email.subject || 'Sans objet', - message: `De ${email.fromName || email.from.split('@')[0]}`, - link: '/courrier', - timestamp: new Date(email.date), - metadata: { - accountId: (email as any).accountId, - accountEmail: account?.email, - }, - }; - }); + } + } + + // Always prepare notification items (unread emails only, max 10) + const notificationItems = transformedEmails + .filter(e => !e.read) + .slice(0, 10) + .map(email => { + const account = accountMap.get((email as any).accountId); + return { + id: email.id, + title: email.subject || 'Sans objet', + message: `De ${email.fromName || email.from.split('@')[0]}`, + link: '/courrier', + timestamp: new Date(email.date), + metadata: { + accountId: (email as any).accountId, + accountEmail: account?.email, + }, + }; + }); - // Trigger notification update (for badge) - await triggerNotification({ - source: 'email', - count: currentUnreadCount, - items: notificationItems, - }); + // Always trigger notification update to keep count fresh in Redis + // This ensures the count doesn't expire even if it hasn't changed + await triggerNotification({ + source: 'email', + count: currentUnreadCount, + items: notificationItems, + }); // Dispatch event for Outlook-style notifications (for new emails detected by ID) if (hasNewEmails) { diff --git a/components/flow.tsx b/components/flow.tsx index 955f578..b74bc33 100644 --- a/components/flow.tsx +++ b/components/flow.tsx @@ -218,40 +218,44 @@ export function Duties() { // Calculate current task count const currentTaskCount = sortedTasks.length; - // Trigger notification if count changed - if (currentTaskCount !== lastTaskCountRef.current) { + // Always trigger notification to keep the count fresh in Redis + // This prevents the count from expiring if it hasn't changed + const shouldUpdate = currentTaskCount !== lastTaskCountRef.current || lastTaskCountRef.current === -1; + + if (shouldUpdate) { lastTaskCountRef.current = currentTaskCount; - - // Prepare notification items (max 10) - const notificationItems = sortedTasks - .slice(0, 10) - .map(task => ({ - id: task.id.toString(), - title: task.headline, - message: task.dateToFinish - ? `Due: ${formatDate(task.dateToFinish)}` - : 'Tâche en retard', - link: (task as any).source === 'twenty-crm' - ? (task as any).url - : `https://agilite.slm-lab.net/tickets/showTicket/${task.id.replace('twenty-', '')}`, - timestamp: task.dateToFinish - ? new Date(task.dateToFinish) - : new Date(), - metadata: { - source: (task as any).source || 'leantime', - projectName: task.projectName, - status: task.status, - }, - })); - - // Trigger notification update - await triggerNotification({ - source: 'leantime', - count: currentTaskCount, - items: notificationItems, - }); } + // Prepare notification items (max 10) + const notificationItems = sortedTasks + .slice(0, 10) + .map(task => ({ + id: task.id.toString(), + title: task.headline, + message: task.dateToFinish + ? `Due: ${formatDate(task.dateToFinish)}` + : 'Tâche en retard', + link: (task as any).source === 'twenty-crm' + ? (task as any).url + : `https://agilite.slm-lab.net/tickets/showTicket/${task.id.replace('twenty-', '')}`, + timestamp: task.dateToFinish + ? new Date(task.dateToFinish) + : new Date(), + metadata: { + source: (task as any).source || 'leantime', + projectName: task.projectName, + status: task.status, + }, + })); + + // Always trigger notification update to keep count fresh in Redis + // This ensures the count doesn't expire even if it hasn't changed + await triggerNotification({ + source: 'leantime', + count: currentTaskCount, + items: notificationItems, + }); + setTasks(sortedTasks); // Dispatch event for Outlook-style notifications (when tasks are due) diff --git a/components/notification-badge.tsx b/components/notification-badge.tsx index 35179e3..d476d45 100644 --- a/components/notification-badge.tsx +++ b/components/notification-badge.tsx @@ -27,12 +27,21 @@ export const NotificationBadge = memo(function NotificationBadge({ className }: const [isOpen, setIsOpen] = useState(false); const [manualFetchAttempted, setManualFetchAttempted] = useState(false); - console.log('[NOTIFICATION_BADGE] Auth status:', status); - console.log('[NOTIFICATION_BADGE] Session:', session ? 'exists' : 'null'); - console.log('[NOTIFICATION_BADGE] Current notification count:', notificationCount); - console.log('[NOTIFICATION_BADGE] Current notifications:', notifications.length > 0 ? `${notifications.length} loaded` : 'none loaded'); - console.log('[NOTIFICATION_BADGE] Loading state:', loading); - console.log('[NOTIFICATION_BADGE] Error state:', error); + console.log('[NOTIFICATION_BADGE] 📊 Notification state:', { + authStatus: status, + hasSession: !!session, + totalUnread: notificationCount.unread, + total: notificationCount.total, + sources: Object.keys(notificationCount.sources || {}).map(source => ({ + source, + unread: notificationCount.sources[source]?.unread || 0, + total: notificationCount.sources[source]?.total || 0, + })), + hasUnread: notificationCount.unread > 0, + notificationsLoaded: notifications.length, + loading, + error: error || null, + }); // Manual fetch function with error handling const manualFetch = async () => { diff --git a/components/parole.tsx b/components/parole.tsx index df455c3..634b962 100644 --- a/components/parole.tsx +++ b/components/parole.tsx @@ -96,32 +96,34 @@ export function Parole() { lastUnreadCountRef.current = currentUnreadCount; isInitializedRef.current = true; } else { - // Si le count a changé ou nouveaux messages détectés, déclencher notification - if (currentUnreadCount !== lastUnreadCountRef.current || hasNewMessages) { - const previousCount = lastUnreadCountRef.current; + // Update count if it changed + if (currentUnreadCount !== lastUnreadCountRef.current) { lastUnreadCountRef.current = currentUnreadCount; - - // Préparer les items pour les notifications (messages, max 10) - const notificationItems = data.messages - .slice(0, 10) - .map((msg: any) => ({ - id: msg.id, - title: msg.sender.name || msg.sender.username, - message: msg.text || msg.message || '', - link: '/parole', - timestamp: new Date(msg.rawTimestamp || msg.timestamp), - metadata: { - roomName: msg.roomName, - roomType: msg.roomType, - }, - })); + } + } + + // Always prepare notification items (messages, max 10) + const notificationItems = data.messages + .slice(0, 10) + .map((msg: any) => ({ + id: msg.id, + title: msg.sender.name || msg.sender.username, + message: msg.text || msg.message || '', + link: '/parole', + timestamp: new Date(msg.rawTimestamp || msg.timestamp), + metadata: { + roomName: msg.roomName, + roomType: msg.roomType, + }, + })); - // Déclencher notification update (for badge) - await triggerNotification({ - source: 'rocketchat', - count: currentUnreadCount, - items: notificationItems, - }); + // Always trigger notification update to keep count fresh in Redis + // This ensures the count doesn't expire even if it hasn't changed + await triggerNotification({ + source: 'rocketchat', + count: currentUnreadCount, + items: notificationItems, + }); // Dispatch event for Outlook-style notifications (for new messages detected by ID) if (hasNewMessages) { diff --git a/hooks/use-notifications.ts b/hooks/use-notifications.ts index 7d9b9c6..511bd90 100644 --- a/hooks/use-notifications.ts +++ b/hooks/use-notifications.ts @@ -58,7 +58,14 @@ export function useNotifications() { ); if (isMountedRef.current) { - console.log('[useNotifications] Received notification count:', data); + console.log('[useNotifications] Received notification count:', { + total: data.total, + unread: data.unread, + sources: Object.keys(data.sources || {}).map(source => ({ + source, + count: data.sources[source]?.unread || 0, + })), + }); setNotificationCount(data); } } catch (err: any) { diff --git a/lib/services/notifications/notification-registry.ts b/lib/services/notifications/notification-registry.ts index ca7feac..957c426 100644 --- a/lib/services/notifications/notification-registry.ts +++ b/lib/services/notifications/notification-registry.ts @@ -20,7 +20,7 @@ export class NotificationRegistry { private static COUNT_CACHE_KEY = (userId: string) => `notifications:count:${userId}`; private static ITEMS_CACHE_KEY = (userId: string, source: string) => `notifications:items:${userId}:${source}`; - private static COUNT_CACHE_TTL = 30; // 30 seconds (aligned with refresh interval) + private static COUNT_CACHE_TTL = 300; // 5 minutes (increased to prevent premature expiration) private static ITEMS_CACHE_TTL = 300; // 5 minutes for items public static getInstance(): NotificationRegistry { @@ -72,12 +72,31 @@ export class NotificationRegistry { total: previousSourceCount + count, unread: previousSourceCount + count, }; + logger.debug('[NOTIFICATION_REGISTRY] Call count incremented', { + source, + previousCount: previousSourceCount, + newCount: currentCount.sources[source].unread, + }); } else { // For regular updates, set the count (widgets send their total) - currentCount.sources[source] = { - total: count, - unread: count, - }; + // Only update if the new count is different or if we're setting it for the first time + if (count !== previousSourceCount || !currentCount.sources[source]) { + currentCount.sources[source] = { + total: count, + unread: count, + }; + logger.debug('[NOTIFICATION_REGISTRY] Count updated', { + source, + previousCount: previousSourceCount, + newCount: count, + }); + } else { + // Count hasn't changed, but refresh the TTL to keep it alive + logger.debug('[NOTIFICATION_REGISTRY] Count unchanged, refreshing TTL', { + source, + count, + }); + } } // Recalculate total