New alert service

This commit is contained in:
Urtzi Alfaro
2025-12-05 20:07:01 +01:00
parent 1fe3a73549
commit 667e6e0404
393 changed files with 26002 additions and 61033 deletions

View File

@@ -0,0 +1,74 @@
/**
* Unified Alert Context
*
* Context provider for sharing alert state across components
*/
import React, { createContext, useContext, ReactNode, useState, useEffect, useCallback } from 'react';
import { Alert } from '../api/types/events';
import { useUnifiedAlerts } from '../api/hooks/useUnifiedAlerts';
import { AlertFilterOptions } from '../utils/alertManagement';
// Define context type
interface AlertContextType {
alerts: Alert[];
filteredAlerts: Alert[];
stats: any;
filters: AlertFilterOptions;
setFilters: (filters: AlertFilterOptions) => void;
search: string;
setSearch: (search: string) => void;
isLoading: boolean;
isRefetching: boolean;
error: Error | null;
refetch: () => void;
acknowledgeAlert: (alertId: string) => Promise<void>;
resolveAlert: (alertId: string) => Promise<void>;
cancelAutoAction: (alertId: string) => Promise<void>;
acknowledgeAlertsByMetadata: (alertType: string, metadata: any) => Promise<void>;
resolveAlertsByMetadata: (alertType: string, metadata: any) => Promise<void>;
isSSEConnected: boolean;
sseError: Error | null;
}
// Create context with default values
const AlertContext = createContext<AlertContextType | undefined>(undefined);
// Props for the provider
interface AlertProviderProps {
children: ReactNode;
tenantId: string;
initialFilters?: AlertFilterOptions;
}
// Alert Provider Component
export const AlertProvider: React.FC<AlertProviderProps> = ({
children,
tenantId,
initialFilters = {}
}) => {
// Use the unified hook
const unifiedAlerts = useUnifiedAlerts(tenantId, initialFilters, {
refetchInterval: 60000, // 1 minute
enableSSE: true,
sseChannels: [`*.alerts`, `*.notifications`]
});
return (
<AlertContext.Provider value={unifiedAlerts}>
{children}
</AlertContext.Provider>
);
};
// Custom hook to use the alert context
export const useAlertContext = (): AlertContextType => {
const context = useContext(AlertContext);
if (context === undefined) {
throw new Error('useAlertContext must be used within an AlertProvider');
}
return context;
};
// Export the context for use in components
export default AlertContext;

View File

@@ -0,0 +1,117 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
export interface NetworkMetrics {
totalSales: number;
totalProduction: number;
totalInventoryValue: number;
averageSales: number;
averageProduction: number;
averageInventoryValue: number;
childCount: number;
}
export interface EnterpriseModeState {
isNetworkView: boolean;
selectedOutletId: string | null;
selectedOutletName: string | null;
parentTenantId: string | null;
networkMetrics: NetworkMetrics | null;
networkViewPath: string | null;
}
interface EnterpriseContextType {
state: EnterpriseModeState;
enterNetworkView: (parentTenantId: string) => void;
drillDownToOutlet: (outletId: string, outletName: string, metrics?: NetworkMetrics) => void;
returnToNetworkView: () => void;
updateNetworkMetrics: (metrics: NetworkMetrics) => void;
clearEnterpriseMode: () => void;
}
const EnterpriseContext = createContext<EnterpriseContextType | undefined>(undefined);
export const useEnterprise = () => {
const context = useContext(EnterpriseContext);
if (context === undefined) {
throw new Error('useEnterprise must be used within an EnterpriseProvider');
}
return context;
};
interface EnterpriseProviderProps {
children: ReactNode;
}
export const EnterpriseProvider: React.FC<EnterpriseProviderProps> = ({ children }) => {
const [state, setState] = useState<EnterpriseModeState>({
isNetworkView: false,
selectedOutletId: null,
selectedOutletName: null,
parentTenantId: null,
networkMetrics: null,
networkViewPath: null,
});
const enterNetworkView = (parentTenantId: string) => {
setState({
isNetworkView: true,
selectedOutletId: null,
selectedOutletName: null,
parentTenantId,
networkMetrics: null,
networkViewPath: window.location.pathname,
});
};
const drillDownToOutlet = (outletId: string, outletName: string, metrics?: NetworkMetrics) => {
setState(prev => ({
...prev,
isNetworkView: false,
selectedOutletId: outletId,
selectedOutletName: outletName,
networkMetrics: metrics || prev.networkMetrics,
}));
};
const returnToNetworkView = () => {
setState(prev => ({
...prev,
isNetworkView: true,
selectedOutletId: null,
selectedOutletName: null,
}));
};
const updateNetworkMetrics = (metrics: NetworkMetrics) => {
setState(prev => ({
...prev,
networkMetrics: metrics,
}));
};
const clearEnterpriseMode = () => {
setState({
isNetworkView: false,
selectedOutletId: null,
selectedOutletName: null,
parentTenantId: null,
networkMetrics: null,
networkViewPath: null,
});
};
const contextValue: EnterpriseContextType = {
state,
enterNetworkView,
drillDownToOutlet,
returnToNetworkView,
updateNetworkMetrics,
clearEnterpriseMode,
};
return (
<EnterpriseContext.Provider value={contextValue}>
{children}
</EnterpriseContext.Provider>
);
};

View File

