Create the forntend panel the control
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user