Add frontend alerts imporvements

This commit is contained in:
Urtzi Alfaro
2025-12-29 08:11:29 +01:00
parent 96d8576103
commit 2e7e1f5557
7 changed files with 351 additions and 190 deletions

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useRef, useState, ReactNode } from 'react';
import React, { createContext, useContext, useEffect, useRef, useState, ReactNode, useCallback } from 'react';
import { useAuthStore } from '../stores/auth.store';
import { useCurrentTenant } from '../stores/tenant.store';
import { showToast } from '../utils/toast';
@@ -103,6 +103,11 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
setIsConnected(true);
reconnectAttempts.current = 0;
// Clear processed event IDs on new connection to allow fresh state from server
// This ensures events are processed again after reconnection or navigation
processedEventIdsRef.current.clear();
console.log('🔄 [SSE] Cleared processed event IDs cache on connection open');
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = undefined;
@@ -214,6 +219,11 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
// Trigger listeners with enriched alert data
// Wrap in queueMicrotask to prevent setState during render warnings
const listeners = eventListenersRef.current.get('alert');
console.log('📤 [SSEContext] Notifying alert listeners:', {
listenerCount: listeners?.size || 0,
eventId: data.id,
eventType: data.event_type || data.type,
});
if (listeners) {
listeners.forEach(callback => {
queueMicrotask(() => callback(data));
@@ -347,6 +357,22 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
eventSource.addEventListener('recommendation', (event) => {
try {
const data = JSON.parse(event.data);
// GLOBAL DEDUPLICATION: Skip if this event was already processed
if (data.id && processedEventIdsRef.current.has(data.id)) {
console.log('⏭️ [SSE] Skipping duplicate recommendation:', data.id);
return;
}
// Mark event as processed
if (data.id) {
processedEventIdsRef.current.add(data.id);
if (processedEventIdsRef.current.size > 1000) {
const firstId = Array.from(processedEventIdsRef.current)[0];
processedEventIdsRef.current.delete(firstId);
}
}
const sseEvent: SSEEvent = {
type: 'recommendation',
data,
@@ -433,6 +459,11 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
// Trigger listeners with recommendation data
// Wrap in queueMicrotask to prevent setState during render warnings
const listeners = eventListenersRef.current.get('recommendation');
console.log('📤 [SSEContext] Notifying recommendation listeners:', {
listenerCount: listeners?.size || 0,
eventId: data.id,
eventType: data.event_type || data.type,
});
if (listeners) {
listeners.forEach(callback => {
queueMicrotask(() => callback(data));
@@ -483,13 +514,14 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
reconnectAttempts.current = 0;
};
const addEventListener = (eventType: string, callback: (data: any) => void) => {
// Memoize addEventListener to prevent unnecessary effect re-runs in consumers
const addEventListener = useCallback((eventType: string, callback: (data: any) => void) => {
if (!eventListenersRef.current.has(eventType)) {
eventListenersRef.current.set(eventType, new Set());
}
eventListenersRef.current.get(eventType)!.add(callback);
// Return cleanup function
return () => {
const listeners = eventListenersRef.current.get(eventType);
@@ -500,7 +532,7 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
}
}
};
};
}, []); // No dependencies - uses only refs
// Connect when authenticated, disconnect when not or when tenant changes
useEffect(() => {
@@ -531,6 +563,8 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
};
}, []);
// Context value - consumers should extract only what they need
// addEventListener is now stable (wrapped in useCallback)
const contextValue: SSEContextType = {
isConnected,
lastEvent,