@@ -0,0 +1,154 @@
/**
* Clean Event Context for Global State Management
*
* NO BACKWARD COMPATIBILITY - Complete rewrite with i18n parameterized content
*/
import React, { createContext, useContext, useReducer, ReactNode } from 'react';
import { Event, Alert, Notification, Recommendation } from '../api/types/events';
// Action types
type EventAction =
| { type: 'ADD_EVENT'; payload: Event }
| { type: 'UPDATE_EVENT'; payload: Event }
| { type: 'REMOVE_EVENT'; payload: string } // event ID
| { type: 'SET_EVENTS'; payload: Event[] }
| { type: 'CLEAR_EVENTS' }
| { type: 'MARK_AS_READ'; payload: string } // event ID
| { type: 'ACKNOWLEDGE_EVENT'; payload: string } // event ID
| { type: 'RESOLVE_EVENT'; payload: string }; // event ID
// State type
interface EventState {
events: Event[];
unreadCount: number;
activeAlerts: Alert[];
recentNotifications: Notification[];
recommendations: Recommendation[];
}
// Initial state
const initialState: EventState = {
events: [],
unreadCount: 0,
activeAlerts: [],
recentNotifications: [],
recommendations: [],
};
// Reducer
const eventReducer = (state: EventState, action: EventAction): EventState => {
switch (action.type) {
case 'SET_EVENTS':
const alerts = action.payload.filter(e => e.event_class === 'alert') as Alert[];
const notifications = action.payload.filter(e => e.event_class === 'notification') as Notification[];
const recommendations = action.payload.filter(e => e.event_class === 'recommendation') as Recommendation[];
return {
...state,
events: action.payload,
activeAlerts: alerts.filter(a => a.status === 'active'),
recentNotifications: notifications.slice(0, 10), // Most recent 10
recommendations: recommendations,
unreadCount: action.payload.filter(e => !e.event_metadata?.read).length,
};
case 'ADD_EVENT':
const existingIndex = state.events.findIndex(e => e.id === action.payload.id);
if (existingIndex !== -1) {
// Update existing event
const updatedEvents = [...state.events];
updatedEvents[existingIndex] = action.payload;
return eventReducer({ ...state, events: updatedEvents }, { type: 'SET_EVENTS', payload: updatedEvents });
} else {
// Add new event
const newEvents = [...state.events, action.payload];
return eventReducer({ ...state, events: newEvents }, { type: 'SET_EVENTS', payload: newEvents });
}
case 'UPDATE_EVENT':
const updatedEvents = state.events.map(e =>
e.id === action.payload.id ? action.payload : e
);
return eventReducer({ ...state, events: updatedEvents }, { type: 'SET_EVENTS', payload: updatedEvents });
case 'REMOVE_EVENT':
const filteredEvents = state.events.filter(e => e.id !== action.payload);
return eventReducer({ ...state, events: filteredEvents }, { type: 'SET_EVENTS', payload: filteredEvents });
case 'MARK_AS_READ':
const eventsWithRead = state.events.map(e =>
e.id === action.payload ? { ...e, event_metadata: { ...e.event_metadata, read: true } } : e
);
return eventReducer({ ...state, events: eventsWithRead }, { type: 'SET_EVENTS', payload: eventsWithRead });
case 'ACKNOWLEDGE_EVENT':
const eventsWithAck = state.events.map(e =>
e.id === action.payload && e.event_class === 'alert'
? { ...e, status: 'acknowledged' as const } as Event
: e
);
return eventReducer({ ...state, events: eventsWithAck }, { type: 'SET_EVENTS', payload: eventsWithAck });
case 'RESOLVE_EVENT':
const eventsWithResolved = state.events.map(e =>
e.id === action.payload && e.event_class === 'alert'
? { ...e, status: 'resolved' as const, resolved_at: new Date().toISOString() } as Event
: e
);
return eventReducer({ ...state, events: eventsWithResolved }, { type: 'SET_EVENTS', payload: eventsWithResolved });
case 'CLEAR_EVENTS':
return initialState;
default:
return state;
}
};
// Context types
interface EventContextType extends EventState {
addEvent: (event: Event) => void;
updateEvent: (event: Event) => void;
removeEvent: (eventId: string) => void;
setEvents: (events: Event[]) => void;
markAsRead: (eventId: string) => void;
acknowledgeEvent: (eventId: string) => void;
resolveEvent: (eventId: string) => void;
clearEvents: () => void;
}
// Create context
const EventContext = createContext<EventContextType | undefined>(undefined);
// Provider component
interface EventProviderProps {
children: ReactNode;
}
export const EventProvider: React.FC<EventProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(eventReducer, initialState);
const value = {
...state,
addEvent: (event: Event) => dispatch({ type: 'ADD_EVENT', payload: event }),
updateEvent: (event: Event) => dispatch({ type: 'UPDATE_EVENT', payload: event }),
removeEvent: (eventId: string) => dispatch({ type: 'REMOVE_EVENT', payload: eventId }),
setEvents: (events: Event[]) => dispatch({ type: 'SET_EVENTS', payload: events }),
markAsRead: (eventId: string) => dispatch({ type: 'MARK_AS_READ', payload: eventId }),
acknowledgeEvent: (eventId: string) => dispatch({ type: 'ACKNOWLEDGE_EVENT', payload: eventId }),
resolveEvent: (eventId: string) => dispatch({ type: 'RESOLVE_EVENT', payload: eventId }),
clearEvents: () => dispatch({ type: 'CLEAR_EVENTS' }),
};
return <EventContext.Provider value={value}>{children}</EventContext.Provider>;
};
// Hook to use the context
export const useEventContext = (): EventContextType => {
const context = useContext(EventContext);
if (!context) {
throw new Error('useEventContext must be used within an EventProvider');
}
return context;
};