New alert system and panel de control page

This commit is contained in:
Urtzi Alfaro
2025-11-27 15:52:40 +01:00
parent 1a2f4602f3
commit e902419b6e
178 changed files with 20982 additions and 6944 deletions

View File

@@ -17,7 +17,7 @@ interface SSEContextType {
addEventListener: (eventType: string, callback: (data: any) => void) => () => void;
}
const SSEContext = createContext<SSEContextType | undefined>(undefined);
export const SSEContext = createContext<SSEContextType | undefined>(undefined);
export const useSSE = () => {
const context = useContext(SSEContext);
@@ -98,63 +98,13 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
console.log('SSE connection opened');
setIsConnected(true);
reconnectAttempts.current = 0;
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = undefined;
}
};
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Handle different SSE message types from notification service
if (data.status === 'keepalive') {
console.log('SSE keepalive received');
return;
}
const sseEvent: SSEEvent = {
type: data.item_type || 'message',
data: data,
timestamp: data.timestamp || new Date().toISOString(),
};
setLastEvent(sseEvent);
// Show notification if it's an alert or recommendation
if (data.item_type && ['alert', 'recommendation'].includes(data.item_type)) {
let toastType: 'info' | 'success' | 'warning' | 'error' = 'info';
if (data.item_type === 'alert') {
if (data.severity === 'urgent') toastType = 'error';
else if (data.severity === 'high') toastType = 'error';
else if (data.severity === 'medium') toastType = 'warning';
else toastType = 'info';
} else if (data.item_type === 'recommendation') {
toastType = 'info';
}
showToast[toastType](data.message, { title: data.title || 'Notificación', duration: data.severity === 'urgent' ? 0 : 5000 });
}
// Trigger registered listeners
const listeners = eventListenersRef.current.get(sseEvent.type);
if (listeners) {
listeners.forEach(callback => callback(data));
}
// Also trigger 'message' listeners for backward compatibility
const messageListeners = eventListenersRef.current.get('message');
if (messageListeners) {
messageListeners.forEach(callback => callback(data));
}
} catch (error) {
console.error('Error parsing SSE message:', error);
}
};
// Handle connection confirmation from gateway
eventSource.addEventListener('connection', (event) => {
try {
@@ -175,7 +125,7 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
}
});
// Handle alert events
// Handle alert events (enriched alerts from alert-processor)
eventSource.addEventListener('alert', (event) => {
try {
const data = JSON.parse(event.data);
@@ -187,114 +137,45 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
setLastEvent(sseEvent);
// Show alert toast
// Determine toast type based on enriched priority_level and type_class
let toastType: 'info' | 'success' | 'warning' | 'error' = 'info';
if (data.severity === 'urgent') toastType = 'error';
else if (data.severity === 'high') toastType = 'error';
else if (data.severity === 'medium') toastType = 'warning';
else toastType = 'info';
showToast[toastType](data.message, { title: data.title || 'Alerta', duration: data.severity === 'urgent' ? 0 : 5000 });
// Use success toast for prevented_issue type (AI already handled)
if (data.type_class === 'prevented_issue') {
toastType = 'success';
} else if (data.priority_level === 'critical') {
toastType = 'error';
} else if (data.priority_level === 'important') {
toastType = 'warning';
} else if (data.priority_level === 'standard') {
toastType = 'info';
}
// Trigger listeners
// Show toast with enriched data
const title = data.title || 'Alerta';
const duration = data.priority_level === 'critical' ? 0 : 5000;
// Add financial impact to message if available
let message = data.message;
if (data.business_impact?.financial_impact_eur) {
message = `${data.message} • €${data.business_impact.financial_impact_eur} en riesgo`;
}
showToast[toastType](message, { title, duration });
// Trigger listeners with enriched alert data
// Wrap in queueMicrotask to prevent setState during render warnings
const listeners = eventListenersRef.current.get('alert');
if (listeners) {
listeners.forEach(callback => callback(data));
listeners.forEach(callback => {
queueMicrotask(() => callback(data));
});
}
} catch (error) {
console.error('Error parsing alert event:', error);
}
});
// Handle recommendation events
eventSource.addEventListener('recommendation', (event) => {
try {
const data = JSON.parse(event.data);
const sseEvent: SSEEvent = {
type: 'recommendation',
data,
timestamp: data.timestamp || new Date().toISOString(),
};
setLastEvent(sseEvent);
// Show recommendation toast
showToast.info(data.message, { title: data.title || 'Recomendación', duration: 5000 });
// Trigger listeners
const listeners = eventListenersRef.current.get('recommendation');
if (listeners) {
listeners.forEach(callback => callback(data));
}
} catch (error) {
console.error('Error parsing recommendation event:', error);
}
});
// Handle inventory_alert events (high/urgent severity alerts from gateway)
eventSource.addEventListener('inventory_alert', (event) => {
try {
const data = JSON.parse(event.data);
const sseEvent: SSEEvent = {
type: 'alert',
data,
timestamp: data.timestamp || new Date().toISOString(),
};
setLastEvent(sseEvent);
// Show urgent alert toast
const toastType = data.severity === 'urgent' ? 'error' : 'error';
showToast[toastType](data.message, { title: data.title || 'Alerta de Inventario', duration: data.severity === 'urgent' ? 0 : 5000 });
// Trigger alert listeners
const listeners = eventListenersRef.current.get('alert');
if (listeners) {
listeners.forEach(callback => callback(data));
}
} catch (error) {
console.error('Error parsing inventory_alert event:', error);
}
});
// Handle generic notification events from gateway
eventSource.addEventListener('notification', (event) => {
try {
const data = JSON.parse(event.data);
const sseEvent: SSEEvent = {
type: data.item_type || 'notification',
data,
timestamp: data.timestamp || new Date().toISOString(),
};
setLastEvent(sseEvent);
// Show notification toast
let toastType: 'info' | 'success' | 'warning' | 'error' = 'info';
if (data.severity === 'urgent') toastType = 'error';
else if (data.severity === 'high') toastType = 'warning';
else if (data.severity === 'medium') toastType = 'info';
showToast[toastType](data.message, { title: data.title || 'Notificación', duration: data.severity === 'urgent' ? 0 : 5000 });
// Trigger listeners for both notification and specific type
const notificationListeners = eventListenersRef.current.get('notification');
if (notificationListeners) {
notificationListeners.forEach(callback => callback(data));
}
if (data.item_type) {
const typeListeners = eventListenersRef.current.get(data.item_type);
if (typeListeners) {
typeListeners.forEach(callback => callback(data));
}
}
} catch (error) {
console.error('Error parsing notification event:', error);
}
});
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
setIsConnected(false);