import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, CardHeader, CardBody } from '../../ui/Card'; import { StatusCard } from '../../ui/StatusCard/StatusCard'; import { Badge } from '../../ui/Badge'; import { Button } from '../../ui/Button'; import { useCurrentTenant } from '../../../stores/tenant.store'; import { useActiveBatches } from '../../../api/hooks/production'; import { Factory, Clock, Play, Pause, CheckCircle, AlertTriangle, ChevronRight, Timer, ChefHat, Flame, Calendar } from 'lucide-react'; export interface TodayProductionProps { className?: string; maxBatches?: number; onStartBatch?: (batchId: string) => void; onPauseBatch?: (batchId: string) => void; onViewDetails?: (batchId: string) => void; onViewAllPlans?: () => void; } const TodayProduction: React.FC = ({ className, maxBatches = 5, onStartBatch, onPauseBatch, onViewDetails, onViewAllPlans }) => { const { t } = useTranslation(['dashboard']); const currentTenant = useCurrentTenant(); const tenantId = currentTenant?.id || ''; // Get today's date const todayDate = useMemo(() => { return new Date().toISOString().split('T')[0]; }, []); // Fetch active production batches const { data: productionData, isLoading, error } = useActiveBatches( tenantId, { enabled: !!tenantId, } ); const getBatchStatusConfig = (batch: any) => { const baseConfig = { isCritical: batch.status === 'FAILED' || batch.priority === 'URGENT', isHighlight: batch.status === 'IN_PROGRESS' || batch.priority === 'HIGH', }; switch (batch.status) { case 'PENDING': return { ...baseConfig, color: 'var(--color-warning)', text: 'Pendiente', icon: Clock }; case 'IN_PROGRESS': return { ...baseConfig, color: 'var(--color-info)', text: 'En Proceso', icon: Flame }; case 'COMPLETED': return { ...baseConfig, color: 'var(--color-success)', text: 'Completado', icon: CheckCircle }; case 'ON_HOLD': return { ...baseConfig, color: 'var(--color-warning)', text: 'Pausado', icon: Pause }; case 'FAILED': return { ...baseConfig, color: 'var(--color-error)', text: 'Fallido', icon: AlertTriangle }; case 'QUALITY_CHECK': return { ...baseConfig, color: 'var(--color-info)', text: 'Control de Calidad', icon: CheckCircle }; default: return { ...baseConfig, color: 'var(--color-warning)', text: 'Pendiente', icon: Clock }; } }; const formatDuration = (minutes: number) => { const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (hours > 0) { return `${hours}h ${mins}m`; } return `${mins}m`; }; // Process batches and sort by priority const displayBatches = useMemo(() => { if (!productionData?.batches || !Array.isArray(productionData.batches)) return []; const batches = [...productionData.batches]; // Filter for today's batches only const todayBatches = batches.filter(batch => { const batchDate = new Date(batch.planned_start_time || batch.created_at); return batchDate.toISOString().split('T')[0] === todayDate; }); // Sort by priority and start time const priorityOrder = { URGENT: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }; todayBatches.sort((a, b) => { // First sort by status (pending/in_progress first) const statusOrder = { PENDING: 0, IN_PROGRESS: 1, QUALITY_CHECK: 2, ON_HOLD: 3, COMPLETED: 4, FAILED: 5, CANCELLED: 6 }; const aStatus = statusOrder[a.status as keyof typeof statusOrder] ?? 7; const bStatus = statusOrder[b.status as keyof typeof statusOrder] ?? 7; if (aStatus !== bStatus) return aStatus - bStatus; // Then by priority const aPriority = priorityOrder[a.priority as keyof typeof priorityOrder] ?? 4; const bPriority = priorityOrder[b.priority as keyof typeof priorityOrder] ?? 4; if (aPriority !== bPriority) return aPriority - bPriority; // Finally by start time const aTime = new Date(a.planned_start_time || a.created_at).getTime(); const bTime = new Date(b.planned_start_time || b.created_at).getTime(); return aTime - bTime; }); return todayBatches.slice(0, maxBatches); }, [productionData, todayDate, maxBatches]); const inProgressBatches = productionData?.batches?.filter( b => b.status === 'IN_PROGRESS' ).length || 0; const completedBatches = productionData?.batches?.filter( b => b.status === 'COMPLETED' ).length || 0; const delayedBatches = productionData?.batches?.filter( b => b.status === 'FAILED' ).length || 0; const pendingBatches = productionData?.batches?.filter( b => b.status === 'PENDING' ).length || 0; if (isLoading) { return (

{t('dashboard:sections.production_today', 'Producción de Hoy')}

{t('dashboard:production.title', '¿Qué necesito producir hoy?')}

); } if (error) { return (

{t('dashboard:sections.production_today', 'Producción de Hoy')}

{t('dashboard:production.title', '¿Qué necesito producir hoy?')}

{t('dashboard:messages.error_loading', 'Error al cargar los datos')}

); } return (

{t('dashboard:sections.production_today', 'Producción de Hoy')}

{t('dashboard:production.title', '¿Qué necesito producir hoy?')}

{delayedBatches > 0 && ( {delayedBatches} retrasados )} {inProgressBatches > 0 && ( {inProgressBatches} activos )} {completedBatches > 0 && ( {completedBatches} completados )}
{new Date(todayDate).toLocaleDateString('es-ES')}
{displayBatches.length === 0 ? (

{t('dashboard:production.empty', 'Sin producción programada para hoy')}

No hay lotes programados para iniciar hoy

) : (
{displayBatches.map((batch) => { const statusConfig = getBatchStatusConfig(batch); // Calculate progress based on status and time let progress = 0; if (batch.status === 'COMPLETED') { progress = 100; } else if (batch.status === 'IN_PROGRESS' && batch.actual_start_time && batch.planned_duration_minutes) { const elapsed = Date.now() - new Date(batch.actual_start_time).getTime(); const elapsedMinutes = elapsed / (1000 * 60); progress = Math.min(Math.round((elapsedMinutes / batch.planned_duration_minutes) * 100), 99); } else if (batch.status === 'QUALITY_CHECK') { progress = 95; } const startTime = batch.planned_start_time ? new Date(batch.planned_start_time).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : 'No programado'; const assignedStaff = batch.staff_assigned && batch.staff_assigned.length > 0 ? batch.staff_assigned[0] : 'Sin asignar'; return ( 70 ? 'var(--color-info)' : progress > 30 ? 'var(--color-warning)' : 'var(--color-error)' } : undefined} metadata={[ `⏰ Inicio: ${startTime}`, ...(batch.planned_duration_minutes ? [`⏱️ Duración: ${formatDuration(batch.planned_duration_minutes)}`] : []), ...(batch.station_id ? [`🏭 Estación: ${batch.station_id}`] : []), ...(batch.priority === 'URGENT' ? [`⚠️ URGENTE`] : []), ...(batch.production_notes ? [`📋 ${batch.production_notes}`] : []) ]} actions={[ ...(batch.status === 'PENDING' ? [{ label: 'Iniciar', icon: Play, variant: 'primary' as const, onClick: () => onStartBatch?.(batch.id), priority: 'primary' as const }] : []), ...(batch.status === 'IN_PROGRESS' ? [{ label: 'Pausar', icon: Pause, variant: 'outline' as const, onClick: () => onPauseBatch?.(batch.id), priority: 'primary' as const, destructive: true }] : []), { label: 'Ver Detalles', icon: ChevronRight, variant: 'outline' as const, onClick: () => onViewDetails?.(batch.id), priority: 'secondary' as const } ]} compact={true} className="border-l-4" /> ); })}
)} {displayBatches.length > 0 && (
{pendingBatches} {t('dashboard:production.batches_pending', 'lotes pendientes')} de {productionData?.batches?.length || 0} total
{onViewAllPlans && ( )}
)}
); }; export default TodayProduction;