Add improved production UI 3
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user