/** * Alerts hook for managing bakery alerts and notifications */ import { useState, useEffect, useCallback } from 'react'; import { InventoryService } from '../../services/api/inventory.service'; import { ProductionService } from '../../services/api/production.service'; import { NotificationService } from '../../services/api/notification.service'; export type AlertType = 'inventory' | 'production' | 'quality' | 'maintenance' | 'safety' | 'system' | 'custom'; export type AlertPriority = 'low' | 'medium' | 'high' | 'critical'; export type AlertStatus = 'active' | 'acknowledged' | 'resolved' | 'dismissed'; export interface Alert { id: string; type: AlertType; priority: AlertPriority; status: AlertStatus; title: string; message: string; details?: Record; source: string; sourceId?: string; createdAt: Date; updatedAt?: Date; acknowledgedAt?: Date; acknowledgedBy?: string; resolvedAt?: Date; resolvedBy?: string; dismissedAt?: Date; dismissedBy?: string; expiresAt?: Date; actions?: { id: string; label: string; action: string; parameters?: Record; }[]; metadata?: Record; } export interface AlertRule { id: string; name: string; type: AlertType; priority: AlertPriority; condition: { field: string; operator: 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'contains' | 'not_contains'; value: any; }[]; actions: string[]; enabled: boolean; cooldownPeriod?: number; // in minutes lastTriggered?: Date; } interface AlertsState { alerts: Alert[]; rules: AlertRule[]; unreadCount: number; criticalCount: number; isLoading: boolean; error: string | null; filters: { types: AlertType[]; priorities: AlertPriority[]; statuses: AlertStatus[]; sources: string[]; }; } interface AlertsActions { // Alert Management fetchAlerts: (filters?: Partial) => Promise; acknowledgeAlert: (id: string) => Promise; resolveAlert: (id: string, resolution?: string) => Promise; dismissAlert: (id: string, reason?: string) => Promise; bulkAcknowledge: (ids: string[]) => Promise; bulkResolve: (ids: string[], resolution?: string) => Promise; bulkDismiss: (ids: string[], reason?: string) => Promise; // Alert Creation createAlert: (alert: Omit) => Promise; createCustomAlert: (title: string, message: string, priority?: AlertPriority, details?: Record) => Promise; // Alert Rules fetchAlertRules: () => Promise; createAlertRule: (rule: Omit) => Promise; updateAlertRule: (id: string, rule: Partial) => Promise; deleteAlertRule: (id: string) => Promise; testAlertRule: (rule: AlertRule) => Promise; // Monitoring and Checks checkInventoryAlerts: () => Promise; checkProductionAlerts: () => Promise; checkQualityAlerts: () => Promise; checkMaintenanceAlerts: () => Promise; checkSystemAlerts: () => Promise; // Analytics getAlertAnalytics: (period: string) => Promise; getAlertTrends: (days: number) => Promise; getMostFrequentAlerts: (period: string) => Promise; // Filters and Search setFilters: (filters: Partial) => void; searchAlerts: (query: string) => Promise; // Real-time Updates subscribeToAlerts: (callback: (alert: Alert) => void) => () => void; // Utilities clearError: () => void; refresh: () => Promise; } export const useAlerts = (): AlertsState & AlertsActions => { const [state, setState] = useState({ alerts: [], rules: [], unreadCount: 0, criticalCount: 0, isLoading: false, error: null, filters: { types: [], priorities: [], statuses: [], sources: [], }, }); const inventoryService = new InventoryService(); const productionService = new ProductionService(); const notificationService = new NotificationService(); // Fetch alerts const fetchAlerts = useCallback(async (filters?: Partial) => { setState(prev => ({ ...prev, isLoading: true, error: null })); try { // Combine filters const activeFilters = { ...state.filters, ...filters }; // Fetch alerts from different sources const [inventoryAlerts, productionAlerts, systemAlerts] = await Promise.all([ getInventoryAlerts(activeFilters), getProductionAlerts(activeFilters), getSystemAlerts(activeFilters), ]); const allAlerts = [...inventoryAlerts, ...productionAlerts, ...systemAlerts] .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); // Calculate counts const unreadCount = allAlerts.filter(alert => alert.status === 'active').length; const criticalCount = allAlerts.filter(alert => alert.priority === 'critical' && alert.status === 'active').length; setState(prev => ({ ...prev, alerts: allAlerts, unreadCount, criticalCount, isLoading: false, filters: activeFilters, })); } catch (error) { setState(prev => ({ ...prev, isLoading: false, error: 'Error al cargar alertas', })); } }, [state.filters]); // Get inventory alerts const getInventoryAlerts = async (filters: Partial): Promise => { try { const response = await inventoryService.getAlerts(); if (response.success && response.data) { return response.data.map((alert: any) => convertToAlert(alert, 'inventory')); } } catch (error) { console.error('Error fetching inventory alerts:', error); } return []; }; // Get production alerts const getProductionAlerts = async (filters: Partial): Promise => { try { const response = await productionService.getAlerts?.(); if (response?.success && response.data) { return response.data.map((alert: any) => convertToAlert(alert, 'production')); } } catch (error) { console.error('Error fetching production alerts:', error); } return []; }; // Get system alerts const getSystemAlerts = async (filters: Partial): Promise => { try { const response = await notificationService.getNotifications(); if (response.success && response.data) { return response.data .filter((notif: any) => notif.type === 'alert') .map((alert: any) => convertToAlert(alert, 'system')); } } catch (error) { console.error('Error fetching system alerts:', error); } return []; }; // Convert API response to Alert format const convertToAlert = (apiAlert: any, source: string): Alert => { return { id: apiAlert.id, type: apiAlert.type || (source as AlertType), priority: mapPriority(apiAlert.priority || apiAlert.severity), status: mapStatus(apiAlert.status), title: apiAlert.title || apiAlert.message?.substring(0, 50) || 'Alert', message: apiAlert.message || apiAlert.description || '', details: apiAlert.details || apiAlert.data, source, sourceId: apiAlert.source_id, createdAt: new Date(apiAlert.created_at || Date.now()), updatedAt: apiAlert.updated_at ? new Date(apiAlert.updated_at) : undefined, acknowledgedAt: apiAlert.acknowledged_at ? new Date(apiAlert.acknowledged_at) : undefined, acknowledgedBy: apiAlert.acknowledged_by, resolvedAt: apiAlert.resolved_at ? new Date(apiAlert.resolved_at) : undefined, resolvedBy: apiAlert.resolved_by, dismissedAt: apiAlert.dismissed_at ? new Date(apiAlert.dismissed_at) : undefined, dismissedBy: apiAlert.dismissed_by, expiresAt: apiAlert.expires_at ? new Date(apiAlert.expires_at) : undefined, actions: apiAlert.actions || [], metadata: apiAlert.metadata || {}, }; }; // Map priority from different sources const mapPriority = (priority: string): AlertPriority => { const priorityMap: Record = { 'low': 'low', 'medium': 'medium', 'high': 'high', 'critical': 'critical', 'urgent': 'critical', 'warning': 'medium', 'error': 'high', 'info': 'low', }; return priorityMap[priority?.toLowerCase()] || 'medium'; }; // Map status from different sources const mapStatus = (status: string): AlertStatus => { const statusMap: Record = { 'active': 'active', 'new': 'active', 'open': 'active', 'acknowledged': 'acknowledged', 'ack': 'acknowledged', 'resolved': 'resolved', 'closed': 'resolved', 'dismissed': 'dismissed', 'ignored': 'dismissed', }; return statusMap[status?.toLowerCase()] || 'active'; }; // Acknowledge alert const acknowledgeAlert = useCallback(async (id: string): Promise => { setState(prev => ({ ...prev, error: null })); try { const alert = state.alerts.find(a => a.id === id); if (!alert) return false; // Call appropriate service based on source let success = false; if (alert.source === 'inventory') { const response = await inventoryService.markAlertAsRead(id); success = response.success; } if (success) { setState(prev => ({ ...prev, alerts: prev.alerts.map(a => a.id === id ? { ...a, status: 'acknowledged', acknowledgedAt: new Date() } : a ), unreadCount: Math.max(0, prev.unreadCount - 1), })); } return success; } catch (error) { setState(prev => ({ ...prev, error: 'Error al confirmar alerta' })); return false; } }, [state.alerts, inventoryService]); // Resolve alert const resolveAlert = useCallback(async (id: string, resolution?: string): Promise => { setState(prev => ({ ...prev, error: null })); try { const alert = state.alerts.find(a => a.id === id); if (!alert) return false; // Call appropriate service based on source let success = false; if (alert.source === 'inventory') { const response = await inventoryService.dismissAlert(id); success = response.success; } if (success) { setState(prev => ({ ...prev, alerts: prev.alerts.map(a => a.id === id ? { ...a, status: 'resolved', resolvedAt: new Date(), metadata: { ...a.metadata, resolution } } : a ), unreadCount: a.status === 'active' ? Math.max(0, prev.unreadCount - 1) : prev.unreadCount, criticalCount: a.priority === 'critical' && a.status === 'active' ? Math.max(0, prev.criticalCount - 1) : prev.criticalCount, })); } return success; } catch (error) { setState(prev => ({ ...prev, error: 'Error al resolver alerta' })); return false; } }, [state.alerts, inventoryService]); // Dismiss alert const dismissAlert = useCallback(async (id: string, reason?: string): Promise => { setState(prev => ({ ...prev, error: null })); try { const alert = state.alerts.find(a => a.id === id); if (!alert) return false; // Call appropriate service based on source let success = false; if (alert.source === 'inventory') { const response = await inventoryService.dismissAlert(id); success = response.success; } if (success) { setState(prev => ({ ...prev, alerts: prev.alerts.map(a => a.id === id ? { ...a, status: 'dismissed', dismissedAt: new Date(), metadata: { ...a.metadata, dismissReason: reason } } : a ), })); } return success; } catch (error) { setState(prev => ({ ...prev, error: 'Error al descartar alerta' })); return false; } }, [state.alerts, inventoryService]); // Bulk acknowledge const bulkAcknowledge = useCallback(async (ids: string[]): Promise => { const results = await Promise.all(ids.map(id => acknowledgeAlert(id))); return results.every(result => result); }, [acknowledgeAlert]); // Bulk resolve const bulkResolve = useCallback(async (ids: string[], resolution?: string): Promise => { const results = await Promise.all(ids.map(id => resolveAlert(id, resolution))); return results.every(result => result); }, [resolveAlert]); // Bulk dismiss const bulkDismiss = useCallback(async (ids: string[], reason?: string): Promise => { const results = await Promise.all(ids.map(id => dismissAlert(id, reason))); return results.every(result => result); }, [dismissAlert]); // Create alert const createAlert = useCallback(async (alert: Omit): Promise => { setState(prev => ({ ...prev, error: null })); try { const newAlert: Alert = { ...alert, id: generateAlertId(), status: 'active', createdAt: new Date(), }; setState(prev => ({ ...prev, alerts: [newAlert, ...prev.alerts], unreadCount: prev.unreadCount + 1, criticalCount: newAlert.priority === 'critical' ? prev.criticalCount + 1 : prev.criticalCount, })); return true; } catch (error) { setState(prev => ({ ...prev, error: 'Error al crear alerta' })); return false; } }, []); // Create custom alert const createCustomAlert = useCallback(async ( title: string, message: string, priority: AlertPriority = 'medium', details?: Record ): Promise => { return createAlert({ type: 'custom', priority, title, message, details, source: 'user', }); }, [createAlert]); // Check inventory alerts const checkInventoryAlerts = useCallback(async () => { try { // Check for low stock const stockResponse = await inventoryService.getStockLevels(); if (stockResponse.success && stockResponse.data) { const lowStockItems = stockResponse.data.filter((item: any) => item.current_quantity <= item.reorder_point ); for (const item of lowStockItems) { await createAlert({ type: 'inventory', priority: item.current_quantity === 0 ? 'critical' : 'high', title: 'Stock bajo', message: `Stock bajo para ${item.ingredient?.name}: ${item.current_quantity} ${item.unit}`, source: 'inventory', sourceId: item.id, details: { item }, }); } } // Check for expiring items const expirationResponse = await inventoryService.getExpirationReport(); if (expirationResponse?.success && expirationResponse.data) { const expiringItems = expirationResponse.data.expiring_soon || []; for (const item of expiringItems) { await createAlert({ type: 'inventory', priority: 'medium', title: 'Producto próximo a expirar', message: `${item.name} expira el ${item.expiration_date}`, source: 'inventory', sourceId: item.id, details: { item }, }); } } } catch (error) { console.error('Error checking inventory alerts:', error); } }, [inventoryService, createAlert]); // Generate unique ID const generateAlertId = (): string => { return `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; // Set filters const setFilters = useCallback((filters: Partial) => { setState(prev => ({ ...prev, filters: { ...prev.filters, ...filters }, })); }, []); // Clear error const clearError = useCallback(() => { setState(prev => ({ ...prev, error: null })); }, []); // Refresh alerts const refresh = useCallback(async () => { await fetchAlerts(state.filters); }, [fetchAlerts, state.filters]); // Placeholder implementations for remaining functions const fetchAlertRules = useCallback(async () => { setState(prev => ({ ...prev, rules: [] })); }, []); const createAlertRule = useCallback(async (rule: Omit): Promise => { return true; }, []); const updateAlertRule = useCallback(async (id: string, rule: Partial): Promise => { return true; }, []); const deleteAlertRule = useCallback(async (id: string): Promise => { return true; }, []); const testAlertRule = useCallback(async (rule: AlertRule): Promise => { return true; }, []); const checkProductionAlerts = useCallback(async () => { // Implementation for production alerts }, []); const checkQualityAlerts = useCallback(async () => { // Implementation for quality alerts }, []); const checkMaintenanceAlerts = useCallback(async () => { // Implementation for maintenance alerts }, []); const checkSystemAlerts = useCallback(async () => { // Implementation for system alerts }, []); const getAlertAnalytics = useCallback(async (period: string): Promise => { return {}; }, []); const getAlertTrends = useCallback(async (days: number): Promise => { return {}; }, []); const getMostFrequentAlerts = useCallback(async (period: string): Promise => { return {}; }, []); const searchAlerts = useCallback(async (query: string): Promise => { return state.alerts.filter(alert => alert.title.toLowerCase().includes(query.toLowerCase()) || alert.message.toLowerCase().includes(query.toLowerCase()) ); }, [state.alerts]); const subscribeToAlerts = useCallback((callback: (alert: Alert) => void): (() => void) => { // Implementation would set up real-time subscription return () => { // Cleanup subscription }; }, []); // Initialize alerts on mount useEffect(() => { fetchAlerts(); }, []); // Set up periodic checks useEffect(() => { const interval = setInterval(() => { checkInventoryAlerts(); }, 5 * 60 * 1000); // Check every 5 minutes return () => clearInterval(interval); }, [checkInventoryAlerts]); return { ...state, fetchAlerts, acknowledgeAlert, resolveAlert, dismissAlert, bulkAcknowledge, bulkResolve, bulkDismiss, createAlert, createCustomAlert, fetchAlertRules, createAlertRule, updateAlertRule, deleteAlertRule, testAlertRule, checkInventoryAlerts, checkProductionAlerts, checkQualityAlerts, checkMaintenanceAlerts, checkSystemAlerts, getAlertAnalytics, getAlertTrends, getMostFrequentAlerts, setFilters, searchAlerts, subscribeToAlerts, clearError, refresh, }; };