162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
// Real API hook for Critical Alerts using backend forecast alerts
|
|
import { useState, useCallback, useEffect } from 'react';
|
|
import { useForecast } from '../api';
|
|
import { useTenantId } from './useTenantId';
|
|
|
|
export interface RealAlert {
|
|
id: string;
|
|
type: 'stock' | 'weather' | 'order' | 'production' | 'system';
|
|
severity: 'high' | 'medium' | 'low';
|
|
title: string;
|
|
description: string;
|
|
action?: string;
|
|
time: string;
|
|
}
|
|
|
|
export const useRealAlerts = () => {
|
|
const [alerts, setAlerts] = useState<RealAlert[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const { tenantId } = useTenantId();
|
|
const { getForecastAlerts, acknowledgeForecastAlert } = useForecast();
|
|
|
|
// Transform backend forecast alerts to frontend alert format
|
|
const transformForecastAlert = (alert: any): RealAlert => {
|
|
// Map alert types
|
|
let type: RealAlert['type'] = 'system';
|
|
if (alert.alert_type?.includes('stock') || alert.alert_type?.includes('demand')) {
|
|
type = 'stock';
|
|
} else if (alert.alert_type?.includes('weather')) {
|
|
type = 'weather';
|
|
} else if (alert.alert_type?.includes('production')) {
|
|
type = 'production';
|
|
}
|
|
|
|
// Map severity
|
|
let severity: RealAlert['severity'] = 'medium';
|
|
if (alert.severity === 'critical' || alert.severity === 'high') {
|
|
severity = 'high';
|
|
} else if (alert.severity === 'low') {
|
|
severity = 'low';
|
|
}
|
|
|
|
// Generate user-friendly title and description
|
|
let title = alert.message;
|
|
let description = alert.message;
|
|
|
|
if (alert.alert_type?.includes('high_demand')) {
|
|
title = 'Alta Demanda Prevista';
|
|
description = `Se prevé alta demanda. ${alert.message}`;
|
|
} else if (alert.alert_type?.includes('low_confidence')) {
|
|
title = 'Predicción Incierta';
|
|
description = `Baja confianza en predicción. ${alert.message}`;
|
|
} else if (alert.alert_type?.includes('stock_risk')) {
|
|
title = 'Riesgo de Desabastecimiento';
|
|
description = `Posible falta de stock. ${alert.message}`;
|
|
}
|
|
|
|
return {
|
|
id: alert.id,
|
|
type,
|
|
severity,
|
|
title,
|
|
description,
|
|
action: 'Ver detalles',
|
|
time: new Date(alert.created_at).toLocaleTimeString('es-ES', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
};
|
|
};
|
|
|
|
// Load real alerts from backend
|
|
const loadAlerts = useCallback(async () => {
|
|
if (!tenantId) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Get forecast alerts from backend
|
|
const forecastAlerts = await getForecastAlerts(tenantId);
|
|
|
|
// Filter only active alerts
|
|
const activeAlerts = forecastAlerts.filter(alert => alert.is_active);
|
|
|
|
// Transform to frontend format
|
|
const transformedAlerts = activeAlerts.map(transformForecastAlert);
|
|
|
|
// Sort by severity and time (most recent first)
|
|
transformedAlerts.sort((a, b) => {
|
|
// First by severity (high > medium > low)
|
|
const severityOrder = { high: 3, medium: 2, low: 1 };
|
|
const severityDiff = severityOrder[b.severity] - severityOrder[a.severity];
|
|
if (severityDiff !== 0) return severityDiff;
|
|
|
|
// Then by time (most recent first)
|
|
return b.time.localeCompare(a.time);
|
|
});
|
|
|
|
setAlerts(transformedAlerts.slice(0, 3)); // Show max 3 alerts in dashboard
|
|
} catch (error) {
|
|
console.error('Error loading alerts:', error);
|
|
setError(error instanceof Error ? error.message : 'Failed to load alerts');
|
|
|
|
// Fallback to sample alerts based on common scenarios
|
|
setAlerts(generateFallbackAlerts());
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [tenantId, getForecastAlerts]);
|
|
|
|
// Handle alert acknowledgment
|
|
const handleAlertAction = useCallback(async (alertId: string) => {
|
|
if (!tenantId) return;
|
|
|
|
try {
|
|
await acknowledgeForecastAlert(tenantId, alertId);
|
|
// Remove acknowledged alert from local state
|
|
setAlerts(prev => prev.filter(alert => alert.id !== alertId));
|
|
} catch (error) {
|
|
console.error('Error acknowledging alert:', error);
|
|
}
|
|
}, [tenantId, acknowledgeForecastAlert]);
|
|
|
|
// Generate fallback alerts when API fails
|
|
const generateFallbackAlerts = (): RealAlert[] => {
|
|
const now = new Date();
|
|
const timeString = now.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
|
|
|
|
return [
|
|
{
|
|
id: 'fallback-1',
|
|
type: 'stock',
|
|
severity: 'high',
|
|
title: 'Stock Bajo de Croissants',
|
|
description: 'Se prevé alta demanda este fin de semana',
|
|
action: 'Aumentar producción',
|
|
time: timeString
|
|
}
|
|
];
|
|
};
|
|
|
|
// Load alerts on mount and when tenant changes
|
|
useEffect(() => {
|
|
loadAlerts();
|
|
|
|
// Refresh alerts every 5 minutes
|
|
const interval = setInterval(loadAlerts, 5 * 60 * 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [loadAlerts]);
|
|
|
|
return {
|
|
alerts,
|
|
isLoading,
|
|
error,
|
|
onAlertAction: handleAlertAction,
|
|
reload: loadAlerts,
|
|
clearError: () => setError(null),
|
|
};
|
|
}; |