import React, { useState, useMemo } from 'react'; import { Plus, Clock, AlertCircle, CheckCircle, Timer, ChefHat, Eye, Edit, Package } from 'lucide-react'; import { Button, Input, Card, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui'; import { statusColors } from '../../../../styles/colors'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; import { LoadingSpinner } from '../../../../components/shared'; import { PageHeader } from '../../../../components/layout'; import { ProductionSchedule, BatchTracker, QualityControl, CreateProductionBatchModal } from '../../../../components/domain/production'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useProductionDashboard, useActiveBatches, useCreateProductionBatch, useUpdateBatchStatus, productionService } from '../../../../api'; import type { ProductionBatchResponse, ProductionBatchCreate, ProductionBatchStatusUpdate } from '../../../../api'; import { ProductionStatusEnum, ProductionPriorityEnum } from '../../../../api'; import { useProductionEnums } from '../../../../utils/enumHelpers'; const ProductionPage: React.FC = () => { const [activeTab, setActiveTab] = useState('schedule'); const [searchQuery, setSearchQuery] = useState(''); const [selectedBatch, setSelectedBatch] = useState(null); const [showBatchModal, setShowBatchModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const [modalMode, setModalMode] = useState<'view' | 'edit'>('view'); const currentTenant = useCurrentTenant(); const tenantId = currentTenant?.id || ''; const productionEnums = useProductionEnums(); // API Data const { data: dashboardData, isLoading: dashboardLoading, error: dashboardError } = useProductionDashboard(tenantId); const { data: activeBatchesData, isLoading: batchesLoading, error: batchesError } = useActiveBatches(tenantId); // Mutations const createBatchMutation = useCreateProductionBatch(); const updateBatchStatusMutation = useUpdateBatchStatus(); // Handlers const handleCreateBatch = async (batchData: ProductionBatchCreate) => { try { await createBatchMutation.mutateAsync({ tenantId, batchData }); } catch (error) { console.error('Error creating production batch:', error); throw error; } }; const getProductionStatusConfig = (status: ProductionStatusEnum, priority: ProductionPriorityEnum) => { const statusConfig = { [ProductionStatusEnum.PENDING]: { icon: Clock }, [ProductionStatusEnum.IN_PROGRESS]: { icon: Timer }, [ProductionStatusEnum.COMPLETED]: { icon: CheckCircle }, [ProductionStatusEnum.CANCELLED]: { icon: AlertCircle }, [ProductionStatusEnum.ON_HOLD]: { icon: AlertCircle }, [ProductionStatusEnum.QUALITY_CHECK]: { icon: Package }, [ProductionStatusEnum.FAILED]: { icon: AlertCircle }, }; const config = statusConfig[status] || { icon: AlertCircle }; const Icon = config.icon; const isUrgent = priority === ProductionPriorityEnum.URGENT; const isCritical = status === ProductionStatusEnum.FAILED || (status === ProductionStatusEnum.PENDING && isUrgent); // Map production statuses to global status colors const getStatusColorForProduction = (status: ProductionStatusEnum) => { // Handle both uppercase (backend) and lowercase (frontend) status values const normalizedStatus = status.toLowerCase(); switch (normalizedStatus) { case 'pending': return statusColors.pending.primary; case 'in_progress': return statusColors.inProgress.primary; case 'completed': return statusColors.completed.primary; case 'cancelled': case 'failed': return statusColors.cancelled.primary; case 'on_hold': return statusColors.pending.primary; case 'quality_check': return statusColors.inProgress.primary; default: return statusColors.other.primary; } }; return { color: getStatusColorForProduction(status), text: productionEnums.getProductionStatusLabel(status), icon: Icon, isCritical, isHighlight: isUrgent }; }; const batches = activeBatchesData?.batches || []; const filteredBatches = useMemo(() => { if (!searchQuery) return batches; const searchLower = searchQuery.toLowerCase(); return batches.filter(batch => batch.product_name.toLowerCase().includes(searchLower) || batch.batch_number.toLowerCase().includes(searchLower) || (batch.staff_assigned && batch.staff_assigned.some(staff => staff.toLowerCase().includes(searchLower) )) ); }, [batches, searchQuery]); // Calculate production stats from real data const productionStats = useMemo(() => { if (!dashboardData) { return { activeBatches: 0, todaysTarget: 0, capacityUtilization: 0, onTimeCompletion: 0, qualityScore: 0, totalOutput: 0, efficiency: 0 }; } return { activeBatches: dashboardData.active_batches || 0, todaysTarget: dashboardData.todays_production_plan?.length || 0, capacityUtilization: Math.round(dashboardData.capacity_utilization || 0), onTimeCompletion: Math.round(dashboardData.on_time_completion_rate || 0), qualityScore: Math.round(dashboardData.average_quality_score || 0), totalOutput: dashboardData.total_output_today || 0, efficiency: Math.round(dashboardData.efficiency_percentage || 0) }; }, [dashboardData]); // Calculate progress for batches const calculateProgress = (batch: ProductionBatchResponse): number => { if (batch.status === 'completed') return 100; if (batch.status === 'pending') return 0; if (batch.status === 'cancelled' || batch.status === 'failed') return 0; // For in-progress batches, calculate based on time elapsed if (batch.actual_start_time && batch.planned_end_time) { const now = new Date(); const startTime = new Date(batch.actual_start_time); const endTime = new Date(batch.planned_end_time); const totalDuration = endTime.getTime() - startTime.getTime(); const elapsed = now.getTime() - startTime.getTime(); if (totalDuration > 0) { return Math.min(90, Math.max(10, Math.round((elapsed / totalDuration) * 100))); } } // Default progress for in-progress items return 50; }; // Loading state if (!tenantId || dashboardLoading || batchesLoading) { return (
); } // Error state if (dashboardError || batchesError) { return (

