import React, { useState, useCallback, useEffect } from 'react'; import { Card, Button, Badge, Select, DatePicker } from '../../ui'; import { ChartWidget } from './ChartWidget'; import { ReportsTable } from './ReportsTable'; import { FilterPanel } from './FilterPanel'; import { ExportOptions } from './ExportOptions'; import type { BakeryMetrics, AnalyticsReport, ChartWidget as ChartWidgetType, FilterPanel as FilterPanelType, AppliedFilter, TimeRange, ExportFormat } from './types'; interface AnalyticsDashboardProps { className?: string; initialTimeRange?: TimeRange; showFilters?: boolean; showExport?: boolean; customCharts?: ChartWidgetType[]; onMetricsLoad?: (metrics: BakeryMetrics) => void; onExport?: (format: ExportFormat, options: any) => void; } const DEFAULT_TIME_RANGES: { value: TimeRange; label: string }[] = [ { value: 'today', label: 'Hoy' }, { value: 'yesterday', label: 'Ayer' }, { value: 'thisWeek', label: 'Esta semana' }, { value: 'lastWeek', label: 'Semana pasada' }, { value: 'thisMonth', label: 'Este mes' }, { value: 'lastMonth', label: 'Mes pasado' }, { value: 'thisQuarter', label: 'Este trimestre' }, { value: 'thisYear', label: 'Este año' }, { value: 'custom', label: 'Personalizado' }, ]; export const AnalyticsDashboard: React.FC = ({ className = '', initialTimeRange = 'thisMonth', showFilters = true, showExport = true, customCharts = [], onMetricsLoad, onExport, }) => { const [selectedTimeRange, setSelectedTimeRange] = useState(initialTimeRange); const [customDateRange, setCustomDateRange] = useState<{ from: Date; to: Date } | null>(null); const [bakeryMetrics, setBakeryMetrics] = useState(null); const [reports, setReports] = useState([]); const [appliedFilters, setAppliedFilters] = useState([]); const [loading, setLoading] = useState(false); const [activeView, setActiveView] = useState<'overview' | 'sales' | 'production' | 'inventory' | 'financial' | 'reports'>('overview'); const [isExportModalOpen, setIsExportModalOpen] = useState(false); const [selectedMetricCards, setSelectedMetricCards] = useState([]); const loadAnalyticsData = useCallback(async () => { setLoading(true); try { // Mock data - in real implementation, this would fetch from API const mockMetrics: BakeryMetrics = { sales: { total_revenue: 45250.75, total_orders: 1247, average_order_value: 36.32, revenue_growth: 12.5, order_growth: 8.3, conversion_rate: 67.8, top_products: [ { product_id: 'prod-1', product_name: 'Pan de Molde Integral', category: 'Pan', quantity_sold: 342, revenue: 8540.50, profit_margin: 45.2, growth_rate: 15.3, stock_turns: 12.4 }, { product_id: 'prod-2', product_name: 'Croissant Mantequilla', category: 'Bollería', quantity_sold: 289, revenue: 7225.00, profit_margin: 52.1, growth_rate: 22.7, stock_turns: 8.9 } ], sales_by_channel: [ { channel: 'Tienda Física', revenue: 28500.45, orders: 789, customers: 634, conversion_rate: 72.1, average_order_value: 36.12, growth_rate: 8.5 }, { channel: 'Online', revenue: 16750.30, orders: 458, customers: 412, conversion_rate: 61.3, average_order_value: 36.58, growth_rate: 18.9 } ], hourly_sales: [], seasonal_trends: [] }, production: { total_batches: 156, success_rate: 94.2, waste_percentage: 3.8, production_efficiency: 87.5, quality_score: 92.1, capacity_utilization: 76.3, production_costs: 18750.25, batch_cycle_time: 4.2, top_recipes: [ { recipe_id: 'rec-1', recipe_name: 'Pan de Molde Tradicional', batches_produced: 45, success_rate: 96.8, average_yield: 98.2, cost_per_unit: 1.25, quality_score: 94.5, profitability: 65.2 } ], quality_trends: [] }, inventory: { total_stock_value: 12350.80, turnover_rate: 8.4, days_of_inventory: 12.5, stockout_rate: 2.1, waste_value: 245.30, carrying_costs: 1850.75, reorder_efficiency: 89.3, supplier_performance: [ { supplier_id: 'sup-1', supplier_name: 'Harinas Premium SA', on_time_delivery: 94.2, quality_rating: 4.6, cost_competitiveness: 87.5, total_orders: 24, total_value: 8450.75, reliability_score: 91.8 } ], stock_levels: [], expiry_alerts: [] }, financial: { gross_profit: 26500.25, net_profit: 18750.30, profit_margin: 41.4, cost_of_goods_sold: 18750.50, operating_expenses: 7750.95, break_even_point: 28500.00, cash_flow: 15250.80, roi: 23.8, expense_breakdown: [ { category: 'Ingredientes', amount: 12500.25, percentage: 66.7, change_from_previous: 5.2, budget_variance: -2.1 }, { category: 'Personal', amount: 4250.25, percentage: 22.7, change_from_previous: 8.5, budget_variance: 3.2 }, { category: 'Suministros', amount: 2000.00, percentage: 10.6, change_from_previous: -1.5, budget_variance: -0.8 } ], profit_trends: [] }, customer: { total_customers: 1524, new_customers: 234, returning_customers: 1290, customer_retention_rate: 84.6, customer_lifetime_value: 285.40, average_purchase_frequency: 2.8, customer_satisfaction: 4.6, customer_segments: [ { segment_id: 'seg-1', segment_name: 'Clientes Frecuentes', customer_count: 456, revenue_contribution: 68.2, average_order_value: 42.50, purchase_frequency: 5.2, retention_rate: 92.3, growth_rate: 12.8 } ], loyalty_program_performance: [] }, operational: { staff_productivity: 87.5, equipment_uptime: 94.2, energy_consumption: 2450.75, delivery_performance: 91.8, food_safety_score: 98.5, compliance_rate: 96.2, maintenance_costs: 1250.30, operational_efficiency: 89.4 } }; setBakeryMetrics(mockMetrics); if (onMetricsLoad) { onMetricsLoad(mockMetrics); } // Mock reports const mockReports: AnalyticsReport[] = [ { id: 'rep-1', name: 'Reporte de Ventas Mensual', description: 'Análisis completo de ventas del mes', type: 'sales', category: 'Ventas', status: 'active', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), tenant_id: 'tenant-1', config: { data_source: 'sales_db', refresh_interval: 60, cache_duration: 30, max_records: 10000, default_time_range: 'thisMonth', default_filters: {}, visualization_type: 'bar', aggregation_rules: [] }, metrics: [], filters: [], access_permissions: [], tags: ['ventas', 'mensual'] } ]; setReports(mockReports); } catch (error) { console.error('Error loading analytics data:', error); } finally { setLoading(false); } }, [selectedTimeRange, customDateRange, appliedFilters, onMetricsLoad]); useEffect(() => { loadAnalyticsData(); }, [loadAnalyticsData]); const handleTimeRangeChange = (newRange: TimeRange) => { setSelectedTimeRange(newRange); if (newRange !== 'custom') { setCustomDateRange(null); } }; const handleFiltersChange = (filters: AppliedFilter[]) => { setAppliedFilters(filters); }; const handleExport = (format: ExportFormat, options: any) => { if (onExport) { onExport(format, { ...options, view: activeView, metrics: bakeryMetrics }); } setIsExportModalOpen(false); }; const renderKPICard = (title: string, value: string | number, subtitle?: string, trend?: number, icon?: string, colorClass: string = 'text-[var(--color-info)]') => (

{title}

{value}

{subtitle &&

{subtitle}

} {trend !== undefined && (
= 0 ? 'text-[var(--color-success)]' : 'text-[var(--color-error)]'}`}> {trend >= 0 ? '↗' : '↘'} {Math.abs(trend).toFixed(1)}%
)}
{icon && {icon}}
); const renderOverviewDashboard = () => { if (!bakeryMetrics) return null; return (
{/* KPI Cards */}
{renderKPICard( 'Ingresos Totales', `€${bakeryMetrics.sales.total_revenue.toLocaleString()}`, undefined, bakeryMetrics.sales.revenue_growth, '💰', 'text-[var(--color-success)]' )} {renderKPICard( 'Pedidos', bakeryMetrics.sales.total_orders.toLocaleString(), `Ticket medio: €${bakeryMetrics.sales.average_order_value.toFixed(2)}`, bakeryMetrics.sales.order_growth, '📦', 'text-[var(--color-info)]' )} {renderKPICard( 'Margen de Beneficio', `${bakeryMetrics.financial.profit_margin.toFixed(1)}%`, `Beneficio: €${bakeryMetrics.financial.net_profit.toLocaleString()}`, undefined, '📈', 'text-purple-600' )} {renderKPICard( 'Tasa de Éxito Producción', `${bakeryMetrics.production.success_rate.toFixed(1)}%`, `${bakeryMetrics.production.total_batches} lotes`, undefined, '🏭', 'text-[var(--color-primary)]' )}
{/* Charts Grid */}

Ventas por Canal

{bakeryMetrics.sales.sales_by_channel.map((channel, index) => (

{channel.channel}

{channel.orders} pedidos • {channel.customers} clientes

€{channel.revenue.toLocaleString()}

Conv. {channel.conversion_rate.toFixed(1)}%

))}

Productos Top

{bakeryMetrics.sales.top_products.map((product, index) => (

{product.product_name}

{product.category} • {product.quantity_sold} vendidos

€{product.revenue.toLocaleString()}

Margen {product.profit_margin.toFixed(1)}%

))}
{/* Operational Metrics */}

Métricas Operacionales

{bakeryMetrics.operational.staff_productivity.toFixed(1)}%

Productividad Personal

{bakeryMetrics.operational.equipment_uptime.toFixed(1)}%

Tiempo Activo Equipos

{bakeryMetrics.production.waste_percentage.toFixed(1)}%

Desperdicio

{bakeryMetrics.operational.food_safety_score.toFixed(1)}%

Seguridad Alimentaria

{bakeryMetrics.inventory.stockout_rate.toFixed(1)}%

Roturas Stock

{bakeryMetrics.customer.customer_retention_rate.toFixed(1)}%

Retención Clientes

€{bakeryMetrics.customer.customer_lifetime_value.toFixed(0)}

Valor Cliente

{bakeryMetrics.inventory.turnover_rate.toFixed(1)}

Velocidad de Venta

); }; const renderViewContent = () => { switch (activeView) { case 'overview': return renderOverviewDashboard(); case 'reports': return ( console.log('View report:', report)} onEditReport={(report) => console.log('Edit report:', report)} onDeleteReport={(id) => console.log('Delete report:', id)} onExportReport={(report, format) => console.log('Export report:', report, format)} bulkActions={true} sortable={true} pagination={{ current: 1, pageSize: 10, total: reports.length, onChange: (page, pageSize) => console.log('Page change:', page, pageSize) }} /> ); default: return (

Vista en desarrollo: {activeView}

Esta vista estará disponible próximamente con métricas detalladas.

); } }; return (
{/* Header */}

Analytics Dashboard

Análisis integral de rendimiento de la panadería

{selectedTimeRange === 'custom' && (
setCustomDateRange(prev => ({ ...prev, from: date || new Date() } as { from: Date; to: Date }))} placeholder="Fecha inicio" /> setCustomDateRange(prev => ({ ...prev, to: date || new Date() } as { from: Date; to: Date }))} placeholder="Fecha fin" />
)} {showExport && ( )}
{/* Navigation Tabs */}
{/* Content */} {loading ? (
) : ( renderViewContent() )} {/* Export Modal */} {isExportModalOpen && ( setIsExportModalOpen(false)} /> )}
); }; export default AnalyticsDashboard;