Add improved production UI 3

This commit is contained in:
Urtzi Alfaro
2025-09-23 19:24:22 +02:00
parent 7f871fc933
commit 7892c5a739
47 changed files with 6211 additions and 267 deletions

View File

@@ -15,9 +15,43 @@ import {
ChevronRight,
Timer,
Package,
Flame
Flame,
ChefHat,
Eye,
Scale,
FlaskRound,
CircleDot,
ArrowRight,
CheckSquare,
XSquare,
Zap,
Snowflake,
Box
} from 'lucide-react';
export interface QualityCheckRequirement {
id: string;
name: string;
stage: ProcessStage;
isRequired: boolean;
isCritical: boolean;
status: 'pending' | 'completed' | 'failed' | 'skipped';
checkType: 'visual' | 'measurement' | 'temperature' | 'weight' | 'boolean';
}
export interface ProcessStageInfo {
current: ProcessStage;
history: Array<{
stage: ProcessStage;
timestamp: string;
duration?: number;
}>;
pendingQualityChecks: QualityCheckRequirement[];
completedQualityChecks: QualityCheckRequirement[];
}
export type ProcessStage = 'mixing' | 'proofing' | 'shaping' | 'baking' | 'cooling' | 'packaging' | 'finishing';
export interface ProductionOrder {
id: string;
product: string;
@@ -39,6 +73,7 @@ export interface ProductionOrder {
unit: string;
available: boolean;
}>;
processStage?: ProcessStageInfo;
}
export interface ProductionPlansProps {
@@ -50,6 +85,56 @@ export interface ProductionPlansProps {
onViewAllPlans?: () => void;
}
const getProcessStageIcon = (stage: ProcessStage) => {
switch (stage) {
case 'mixing': return ChefHat;
case 'proofing': return Timer;
case 'shaping': return Package;
case 'baking': return Flame;
case 'cooling': return Snowflake;
case 'packaging': return Box;
case 'finishing': return CheckCircle;
default: return CircleDot;
}
};
const getProcessStageColor = (stage: ProcessStage) => {
switch (stage) {
case 'mixing': return 'var(--color-info)';
case 'proofing': return 'var(--color-warning)';
case 'shaping': return 'var(--color-primary)';
case 'baking': return 'var(--color-error)';
case 'cooling': return 'var(--color-info)';
case 'packaging': return 'var(--color-success)';
case 'finishing': return 'var(--color-success)';
default: return 'var(--color-gray)';
}
};
const getProcessStageLabel = (stage: ProcessStage) => {
switch (stage) {
case 'mixing': return 'Mezclado';
case 'proofing': return 'Fermentado';
case 'shaping': return 'Formado';
case 'baking': return 'Horneado';
case 'cooling': return 'Enfriado';
case 'packaging': return 'Empaquetado';
case 'finishing': return 'Acabado';
default: return 'Sin etapa';
}
};
const getQualityCheckIcon = (checkType: string) => {
switch (checkType) {
case 'visual': return Eye;
case 'measurement': return Scale;
case 'temperature': return Thermometer;
case 'weight': return Scale;
case 'boolean': return CheckSquare;
default: return FlaskRound;
}
};
const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
className,
orders = [],
@@ -78,7 +163,38 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
{ 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 }
]
],
processStage: {
current: 'baking',
history: [
{ stage: 'mixing', timestamp: '06:00', duration: 30 },
{ stage: 'proofing', timestamp: '06:30', duration: 90 },
{ stage: 'shaping', timestamp: '08:00', duration: 15 },
{ stage: 'baking', timestamp: '08:15' }
],
pendingQualityChecks: [
{
id: 'qc1',
name: 'Control de temperatura interna',
stage: 'baking',
isRequired: true,
isCritical: true,
status: 'pending',
checkType: 'temperature'
}
],
completedQualityChecks: [
{
id: 'qc2',
name: 'Inspección visual de masa',
stage: 'mixing',
isRequired: true,
isCritical: false,
status: 'completed',
checkType: 'visual'
}
]
}
},
{
id: '2',
@@ -99,7 +215,34 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
{ 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 }
]
],
processStage: {
current: 'shaping',
history: [
{ stage: 'proofing', timestamp: '07:30', duration: 120 }
],
pendingQualityChecks: [
{
id: 'qc3',
name: 'Verificar formado de hojaldre',
stage: 'shaping',
isRequired: true,
isCritical: false,
status: 'pending',
checkType: 'visual'
},
{
id: 'qc4',
name: 'Control de peso individual',
stage: 'shaping',
isRequired: false,
isCritical: false,
status: 'pending',
checkType: 'weight'
}
],
completedQualityChecks: []
}
},
{
id: '3',
@@ -120,7 +263,39 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
{ 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 }
]
],
processStage: {
current: 'finishing',
history: [
{ stage: 'mixing', timestamp: '05:00', duration: 20 },
{ stage: 'proofing', timestamp: '05:20', duration: 120 },
{ stage: 'shaping', timestamp: '07:20', duration: 30 },
{ stage: 'baking', timestamp: '07:50', duration: 45 },
{ stage: 'cooling', timestamp: '08:35', duration: 30 },
{ stage: 'finishing', timestamp: '09:05' }
],
pendingQualityChecks: [],
completedQualityChecks: [
{
id: 'qc5',
name: 'Inspección visual final',
stage: 'finishing',
isRequired: true,
isCritical: false,
status: 'completed',
checkType: 'visual'
},
{
id: 'qc6',
name: 'Control de temperatura de cocción',
stage: 'baking',
isRequired: true,
isCritical: true,
status: 'completed',
checkType: 'temperature'
}
]
}
},
{
id: '4',
@@ -143,7 +318,23 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
{ name: 'Huevos', quantity: 24, unit: 'unidades', available: true },
{ name: 'Mantequilla', quantity: 1, unit: 'kg', available: false },
{ name: 'Vainilla', quantity: 50, unit: 'ml', available: true }
]
],
processStage: {
current: 'mixing',
history: [],
pendingQualityChecks: [
{
id: 'qc7',
name: 'Verificar consistencia de masa',
stage: 'mixing',
isRequired: true,
isCritical: false,
status: 'pending',
checkType: 'visual'
}
],
completedQualityChecks: []
}
}
];
@@ -214,6 +405,63 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
const completedOrders = displayOrders.filter(order => order.status === 'completed').length;
const delayedOrders = displayOrders.filter(order => order.status === 'delayed').length;
// Cross-batch quality overview calculations
const totalPendingQualityChecks = displayOrders.reduce((total, order) =>
total + (order.processStage?.pendingQualityChecks.length || 0), 0);
const criticalPendingQualityChecks = displayOrders.reduce((total, order) =>
total + (order.processStage?.pendingQualityChecks.filter(qc => qc.isCritical).length || 0), 0);
const ordersBlockedByQuality = displayOrders.filter(order =>
order.processStage?.pendingQualityChecks.some(qc => qc.isCritical && qc.isRequired) || false).length;
// Helper function to create enhanced metadata with process stage info
const createEnhancedMetadata = (order: ProductionOrder) => {
const baseMetadata = [
`⏰ Inicio: ${order.startTime}`,
`⏱️ Duración: ${formatDuration(order.estimatedDuration)}`,
...(order.ovenNumber ? [`🔥 Horno ${order.ovenNumber} - ${order.temperature}°C`] : [])
];
if (order.processStage) {
const { current, pendingQualityChecks, completedQualityChecks } = order.processStage;
const currentStageIcon = getProcessStageIcon(current);
const currentStageLabel = getProcessStageLabel(current);
// Add current stage info
baseMetadata.push(`🔄 Etapa: ${currentStageLabel}`);
// Add quality check info
if (pendingQualityChecks.length > 0) {
const criticalPending = pendingQualityChecks.filter(qc => qc.isCritical).length;
const requiredPending = pendingQualityChecks.filter(qc => qc.isRequired).length;
if (criticalPending > 0) {
baseMetadata.push(`🚨 ${criticalPending} controles críticos pendientes`);
} else if (requiredPending > 0) {
baseMetadata.push(`${requiredPending} controles requeridos pendientes`);
} else {
baseMetadata.push(`📋 ${pendingQualityChecks.length} controles opcionales pendientes`);
}
}
if (completedQualityChecks.length > 0) {
baseMetadata.push(`${completedQualityChecks.length} controles completados`);
}
}
// Add ingredients info
const availableIngredients = order.ingredients.filter(ing => ing.available).length;
const totalIngredients = order.ingredients.length;
const ingredientsReady = availableIngredients === totalIngredients;
baseMetadata.push(`📋 Ingredientes: ${availableIngredients}/${totalIngredients} ${ingredientsReady ? '✅' : '❌'}`);
// Add notes if any
if (order.notes) {
baseMetadata.push(`📝 ${order.notes}`);
}
return baseMetadata;
};
return (
<Card className={className} variant="elevated" padding="none">
<CardHeader padding="lg" divider>
@@ -236,6 +484,21 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
</div>
<div className="flex items-center gap-2">
{ordersBlockedByQuality > 0 && (
<Badge variant="error" size="sm">
🚨 {ordersBlockedByQuality} bloqueadas por calidad
</Badge>
)}
{criticalPendingQualityChecks > 0 && ordersBlockedByQuality === 0 && (
<Badge variant="warning" size="sm">
🔍 {criticalPendingQualityChecks} controles críticos
</Badge>
)}
{totalPendingQualityChecks > 0 && criticalPendingQualityChecks === 0 && (
<Badge variant="info" size="sm">
📋 {totalPendingQualityChecks} controles pendientes
</Badge>
)}
{delayedOrders > 0 && (
<Badge variant="error" size="sm">
{delayedOrders} retrasadas
@@ -273,23 +536,44 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
<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;
const enhancedMetadata = createEnhancedMetadata(order);
// Enhanced secondary info that includes stage information
const getSecondaryInfo = () => {
if (order.processStage) {
const currentStageLabel = getProcessStageLabel(order.processStage.current);
return {
label: 'Etapa actual',
value: `${currentStageLabel}${order.assignedBaker}`
};
}
return {
label: 'Panadero asignado',
value: order.assignedBaker
};
};
// Enhanced status indicator with process stage color for active orders
const getEnhancedStatusConfig = () => {
if (order.processStage && order.status === 'in_progress') {
return {
...statusConfig,
color: getProcessStageColor(order.processStage.current)
};
}
return statusConfig;
};
return (
<StatusCard
key={order.id}
id={order.id}
statusIndicator={statusConfig}
statusIndicator={getEnhancedStatusConfig()}
title={order.product}
subtitle={`${order.recipe}${order.quantity} ${order.unit}`}
primaryValue={`${order.progress}%`}
primaryValueLabel="PROGRESO"
secondaryInfo={{
label: 'Panadero asignado',
value: order.assignedBaker
}}
secondaryInfo={getSecondaryInfo()}
progress={order.status !== 'pending' ? {
label: `Progreso de producción`,
percentage: order.progress,
@@ -297,13 +581,7 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
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}`] : [])
]}
metadata={enhancedMetadata}
actions={[
...(order.status === 'pending' ? [{
label: 'Iniciar',
@@ -320,6 +598,22 @@ const ProductionPlansToday: React.FC<ProductionPlansProps> = ({
priority: 'primary' as const,
destructive: true
}] : []),
// Add quality check action if there are pending quality checks
...(order.processStage?.pendingQualityChecks.length > 0 ? [{
label: `${order.processStage.pendingQualityChecks.filter(qc => qc.isCritical).length > 0 ? '🚨 ' : ''}Controles Calidad`,
icon: FlaskRound,
variant: order.processStage.pendingQualityChecks.filter(qc => qc.isCritical).length > 0 ? 'primary' as const : 'outline' as const,
onClick: () => onViewDetails?.(order.id), // This would open the quality check modal
priority: order.processStage.pendingQualityChecks.filter(qc => qc.isCritical).length > 0 ? 'primary' as const : 'secondary' as const
}] : []),
// Add next stage action for orders that can progress
...(order.status === 'in_progress' && order.processStage && order.processStage.pendingQualityChecks.length === 0 ? [{
label: 'Siguiente Etapa',
icon: ArrowRight,
variant: 'primary' as const,
onClick: () => console.log(`Advancing stage for order ${order.id}`),
priority: 'primary' as const
}] : []),
{
label: 'Ver Detalles',
icon: ChevronRight,