Files
bakery-ia/frontend/src/hooks/business/useAlerts.ts
2025-08-28 10:41:04 +02:00

621 lines
19 KiB
TypeScript

/**
* 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<string, any>;
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<string, any>;
}[];
metadata?: Record<string, any>;
}
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<AlertsState['filters']>) => Promise<void>;
acknowledgeAlert: (id: string) => Promise<boolean>;
resolveAlert: (id: string, resolution?: string) => Promise<boolean>;
dismissAlert: (id: string, reason?: string) => Promise<boolean>;
bulkAcknowledge: (ids: string[]) => Promise<boolean>;
bulkResolve: (ids: string[], resolution?: string) => Promise<boolean>;
bulkDismiss: (ids: string[], reason?: string) => Promise<boolean>;
// Alert Creation
createAlert: (alert: Omit<Alert, 'id' | 'createdAt' | 'status'>) => Promise<boolean>;
createCustomAlert: (title: string, message: string, priority?: AlertPriority, details?: Record<string, any>) => Promise<boolean>;
// Alert Rules
fetchAlertRules: () => Promise<void>;
createAlertRule: (rule: Omit<AlertRule, 'id'>) => Promise<boolean>;
updateAlertRule: (id: string, rule: Partial<AlertRule>) => Promise<boolean>;
deleteAlertRule: (id: string) => Promise<boolean>;
testAlertRule: (rule: AlertRule) => Promise<boolean>;
// Monitoring and Checks
checkInventoryAlerts: () => Promise<void>;
checkProductionAlerts: () => Promise<void>;
checkQualityAlerts: () => Promise<void>;
checkMaintenanceAlerts: () => Promise<void>;
checkSystemAlerts: () => Promise<void>;
// Analytics
getAlertAnalytics: (period: string) => Promise<any>;
getAlertTrends: (days: number) => Promise<any>;
getMostFrequentAlerts: (period: string) => Promise<any>;
// Filters and Search
setFilters: (filters: Partial<AlertsState['filters']>) => void;
searchAlerts: (query: string) => Promise<Alert[]>;
// Real-time Updates
subscribeToAlerts: (callback: (alert: Alert) => void) => () => void;
// Utilities
clearError: () => void;
refresh: () => Promise<void>;
}
export const useAlerts = (): AlertsState & AlertsActions => {
const [state, setState] = useState<AlertsState>({
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<AlertsState['filters']>) => {
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<AlertsState['filters']>): Promise<Alert[]> => {
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<AlertsState['filters']>): Promise<Alert[]> => {
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<AlertsState['filters']>): Promise<Alert[]> => {
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<string, AlertPriority> = {
'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<string, AlertStatus> = {
'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<boolean> => {
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<boolean> => {
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<boolean> => {
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<boolean> => {
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<boolean> => {
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<boolean> => {
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<Alert, 'id' | 'createdAt' | 'status'>): Promise<boolean> => {
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<string, any>
): Promise<boolean> => {
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<AlertsState['filters']>) => {
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<AlertRule, 'id'>): Promise<boolean> => {
return true;
}, []);
const updateAlertRule = useCallback(async (id: string, rule: Partial<AlertRule>): Promise<boolean> => {
return true;
}, []);
const deleteAlertRule = useCallback(async (id: string): Promise<boolean> => {
return true;
}, []);
const testAlertRule = useCallback(async (rule: AlertRule): Promise<boolean> => {
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<any> => {
return {};
}, []);
const getAlertTrends = useCallback(async (days: number): Promise<any> => {
return {};
}, []);
const getMostFrequentAlerts = useCallback(async (period: string): Promise<any> => {
return {};
}, []);
const searchAlerts = useCallback(async (query: string): Promise<Alert[]> => {
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,
};
};