import React, { useState, useMemo } from 'react'; import { Calendar, TrendingUp, AlertTriangle, BarChart3, Settings, Loader, Zap, Brain, Target, Activity } from 'lucide-react'; import { Button, Card, Badge, StatsGrid, StatusCard, getStatusColor } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; import { LoadingSpinner } from '../../../../components/ui'; import { DemandChart } from '../../../../components/domain/forecasting'; import { useTenantForecasts, useCreateSingleForecast } from '../../../../api/hooks/forecasting'; import { useIngredients } from '../../../../api/hooks/inventory'; import { useModels } from '../../../../api/hooks/training'; import { useTenantId } from '../../../../hooks/useTenantId'; import { ForecastResponse } from '../../../../api/types/forecasting'; import { forecastingService } from '../../../../api/services/forecasting'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; const ForecastingPage: React.FC = () => { const [selectedProduct, setSelectedProduct] = useState(''); const [forecastPeriod, setForecastPeriod] = useState('7'); const [isGenerating, setIsGenerating] = useState(false); const [hasGeneratedForecast, setHasGeneratedForecast] = useState(false); const [currentForecastData, setCurrentForecastData] = useState([]); // Get tenant ID from tenant store const tenantId = useTenantId(); // Calculate date range based on selected period const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - parseInt(forecastPeriod)); // Fetch existing forecasts const { data: forecastsData, isLoading: forecastsLoading, error: forecastsError } = useTenantForecasts(tenantId, { start_date: startDate.toISOString().split('T')[0], end_date: endDate.toISOString().split('T')[0], ...(selectedProduct && { inventory_product_id: selectedProduct }), limit: 100 }, { enabled: !!tenantId && hasGeneratedForecast && !!selectedProduct }); // Fetch real inventory data const { data: ingredientsData, isLoading: ingredientsLoading, error: ingredientsError } = useIngredients(tenantId); // Fetch trained models to filter products const { data: modelsData, isLoading: modelsLoading, error: modelsError } = useModels(tenantId, { active_only: true }); // Forecast generation mutation const createForecastMutation = useCreateSingleForecast({ onSuccess: (data) => { setIsGenerating(false); setHasGeneratedForecast(true); // Store the generated forecast data locally for immediate display setCurrentForecastData([data]); }, onError: (error) => { setIsGenerating(false); console.error('Error generating forecast:', error); }, }); // Build products list from ingredients that have trained models const products = useMemo(() => { if (!ingredientsData || !modelsData?.models) { return []; } // Get inventory product IDs that have trained models const modelProductIds = new Set(modelsData.models.map(model => model.inventory_product_id)); // Filter ingredients to only those with models const ingredientsWithModels = ingredientsData.filter(ingredient => modelProductIds.has(ingredient.id) ); return ingredientsWithModels.map(ingredient => ({ id: ingredient.id, name: ingredient.name, category: ingredient.category, hasModel: true })); }, [ingredientsData, modelsData]); const periods = [ { value: '7', label: '7 días' }, { value: '14', label: '14 días' }, { value: '30', label: '30 días' }, { value: '90', label: '3 meses' }, ]; // Handle forecast generation const handleGenerateForecast = async () => { if (!tenantId || !selectedProduct) { alert('Por favor, selecciona un ingrediente para generar predicciones.'); return; } setIsGenerating(true); try { const today = new Date(); const forecastRequest = { inventory_product_id: selectedProduct, forecast_date: today.toISOString().split('T')[0], forecast_days: parseInt(forecastPeriod), location: 'default', confidence_level: 0.8, }; // Use the new multi-day endpoint for all forecasts const multiDayResult = await forecastingService.createMultiDayForecast(tenantId, forecastRequest); setIsGenerating(false); setHasGeneratedForecast(true); // Use the forecasts from the multi-day response setCurrentForecastData(multiDayResult.forecasts); } catch (error) { console.error('Failed to generate forecast:', error); setIsGenerating(false); } }; // Use either current forecast data or fetched data const forecasts = currentForecastData.length > 0 ? currentForecastData : (forecastsData?.forecasts || []); const isLoading = forecastsLoading || ingredientsLoading || modelsLoading || isGenerating; const hasError = forecastsError || ingredientsError || modelsError; // Calculate metrics from real data const totalDemand = forecasts.reduce((sum, f) => sum + f.predicted_demand, 0); const averageConfidence = forecasts.length > 0 ? Math.round((forecasts.reduce((sum, f) => sum + f.confidence_level, 0) / forecasts.length) * 100) : 0; // Get simplified forecast insights const getForecastInsights = (forecasts: ForecastResponse[]) => { if (!forecasts || forecasts.length === 0) return []; const insights = []; const avgConfidence = forecasts.reduce((sum, f) => sum + f.confidence_level, 0) / forecasts.length; // Model confidence insights.push({ type: 'model', icon: Target, title: 'Confianza del Modelo', description: `${Math.round(avgConfidence * 100)}%`, impact: avgConfidence > 0.8 ? 'positive' : avgConfidence > 0.6 ? 'moderate' : 'high' }); // Algorithm used if (forecasts[0]?.algorithm) { insights.push({ type: 'algorithm', icon: Brain, title: 'Algoritmo', description: forecasts[0].algorithm, impact: 'info' }); } // Forecast period insights.push({ type: 'period', icon: Calendar, title: 'Período', description: `${forecasts.length} días predichos`, impact: 'info' }); return insights; }; const currentInsights = getForecastInsights(forecasts); // Loading and error states - using project patterns if (isLoading || !tenantId) { return (
); } if (hasError) { return (

Error al cargar datos

{forecastsError?.message || ingredientsError?.message || modelsError?.message || 'Ha ocurrido un error inesperado'}

); } return (
{/* Stats Grid - Similar to POSPage */}
{/* Ingredient Selection Section */}
{/* Ingredients Grid - Similar to POSPage products */}

Ingredientes Disponibles ({products.length})

{products.map(product => { const isSelected = selectedProduct === product.id; const getStatusConfig = () => { if (isSelected) { return { color: getStatusColor('completed'), text: 'Seleccionado', icon: Target, isCritical: false, isHighlight: true }; } else { return { color: getStatusColor('pending'), text: 'Disponible', icon: Brain, isCritical: false, isHighlight: false }; } }; return ( setSelectedProduct(product.id) } ]} /> ); })}
{/* Empty State */} {products.length === 0 && (

No hay modelos entrenados

Para generar predicciones, necesitas modelos IA entrenados para tus ingredientes

)}
{/* Configuration and Generation Section */}
{/* Period Selection */}

Configuración

{selectedProduct && (

{products.find(p => p.id === selectedProduct)?.name}

Predicción para {forecastPeriod} días

)}
{/* Generate Forecast */}

Generar Predicción

{!selectedProduct && (

Selecciona un ingrediente para continuar

)}
{/* Results Section */} {hasGeneratedForecast && forecasts.length > 0 && ( <> {/* Results Header */}

Resultados de Predicción

{products.find(p => p.id === selectedProduct)?.name} • {forecastPeriod} días

{/* Chart View */}
{/* Insights */} {currentInsights.length > 0 && (

Factores de Influencia

{currentInsights.map((insight, index) => { const IconComponent = insight.icon; return (

{insight.title}

{insight.description}

); })}
)} )} {/* Help Section - Only when no models available */} {!isLoading && !hasError && products.length === 0 && (

No hay modelos entrenados

Para generar predicciones, necesitas entrenar modelos de IA para tus ingredientes.

)}
); }; export default ForecastingPage;