Files
bakery-ia/frontend/src/components/domain/dashboard/ProductionPlansToday.tsx
2025-09-19 16:17:04 +02:00

371 lines
12 KiB
TypeScript

import React from 'react';
import { Card, CardHeader, CardBody } from '../../ui/Card';
import { StatusCard } from '../../ui/StatusCard/StatusCard';
import { Badge } from '../../ui/Badge';
import { Button } from '../../ui/Button';
import {
Factory,
Clock,
Users,
Thermometer,
Play,
Pause,
CheckCircle,
AlertTriangle,
ChevronRight,
Timer,
Package,
Flame
} from 'lucide-react';
export interface ProductionOrder {
id: string;
product: string;
quantity: number;
unit: string;
priority: 'urgent' | 'high' | 'medium' | 'low';
status: 'pending' | 'in_progress' | 'completed' | 'paused' | 'delayed';
startTime: string;
estimatedDuration: number; // in minutes
assignedBaker: string;
ovenNumber?: number;
temperature?: number;
progress: number; // 0-100
notes?: string;
recipe: string;
ingredients: Array<{
name: string;
quantity: number;
unit: string;
available: boolean;
}>;
}
export interface ProductionPlansProps {
className?: string;
orders?: ProductionOrder[];
onStartOrder?: (orderId: string) => void;
onPauseOrder?: (orderId: string) => void;
onViewDetails?: (orderId: string) => void;
onViewAllPlans?: () => void;
}
const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
className,
orders = [],
onStartOrder,
onPauseOrder,
onViewDetails,
onViewAllPlans
}) => {
const defaultOrders: ProductionOrder[] = [
{
id: '1',
product: 'Pan de Molde Integral',
quantity: 20,
unit: 'unidades',
priority: 'urgent',
status: 'in_progress',
startTime: '06:00',
estimatedDuration: 180,
assignedBaker: 'María González',
ovenNumber: 1,
temperature: 220,
progress: 65,
recipe: 'Receta Estándar Integral',
ingredients: [
{ name: 'Harina integral', quantity: 5, unit: 'kg', available: true },
{ name: 'Levadura', quantity: 0.5, unit: 'kg', available: true },
{ name: 'Sal', quantity: 0.2, unit: 'kg', available: true },
{ name: 'Agua', quantity: 3, unit: 'L', available: true }
]
},
{
id: '2',
product: 'Croissants de Mantequilla',
quantity: 50,
unit: 'unidades',
priority: 'high',
status: 'pending',
startTime: '07:30',
estimatedDuration: 240,
assignedBaker: 'Carlos Rodríguez',
ovenNumber: 2,
temperature: 200,
progress: 0,
recipe: 'Croissant Francés',
notes: 'Masa preparada ayer, lista para horneado',
ingredients: [
{ name: 'Masa de croissant', quantity: 3, unit: 'kg', available: true },
{ name: 'Mantequilla', quantity: 1, unit: 'kg', available: false },
{ name: 'Huevo', quantity: 6, unit: 'unidades', available: true }
]
},
{
id: '3',
product: 'Baguettes Tradicionales',
quantity: 30,
unit: 'unidades',
priority: 'medium',
status: 'completed',
startTime: '05:00',
estimatedDuration: 240,
assignedBaker: 'Ana Martín',
ovenNumber: 3,
temperature: 240,
progress: 100,
recipe: 'Baguette Francesa',
ingredients: [
{ name: 'Harina blanca', quantity: 4, unit: 'kg', available: true },
{ name: 'Levadura', quantity: 0.3, unit: 'kg', available: true },
{ name: 'Sal', quantity: 0.15, unit: 'kg', available: true },
{ name: 'Agua', quantity: 2.5, unit: 'L', available: true }
]
},
{
id: '4',
product: 'Magdalenas de Vainilla',
quantity: 100,
unit: 'unidades',
priority: 'medium',
status: 'delayed',
startTime: '09:00',
estimatedDuration: 90,
assignedBaker: 'Luis Fernández',
ovenNumber: 4,
temperature: 180,
progress: 0,
recipe: 'Magdalenas Clásicas',
notes: 'Retraso por falta de moldes',
ingredients: [
{ name: 'Harina', quantity: 2, unit: 'kg', available: true },
{ name: 'Azúcar', quantity: 1.5, unit: 'kg', available: true },
{ name: 'Huevos', quantity: 24, unit: 'unidades', available: true },
{ name: 'Mantequilla', quantity: 1, unit: 'kg', available: false },
{ name: 'Vainilla', quantity: 50, unit: 'ml', available: true }
]
}
];
const displayOrders = orders.length > 0 ? orders : defaultOrders;
const getOrderStatusConfig = (order: ProductionOrder) => {
const baseConfig = {
isCritical: order.status === 'delayed' || order.priority === 'urgent',
isHighlight: order.status === 'in_progress' || order.priority === 'high',
};
switch (order.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: Play
};
case 'completed':
return {
...baseConfig,
color: 'var(--color-success)',
text: 'Completado',
icon: CheckCircle
};
case 'paused':
return {
...baseConfig,
color: 'var(--color-warning)',
text: 'Pausado',
icon: Pause
};
case 'delayed':
return {
...baseConfig,
color: 'var(--color-error)',
text: 'Retrasado',
icon: AlertTriangle
};
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`;
};
const inProgressOrders = displayOrders.filter(order => order.status === 'in_progress').length;
const completedOrders = displayOrders.filter(order => order.status === 'completed').length;
const delayedOrders = displayOrders.filter(order => order.status === 'delayed').length;
return (
<Card className={className} variant="elevated" padding="none">
<CardHeader padding="lg" divider>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
<div
className="p-2 rounded-lg"
style={{ backgroundColor: 'var(--color-primary)20' }}
>
<Factory className="w-5 h-5" style={{ color: 'var(--color-primary)' }} />
</div>
<div>
<h3 className="text-lg font-semibold" style={{ color: 'var(--text-primary)' }}>
Planes de Producción - Hoy
</h3>
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Gestiona la producción programada para hoy
</p>
</div>
</div>
<div className="flex items-center gap-2">
{delayedOrders > 0 && (
<Badge variant="error" size="sm">
{delayedOrders} retrasadas
</Badge>
)}
{inProgressOrders > 0 && (
<Badge variant="info" size="sm">
{inProgressOrders} activas
</Badge>
)}
<Badge variant="success" size="sm">
{completedOrders} completadas
</Badge>
</div>
</div>
</CardHeader>
<CardBody padding="none">
{displayOrders.length === 0 ? (
<div className="p-8 text-center">
<div
className="p-4 rounded-full mx-auto mb-4 w-16 h-16 flex items-center justify-center"
style={{ backgroundColor: 'var(--color-success)20' }}
>
<Factory className="w-8 h-8" style={{ color: 'var(--color-success)' }} />
</div>
<h4 className="text-lg font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
No hay producción programada
</h4>
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Día libre de producción
</p>
</div>
) : (
<div className="space-y-3 p-4">
{displayOrders.map((order) => {
const statusConfig = getOrderStatusConfig(order);
const availableIngredients = order.ingredients.filter(ing => ing.available).length;
const totalIngredients = order.ingredients.length;
const ingredientsReady = availableIngredients === totalIngredients;
return (
<StatusCard
key={order.id}
id={order.id}
statusIndicator={statusConfig}
title={order.product}
subtitle={`${order.recipe}${order.quantity} ${order.unit}`}
primaryValue={`${order.progress}%`}
primaryValueLabel="PROGRESO"
secondaryInfo={{
label: 'Panadero asignado',
value: order.assignedBaker
}}
progress={order.status !== 'pending' ? {
label: `Progreso de producción`,
percentage: order.progress,
color: order.progress === 100 ? 'var(--color-success)' :
order.progress > 70 ? 'var(--color-info)' :
order.progress > 30 ? 'var(--color-warning)' : 'var(--color-error)'
} : undefined}
metadata={[
`⏰ Inicio: ${order.startTime}`,
`⏱️ Duración: ${formatDuration(order.estimatedDuration)}`,
...(order.ovenNumber ? [`🔥 Horno ${order.ovenNumber} - ${order.temperature}°C`] : []),
`📋 Ingredientes: ${availableIngredients}/${totalIngredients} ${ingredientsReady ? '✅' : '❌'}`,
...(order.notes ? [`📝 ${order.notes}`] : [])
]}
actions={[
...(order.status === 'pending' ? [{
label: 'Iniciar',
icon: Play,
variant: 'primary' as const,
onClick: () => onStartOrder?.(order.id),
priority: 'primary' as const
}] : []),
...(order.status === 'in_progress' ? [{
label: 'Pausar',
icon: Pause,
variant: 'outline' as const,
onClick: () => onPauseOrder?.(order.id),
priority: 'primary' as const,
destructive: true
}] : []),
{
label: 'Ver Detalles',
icon: ChevronRight,
variant: 'outline' as const,
onClick: () => onViewDetails?.(order.id),
priority: 'secondary' as const
}
]}
compact={true}
className="border-l-4"
/>
);
})}
</div>
)}
{displayOrders.length > 0 && (
<div
className="p-4 border-t"
style={{
borderColor: 'var(--border-primary)',
backgroundColor: 'var(--bg-secondary)'
}}
>
<div className="flex items-center justify-between">
<div className="text-sm">
<span style={{ color: 'var(--text-secondary)' }}>
{completedOrders} de {displayOrders.length} órdenes completadas
</span>
</div>
<Button
variant="outline"
size="sm"
onClick={onViewAllPlans}
className="flex items-center gap-2"
>
Ver Todos los Planes
<ChevronRight className="w-4 h-4" />
</Button>
</div>
</div>
)}
</CardBody>
</Card>
);
};
export default ProductionPlansToday;