diff --git a/frontend/src/components/dashboard/EnhancedDashboard.tsx b/frontend/src/components/dashboard/EnhancedDashboard.tsx deleted file mode 100644 index ff7da32c..00000000 --- a/frontend/src/components/dashboard/EnhancedDashboard.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import React from 'react'; -import { useTenantId } from '../../hooks/useTenantId'; -import { TodayProductionBlock } from './TodayProductionBlock'; -import { TomorrowOrdersBlock } from './TomorrowOrdersBlock'; - -// Import existing simplified components for comparison -import TodayRevenue from '../simple/TodayRevenue'; -import QuickActions from '../simple/QuickActions'; -import WeatherContext from '../simple/WeatherContext'; - -interface EnhancedDashboardProps { - onNavigateToOrders?: () => void; - onNavigateToReports?: () => void; - onNavigateToProduction?: () => void; - onNavigateToInventory?: () => void; - onNavigateToRecipes?: () => void; - onNavigateToSales?: () => void; -} - -export const EnhancedDashboard: React.FC = ({ - onNavigateToOrders, - onNavigateToReports, - onNavigateToProduction, - onNavigateToInventory, - onNavigateToRecipes, - onNavigateToSales -}) => { - const [isLoading, setIsLoading] = React.useState(true); - const [error, setError] = React.useState(null); - const [metrics, setMetrics] = React.useState(null); - const [weather, setWeather] = React.useState(null); - const { tenantId } = useTenantId(); - - // Simplified dashboard data loading without complex forecasting - React.useEffect(() => { - const loadBasicData = async () => { - if (!tenantId) { - setIsLoading(false); - return; - } - - try { - setIsLoading(true); - setError(null); - - // Load basic metrics without complex operations - const metricsData = { - totalSales: 287.50 // This would come from a simple sales API call - }; - setMetrics(metricsData); - - // Skip weather for now to avoid blocking - setWeather(null); - - } catch (err) { - console.error('Error loading dashboard data:', err); - setError('Error al cargar datos básicos'); - } finally { - setIsLoading(false); - } - }; - - loadBasicData(); - }, [tenantId]); - - const reload = () => { - window.location.reload(); - }; - - // Helper function for greeting - const getGreeting = () => { - const hour = new Date().getHours(); - if (hour < 12) return 'Buenos días'; - if (hour < 18) return 'Buenas tardes'; - return 'Buenas noches'; - }; - - const getWelcomeMessage = () => { - const hour = new Date().getHours(); - if (hour < 6) return 'Trabajo nocturno en la panadería'; - if (hour < 12) return 'Comenzando el día en la panadería'; - if (hour < 18) return 'Continuando con las operaciones'; - return 'Finalizando las operaciones del día'; - }; - - if (isLoading) { - return ( -
-
-
-
-

Cargando panel de control...

- -
-
-
- ); - } - - if (error) { - return ( -
-
-

Error al cargar el panel

-

{error}

- -
-
- ); - } - - return ( -
-
- {/* Enhanced Welcome Header */} -
-
-
-
-
🥖
-
-

- {getGreeting()}! 👋 -

-

- {getWelcomeMessage()} • {new Date().toLocaleDateString('es-ES', { - weekday: 'long', - day: 'numeric', - month: 'long', - year: 'numeric' - })} -

-
-
-
- -
- {/* Weather Widget */} - {weather && ( -
- - {weather.precipitation && weather.precipitation > 0 ? '🌧️' : weather.temperature && weather.temperature > 20 ? '☀️' : '⛅'} - -
- {weather.temperature?.toFixed(1) || '--'}°C - AEMET Madrid -
-
- )} - - {/* System Status */} -
-
Sistema
-
-
- Operativo -
-
-
-
-
- - {/* Primary KPIs Row - Most Critical Information */} -
- {/* Today's Revenue - Financial KPI */} - - - {/* Quick Actions - Operational Access */} - { - console.log('Action clicked:', actionId); - switch (actionId) { - case 'view_orders': - onNavigateToOrders?.(); - break; - case 'view_sales': - onNavigateToReports?.(); - break; - case 'view_production': - onNavigateToProduction?.(); - break; - case 'view_inventory': - onNavigateToInventory?.(); - break; - default: - break; - } - }} - className="lg:col-span-1" - /> -
- - {/* Core Operations Row - Production & Orders */} -
- {/* Today's Production Requirements */} - - - {/* Tomorrow's Order Requirements */} - -
- - {/* Secondary Information Row */} -
- {/* Weather Context & Recommendations */} - - - {/* Real-time Alerts - Using existing AlertDashboard component */} -
-
-