Error al cargar la producción

{(dashboardError || batchesError)?.message || 'Ha ocurrido un error inesperado'}

); } return (
setShowCreateModal(true) } ]} /> {/* Production Stats */} = 80 ? 'success' as const : 'warning' as const, icon: Timer, }, { title: 'Completado a Tiempo', value: `${productionStats.onTimeCompletion}%`, variant: productionStats.onTimeCompletion >= 90 ? 'success' as const : 'error' as const, icon: CheckCircle, }, { title: 'Puntuación Calidad', value: `${productionStats.qualityScore}%`, variant: productionStats.qualityScore >= 85 ? 'success' as const : 'warning' as const, icon: Package, }, { title: 'Producción Hoy', value: formatters.number(productionStats.totalOutput), variant: 'info' as const, icon: ChefHat, }, { title: 'Eficiencia', value: `${productionStats.efficiency}%`, variant: productionStats.efficiency >= 75 ? 'success' as const : 'warning' as const, icon: Timer, }, ]} columns={3} /> {/* Tabs Navigation */}
{/* Production Orders Tab */} {activeTab === 'schedule' && ( <> {/* Search Controls */}
setSearchQuery(e.target.value)} className="w-full" />
{/* Production Batches Grid */}
{filteredBatches.map((batch) => { const statusConfig = getProductionStatusConfig(batch.status, batch.priority); const progress = calculateProgress(batch); return ( { setSelectedBatch(batch); setModalMode('view'); setShowBatchModal(true); } }, { label: 'Editar', icon: Edit, priority: 'secondary', onClick: () => { setSelectedBatch(batch); setModalMode('edit'); setShowBatchModal(true); } } ]} /> ); })}
{/* Empty State */} {filteredBatches.length === 0 && (

No se encontraron lotes de producción

{batches.length === 0 ? 'No hay lotes de producción activos. Crea el primer lote para comenzar.' : 'Intenta ajustar la búsqueda o crear un nuevo lote de producción' }

)} )} {activeTab === 'batches' && ( )} {activeTab === 'quality' && ( )} {/* Production Batch Modal */} {showBatchModal && selectedBatch && ( { setShowBatchModal(false); setSelectedBatch(null); setModalMode('view'); }} mode={modalMode} onModeChange={setModalMode} title={selectedBatch.product_name} subtitle={`Lote de Producción #${selectedBatch.batch_number}`} statusIndicator={getProductionStatusConfig(selectedBatch.status, selectedBatch.priority)} size="lg" sections={[ { title: 'Información General', icon: Package, fields: [ { label: 'Cantidad Planificada', value: `${selectedBatch.planned_quantity} unidades`, highlight: true }, { label: 'Cantidad Real', value: selectedBatch.actual_quantity ? `${selectedBatch.actual_quantity} unidades` : 'Pendiente', editable: modalMode === 'edit', type: 'number' }, { label: 'Prioridad', value: selectedBatch.priority, type: 'select', editable: modalMode === 'edit', options: productionEnums.getProductionPriorityOptions() }, { label: 'Estado', value: selectedBatch.status, type: 'select', editable: modalMode === 'edit', options: productionEnums.getProductionStatusOptions() }, { label: 'Personal Asignado', value: selectedBatch.staff_assigned?.join(', ') || 'No asignado', editable: modalMode === 'edit', type: 'text' } ] }, { title: 'Cronograma', icon: Clock, fields: [ { label: 'Inicio Planificado', value: selectedBatch.planned_start_time, type: 'datetime' }, { label: 'Fin Planificado', value: selectedBatch.planned_end_time, type: 'datetime' }, { label: 'Inicio Real', value: selectedBatch.actual_start_time || 'Pendiente', type: 'datetime' }, { label: 'Fin Real', value: selectedBatch.actual_end_time || 'Pendiente', type: 'datetime' } ] }, { title: 'Calidad y Costos', icon: CheckCircle, fields: [ { label: 'Puntuación de Calidad', value: selectedBatch.quality_score ? `${selectedBatch.quality_score}/10` : 'Pendiente' }, { label: 'Rendimiento', value: selectedBatch.yield_percentage ? `${selectedBatch.yield_percentage}%` : 'Calculando...' }, { label: 'Costo Estimado', value: selectedBatch.estimated_cost || 0, type: 'currency' }, { label: 'Costo Real', value: selectedBatch.actual_cost || 0, type: 'currency' } ] } ]} onSave={async () => { try { // Implementation would depend on specific fields changed console.log('Saving batch changes:', selectedBatch.id); // await updateBatchStatusMutation.mutateAsync({ // batchId: selectedBatch.id, // updates: selectedBatch // }); setShowBatchModal(false); setSelectedBatch(null); setModalMode('view'); } catch (error) { console.error('Error saving batch:', error); } }} onFieldChange={(sectionIndex, fieldIndex, value) => { if (!selectedBatch) return; const sections = [ ['planned_quantity', 'actual_quantity', 'priority', 'status', 'staff_assigned'], ['planned_start_time', 'planned_end_time', 'actual_start_time', 'actual_end_time'], ['quality_score', 'yield_percentage', 'estimated_cost', 'actual_cost'] ]; // Get the field names from modal sections const sectionFields = [ { fields: ['planned_quantity', 'actual_quantity', 'priority', 'status', 'staff_assigned'] }, { fields: ['planned_start_time', 'planned_end_time', 'actual_start_time', 'actual_end_time'] }, { fields: ['quality_score', 'yield_percentage', 'estimated_cost', 'actual_cost'] } ]; const fieldMapping: Record = { 'Cantidad Real': 'actual_quantity', 'Prioridad': 'priority', 'Estado': 'status', 'Personal Asignado': 'staff_assigned' }; // Get section labels to map back to field names const sectionLabels = [ ['Cantidad Planificada', 'Cantidad Real', 'Prioridad', 'Estado', 'Personal Asignado'], ['Inicio Planificado', 'Fin Planificado', 'Inicio Real', 'Fin Real'], ['Puntuación de Calidad', 'Rendimiento', 'Costo Estimado', 'Costo Real'] ]; const fieldLabel = sectionLabels[sectionIndex]?.[fieldIndex]; const propertyName = fieldMapping[fieldLabel] || sectionFields[sectionIndex]?.fields[fieldIndex]; if (propertyName) { let processedValue: any = value; if (propertyName === 'staff_assigned' && typeof value === 'string') { processedValue = value.split(',').map(s => s.trim()).filter(s => s.length > 0); } else if (propertyName === 'actual_quantity') { processedValue = parseFloat(value as string) || 0; } setSelectedBatch({ ...selectedBatch, [propertyName]: processedValue }); } }} /> )} {/* Create Production Batch Modal */} setShowCreateModal(false)} onCreateBatch={handleCreateBatch} />
); }; export default ProductionPage;