Sistema de Alertas

-

- Monitoreo en tiempo real de operaciones críticas -

-
-
-
-

Sistema Operativo

-

- No hay alertas críticas en este momento -

-
-
-
Producción
-
- 🟢 Operativo -
-
-
-
Inventario
-
- 🟢 Normal -
-
-
-
Equipos
-
- 🟢 Funcionando -
-
-
-
-
-
- - {/* Success Status Footer */} -
-
-
-
🎯
-
-

Panel de Control Operativo

-

- Todos los sistemas funcionando correctamente • Última actualización: {new Date().toLocaleTimeString('es-ES')} -

-
-
- - {/* Quick Performance Indicators */} -
-
-
Eficiencia
-
94%
-
-
-
Calidad
-
98%
-
-
-
Satisfacción
-
4.8★
-
-
-
-
- - {/* Navigation Quick Links */} -
-

Acceso Rápido a Secciones

-
- - - - - - -
-
-
-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/dashboard/TodayProductionBlock.tsx b/frontend/src/components/dashboard/TodayProductionBlock.tsx deleted file mode 100644 index 885364ed..00000000 --- a/frontend/src/components/dashboard/TodayProductionBlock.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { apiClient } from '../../api/client'; -import { useTenantId } from '../../hooks/useTenantId'; - -interface ProductionRequirement { - id: string; - recipe_name: string; - quantity: number; - unit: string; - priority: 'high' | 'medium' | 'low'; - scheduled_time: string; - status: 'pending' | 'in_progress' | 'completed' | 'delayed'; - progress: number; - equipment_needed?: string; - estimated_completion?: string; -} - -interface TodayProductionBlockProps { - className?: string; -} - -export const TodayProductionBlock: React.FC = ({ - className = '' -}) => { - const { tenantId } = useTenantId(); - const [requirements, setRequirements] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [refreshKey, setRefreshKey] = useState(0); - - const fetchTodayProduction = async () => { - if (!tenantId) { - setIsLoading(false); - return; - } - - try { - setIsLoading(true); - setError(null); - - // Use the correct API endpoint for daily production requirements - const today = new Date().toISOString().split('T')[0]; - - // Use the API client for proper authentication and error handling - const data = await apiClient.get(`/tenants/${tenantId}/production/daily-requirements`, { - params: { date: today } - }); - - // Transform API data to component format based on the actual API response structure - const transformedRequirements: ProductionRequirement[] = data.production_items?.map((item: any) => ({ - id: item.id || item.batch_id || `req-${Math.random()}`, - recipe_name: item.recipe_name || item.product_name || 'Producto sin nombre', - quantity: item.quantity || item.required_quantity || 0, - unit: item.unit || 'unidades', - priority: item.priority || 'medium', - scheduled_time: item.scheduled_time || item.start_time || '08:00', - status: item.status || 'pending', - progress: item.progress || 0, - equipment_needed: item.equipment_name || item.equipment, - estimated_completion: item.estimated_completion || item.end_time - })) || []; - - setRequirements(transformedRequirements); - } catch (err) { - console.error('Error fetching production data:', err); - setError('No hay datos de producción disponibles para hoy'); - setRequirements([]); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchTodayProduction(); - }, [tenantId, refreshKey]); - - const handleRefresh = () => { - setRefreshKey(prev => prev + 1); - }; - - const handleUpdateStatus = async (itemId: string, newStatus: 'pending' | 'in_progress' | 'completed' | 'delayed') => { - try { - // Update locally for immediate feedback - setRequirements(prev => prev.map(req => - req.id === itemId ? { ...req, status: newStatus } : req - )); - - // API call would go here - // await productionService.updateBatchStatus(itemId, { status: newStatus }); - } catch (err) { - console.error('Error updating status:', err); - // Revert on error - fetchTodayProduction(); - } - }; - - const getStatusColor = (status: string) => { - switch (status) { - case 'completed': return 'text-green-700 bg-green-100'; - case 'in_progress': return 'text-blue-700 bg-blue-100'; - case 'delayed': return 'text-red-700 bg-red-100'; - default: return 'text-gray-700 bg-gray-100'; - } - }; - - const getPriorityIcon = (priority: string) => { - switch (priority) { - case 'high': return '🔥'; - case 'medium': return '⚡'; - case 'low': return '📋'; - default: return '📋'; - } - }; - - const getProductEmoji = (productName: string) => { - const name = productName.toLowerCase(); - if (name.includes('croissant')) return '🥐'; - if (name.includes('pan') || name.includes('bread')) return '🍞'; - if (name.includes('magdalena') || name.includes('muffin')) return '🧁'; - if (name.includes('tarta') || name.includes('cake')) return '🎂'; - if (name.includes('cookie') || name.includes('galleta')) return '🍪'; - return '🥖'; - }; - - const completedCount = requirements.filter(r => r.status === 'completed').length; - const inProgressCount = requirements.filter(r => r.status === 'in_progress').length; - const pendingCount = requirements.filter(r => r.status === 'pending').length; - const delayedCount = requirements.filter(r => r.status === 'delayed').length; - - if (isLoading) { - return ( -
-
-

¿Qué debo producir hoy?

-
-
-
- {[1, 2, 3].map(i => ( -
-
-
-
-
-
-
- ))} -
-
- ); - } - - return ( -
-
- {/* Header */} -
-
-

¿Qué debo producir hoy?

-

- {new Date().toLocaleDateString('es-ES', { - weekday: 'long', - day: 'numeric', - month: 'long' - })} -

-
- -
- - {/* Stats Overview */} -
-
-
{completedCount}
-
Completado
-
-
-
-
-
-
{inProgressCount}
-
En Proceso
-
-
-
-
-
-
{pendingCount}
-
Pendiente
-
-
-
-
-
-
{delayedCount}
-
Retrasado
-
-
-
-
-
- - {/* Error State */} - {error && ( -
-
-
- - - -
-
-

{error}

-
-
-
- )} - - {/* Production Requirements List */} - {requirements.length === 0 ? ( -
-
🎉
-

¡Sin producción programada!

-

- No hay elementos de producción para hoy. -

-
- ) : ( -
- {requirements.map((requirement) => ( -
-
-
- {getProductEmoji(requirement.recipe_name)} -
-
-
-

- {requirement.recipe_name} -

- {getPriorityIcon(requirement.priority)} -
-
-

- {requirement.quantity} {requirement.unit} -

-

- 📅 {requirement.scheduled_time} -

- {requirement.equipment_needed && ( -

- 🔧 {requirement.equipment_needed} -

- )} -
-
-
- -
- - {requirement.status === 'pending' && 'Pendiente'} - {requirement.status === 'in_progress' && 'En Proceso'} - {requirement.status === 'completed' && 'Completado'} - {requirement.status === 'delayed' && 'Retrasado'} - - - {requirement.status !== 'completed' && ( -
- {requirement.status === 'pending' && ( - - )} - {requirement.status === 'in_progress' && ( - - )} -
- )} -
-
- ))} -
- )} - - {/* Quick Actions */} - {requirements.length > 0 && ( -
-
-
- Total: {requirements.length} elementos de producción -
-
- - -
-
-
- )} -
-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/dashboard/TomorrowOrdersBlock.tsx b/frontend/src/components/dashboard/TomorrowOrdersBlock.tsx deleted file mode 100644 index 965f6f92..00000000 --- a/frontend/src/components/dashboard/TomorrowOrdersBlock.tsx +++ /dev/null @@ -1,409 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { apiClient } from '../../api/client'; -import { useTenantId } from '../../hooks/useTenantId'; - -interface OrderRequirement { - id: string; - ingredient_name: string; - required_quantity: number; - current_stock: number; - net_requirement: number; - unit: string; - priority: 'critical' | 'high' | 'medium' | 'low'; - supplier_name?: string; - estimated_cost?: number; - required_by_date: string; - category: string; - status: 'needed' | 'ordered' | 'confirmed' | 'delivered'; -} - -interface TomorrowOrdersBlockProps { - className?: string; -} - -export const TomorrowOrdersBlock: React.FC = ({ - className = '' -}) => { - const { tenantId } = useTenantId(); - const [requirements, setRequirements] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [refreshKey, setRefreshKey] = useState(0); - - const fetchTomorrowOrders = async () => { - if (!tenantId) { - setIsLoading(false); - return; - } - - try { - setIsLoading(true); - setError(null); - - // Get tomorrow's date - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - const tomorrowDateString = tomorrow.toISOString().split('T')[0]; - - // Use the API client for proper authentication and error handling - const data = await apiClient.get(`/tenants/${tenantId}/orders/demand-requirements`, { - params: { target_date: tomorrowDateString } - }); - - // Transform API data to component format based on the actual API response structure - const transformedRequirements: OrderRequirement[] = data.procurement_requirements?.map((item: any) => ({ - id: item.id || `ord-${Math.random()}`, - ingredient_name: item.ingredient_name || item.product_name || 'Ingrediente sin nombre', - required_quantity: item.required_quantity || 0, - current_stock: item.current_stock_level || item.current_stock || 0, - net_requirement: item.net_requirement || Math.max(0, (item.required_quantity || 0) - (item.current_stock_level || 0)), - unit: item.unit || 'kg', - priority: item.priority || 'medium', - supplier_name: item.supplier_name, - estimated_cost: item.estimated_cost, - required_by_date: item.required_by_date || tomorrowDateString, - category: item.category || 'ingredientes', - status: item.status || 'needed' - })) || []; - - setRequirements(transformedRequirements); - } catch (err) { - console.error('Error fetching orders data:', err); - setError('No hay datos de pedidos disponibles para mañana'); - setRequirements([]); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchTomorrowOrders(); - }, [tenantId, refreshKey]); - - const handleRefresh = () => { - setRefreshKey(prev => prev + 1); - }; - - const handleUpdateStatus = async (itemId: string, newStatus: 'needed' | 'ordered' | 'confirmed' | 'delivered') => { - try { - // Update locally for immediate feedback - setRequirements(prev => prev.map(req => - req.id === itemId ? { ...req, status: newStatus } : req - )); - - // API call would go here - // await ordersService.updateProcurementStatus(itemId, { status: newStatus }); - } catch (err) { - console.error('Error updating status:', err); - // Revert on error - fetchTomorrowOrders(); - } - }; - - const getStatusColor = (status: string) => { - switch (status) { - case 'delivered': return 'text-green-700 bg-green-100'; - case 'confirmed': return 'text-blue-700 bg-blue-100'; - case 'ordered': return 'text-yellow-700 bg-yellow-100'; - default: return 'text-red-700 bg-red-100'; - } - }; - - const getPriorityIcon = (priority: string) => { - switch (priority) { - case 'critical': return '🚨'; - case 'high': return '🔴'; - case 'medium': return '🟡'; - case 'low': return '🟢'; - default: return '📦'; - } - }; - - const getCategoryEmoji = (category: string) => { - switch (category.toLowerCase()) { - case 'harinas': return '🌾'; - case 'lácteos': return '🥛'; - case 'fermentos': return '🧪'; - case 'azúcares': return '🍯'; - case 'grasas': return '🧈'; - case 'especias': return '🌿'; - case 'frutas': return '🍓'; - case 'chocolate': return '🍫'; - case 'empaques': return '📦'; - default: return '🥄'; - } - }; - - const getStockStatus = (current: number, required: number, netRequirement: number) => { - if (netRequirement <= 0) return { status: 'sufficient', color: 'text-green-600', text: 'Stock suficiente' }; - if (current === 0) return { status: 'critical', color: 'text-red-600', text: 'Sin stock' }; - if (current < required * 0.5) return { status: 'low', color: 'text-orange-600', text: 'Stock bajo' }; - return { status: 'moderate', color: 'text-yellow-600', text: 'Stock moderado' }; - }; - - const neededCount = requirements.filter(r => r.status === 'needed').length; - const orderedCount = requirements.filter(r => r.status === 'ordered').length; - const confirmedCount = requirements.filter(r => r.status === 'confirmed').length; - const deliveredCount = requirements.filter(r => r.status === 'delivered').length; - - const totalCost = requirements.reduce((sum, req) => sum + (req.estimated_cost || 0), 0); - const criticalItems = requirements.filter(r => r.priority === 'critical' && r.status === 'needed').length; - - if (isLoading) { - return ( -
-
-

¿Qué debo pedir para mañana?

-
-
-
- {[1, 2, 3].map(i => ( -
-
-
-
-
-
-
- ))} -
-
- ); - } - - return ( -
-
- {/* Header */} -
-
-

¿Qué debo pedir para mañana?

-

- Planificación para {new Date(Date.now() + 24*60*60*1000).toLocaleDateString('es-ES', { - weekday: 'long', - day: 'numeric', - month: 'long' - })} -

-
- -
- - {/* Stats Overview */} -
-
-
{neededCount}
-
Por Pedir
-
-
-
{orderedCount}
-
Pedido
-
-
-
{confirmedCount}
-
Confirmado
-
-
-
{deliveredCount}
-
Entregado
-
-
- - {/* Critical Alerts */} - {criticalItems > 0 && ( -
-
-
- - - -
-
-

- ⚠️ {criticalItems} ingrediente{criticalItems !== 1 ? 's' : ''} crítico{criticalItems !== 1 ? 's' : ''} -

-

- Requieren pedido urgente para la producción de mañana -

-
-
-
- )} - - {/* Cost Summary */} -
-
-
-

Costo Estimado Total

-

€{totalCost.toFixed(2)}

-
-
-

{requirements.length} ingredientes

-

Estimación basada en precios anteriores

-
-
-
- - {/* Error State */} - {error && ( -
-
-
- - - -
-
-

- {error} - Mostrando datos de ejemplo -

-
-
-
- )} - - {/* Orders Requirements List */} - {requirements.length === 0 ? ( -
-
-

¡Todo listo para mañana!

-

- No necesitas hacer pedidos adicionales. -

-
- ) : ( -
- {requirements - .sort((a, b) => { - // Sort by priority first (critical -> high -> medium -> low) - const priorityOrder = { 'critical': 0, 'high': 1, 'medium': 2, 'low': 3 }; - const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; - if (priorityDiff !== 0) return priorityDiff; - - // Then by status (needed -> ordered -> confirmed -> delivered) - const statusOrder = { 'needed': 0, 'ordered': 1, 'confirmed': 2, 'delivered': 3 }; - return statusOrder[a.status] - statusOrder[b.status]; - }) - .map((requirement) => { - const stockStatus = getStockStatus(requirement.current_stock, requirement.required_quantity, requirement.net_requirement); - - return ( -
-
-
- {getCategoryEmoji(requirement.category)} -
-
-
-

- {requirement.ingredient_name} -

- {getPriorityIcon(requirement.priority)} -
-
-
- Necesario: {requirement.required_quantity} {requirement.unit} -
-
- Stock: {requirement.current_stock} {requirement.unit} -
- {requirement.net_requirement > 0 && ( -
- ⚠️ Falta: {requirement.net_requirement} {requirement.unit} -
- )} -
-
- {requirement.supplier_name && ( -

- 🏪 {requirement.supplier_name} -

- )} - {requirement.estimated_cost && ( -

- 💰 €{requirement.estimated_cost.toFixed(2)} -

- )} - - {stockStatus.text} - -
-
-
- -
- - {requirement.status === 'needed' && 'Por Pedir'} - {requirement.status === 'ordered' && 'Pedido'} - {requirement.status === 'confirmed' && 'Confirmado'} - {requirement.status === 'delivered' && 'Entregado'} - - - {requirement.status !== 'delivered' && requirement.net_requirement > 0 && ( -
- {requirement.status === 'needed' && ( - - )} - {requirement.status === 'ordered' && ( - - )} -
- )} -
-
- ); - })} -
- )} - - {/* Quick Actions */} - {requirements.length > 0 && ( -
-
-
- Total: {requirements.length} ingredientes • {neededCount} por pedir -
-
- - -
-
-
- )} -
-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/dashboard/index.ts b/frontend/src/components/dashboard/index.ts deleted file mode 100644 index 775d15a0..00000000 --- a/frontend/src/components/dashboard/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { TodayProductionBlock } from './TodayProductionBlock'; -export { TomorrowOrdersBlock } from './TomorrowOrdersBlock'; -export { EnhancedDashboard } from './EnhancedDashboard'; -export { DebugDashboard } from './DebugDashboard'; \ No newline at end of file diff --git a/frontend/src/pages/dashboard/DashboardPage.tsx b/frontend/src/pages/dashboard/DashboardPage.tsx index 8b2073d8..f1afd1be 100644 --- a/frontend/src/pages/dashboard/DashboardPage.tsx +++ b/frontend/src/pages/dashboard/DashboardPage.tsx @@ -2,9 +2,6 @@ import React, { useState } from 'react'; import { useDashboard } from '../../hooks/useDashboard'; import { useOrderSuggestions } from '../../hooks/useOrderSuggestions'; -// Import enhanced dashboard components -import { EnhancedDashboard } from '../../components/dashboard/EnhancedDashboard'; - // Import simplified components for fallback import TodayRevenue from '../../components/simple/TodayRevenue'; import TodayProduction from '../../components/simple/TodayProduction'; @@ -30,7 +27,6 @@ const DashboardPage: React.FC = ({ onNavigateToRecipes, onNavigateToSales }) => { - const [useEnhancedView, setUseEnhancedView] = useState(true); const { weather, tenantId, @@ -82,38 +78,6 @@ const DashboardPage: React.FC = ({ return 'Buenas noches'; }; - // Toggle between enhanced and classic view - const toggleDashboardView = () => { - setUseEnhancedView(!useEnhancedView); - }; - - // Show enhanced dashboard by default - use debug version for stability - if (useEnhancedView) { - return ( -
- {/* Dashboard View Toggle */} -
- -
- - -
- ); - } - // Classic dashboard view if (isLoading) { return ( @@ -143,17 +107,7 @@ const DashboardPage: React.FC = ({ return (
- {/* Dashboard View Toggle */} -
- -
- + {/* Welcome Header */}