2025-09-21 07:45:19 +02:00
import React , { useState , useMemo } from 'react' ;
2025-12-13 23:57:54 +01:00
import { Plus , Clock , AlertCircle , CheckCircle , Timer , ChefHat , Eye , Edit , Package , PlusCircle , Play , Info } from 'lucide-react' ;
2025-10-27 16:33:26 +01:00
import { Button , StatsGrid , EditViewModal , Toggle , SearchAndFilter , type FilterConfig , EmptyState } from '../../../../components/ui' ;
2025-09-21 17:35:36 +02:00
import { statusColors } from '../../../../styles/colors' ;
2025-09-21 07:45:19 +02:00
import { formatters } from '../../../../components/ui/Stats/StatsPresets' ;
2025-09-26 07:46:25 +02:00
import { LoadingSpinner } from '../../../../components/ui' ;
2025-08-28 10:41:04 +02:00
import { PageHeader } from '../../../../components/layout' ;
2025-11-19 22:12:51 +01:00
import { ProductionSchedule , ProductionStatusCard , QualityCheckModal , ProcessStageTracker } from '../../../../components/domain/production' ;
import { UnifiedAddWizard } from '../../../../components/domain/unified-wizard' ;
import type { ItemType } from '../../../../components/domain/unified-wizard' ;
2025-09-21 07:45:19 +02:00
import { useCurrentTenant } from '../../../../stores/tenant.store' ;
import {
useProductionDashboard ,
useActiveBatches ,
useCreateProductionBatch ,
useUpdateBatchStatus ,
2025-10-24 13:05:04 +02:00
useTriggerProductionScheduler ,
2025-09-21 07:45:19 +02:00
productionService
} from '../../../../api' ;
import type {
ProductionBatchResponse ,
ProductionBatchCreate ,
ProductionBatchStatusUpdate
} from '../../../../api' ;
import {
ProductionStatusEnum ,
ProductionPriorityEnum
} from '../../../../api' ;
2025-09-26 07:46:25 +02:00
import { useTranslation } from 'react-i18next' ;
2025-11-19 21:01:06 +01:00
import { ProcessStage as QualityProcessStage } from '../../../../api/types/qualityTemplates' ;
2025-10-30 21:08:07 +01:00
import { showToast } from '../../../../utils/toast' ;
2025-08-28 10:41:04 +02:00
const ProductionPage : React.FC = ( ) = > {
2025-08-28 23:40:44 +02:00
const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
2025-09-26 12:12:17 +02:00
const [ statusFilter , setStatusFilter ] = useState ( '' ) ;
const [ priorityFilter , setPriorityFilter ] = useState ( '' ) ;
2025-09-21 07:45:19 +02:00
const [ selectedBatch , setSelectedBatch ] = useState < ProductionBatchResponse | null > ( null ) ;
const [ showBatchModal , setShowBatchModal ] = useState ( false ) ;
2025-11-19 22:12:51 +01:00
const [ isWizardOpen , setIsWizardOpen ] = useState ( false ) ;
2025-09-23 15:57:22 +02:00
const [ showQualityModal , setShowQualityModal ] = useState ( false ) ;
2025-08-31 10:46:13 +02:00
const [ modalMode , setModalMode ] = useState < 'view' | 'edit' > ( 'view' ) ;
2025-08-28 10:41:04 +02:00
2025-09-21 07:45:19 +02:00
const currentTenant = useCurrentTenant ( ) ;
const tenantId = currentTenant ? . id || '' ;
2025-09-26 07:46:25 +02:00
const { t } = useTranslation ( [ 'production' , 'common' ] ) ;
2025-09-21 07:45:19 +02:00
// API Data
const {
data : dashboardData ,
isLoading : dashboardLoading ,
error : dashboardError
} = useProductionDashboard ( tenantId ) ;
const {
data : activeBatchesData ,
isLoading : batchesLoading ,
error : batchesError
} = useActiveBatches ( tenantId ) ;
// Mutations
const createBatchMutation = useCreateProductionBatch ( ) ;
const updateBatchStatusMutation = useUpdateBatchStatus ( ) ;
// Handlers
const handleCreateBatch = async ( batchData : ProductionBatchCreate ) = > {
try {
await createBatchMutation . mutateAsync ( {
tenantId ,
batchData
} ) ;
} catch ( error ) {
console . error ( 'Error creating production batch:' , error ) ;
throw error ;
}
2025-08-28 10:41:04 +02:00
} ;
2025-10-24 13:05:04 +02:00
const handleTriggerScheduler = async ( ) = > {
try {
await triggerSchedulerMutation . mutateAsync ( tenantId ) ;
2025-10-30 21:08:07 +01:00
showToast . success ( 'Scheduler ejecutado exitosamente' ) ;
2025-10-24 13:05:04 +02:00
} catch ( error ) {
console . error ( 'Error triggering scheduler:' , error ) ;
2025-10-30 21:08:07 +01:00
showToast . error ( 'Error al ejecutar scheduler' ) ;
2025-10-24 13:05:04 +02:00
}
} ;
2025-09-23 19:24:22 +02:00
// Stage management handlers
2025-11-19 21:01:06 +01:00
const handleStageAdvance = async ( batchId : string , currentStage : QualityProcessStage ) = > {
const stages = Object . values ( QualityProcessStage ) ;
2025-09-23 19:24:22 +02:00
const currentIndex = stages . indexOf ( currentStage ) ;
const nextStage = stages [ currentIndex + 1 ] ;
if ( nextStage ) {
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId ,
updates : {
current_process_stage : nextStage ,
process_stage_history : {
[ currentStage ] : { end_time : new Date ( ) . toISOString ( ) } ,
[ nextStage ] : { start_time : new Date ( ) . toISOString ( ) }
}
}
} ) ;
} catch ( error ) {
console . error ( 'Error advancing stage:' , error ) ;
}
} else {
// Final stage - mark as completed
await updateBatchStatusMutation . mutateAsync ( {
batchId ,
updates : { status : ProductionStatusEnum.COMPLETED }
} ) ;
}
} ;
2025-11-19 21:01:06 +01:00
const handleStageStart = async ( batchId : string , stage : QualityProcessStage ) = > {
2025-09-23 19:24:22 +02:00
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId ,
updates : {
status : ProductionStatusEnum.IN_PROGRESS ,
current_process_stage : stage ,
process_stage_history : {
[ stage ] : { start_time : new Date ( ) . toISOString ( ) }
}
}
} ) ;
} catch ( error ) {
console . error ( 'Error starting stage:' , error ) ;
}
} ;
2025-11-19 21:01:06 +01:00
const handleQualityCheckForStage = ( batch : ProductionBatchResponse , stage : QualityProcessStage ) = > {
2025-09-23 19:24:22 +02:00
setSelectedBatch ( batch ) ;
setShowQualityModal ( true ) ;
// The QualityCheckModal should be enhanced to handle stage-specific checks
} ;
2025-11-12 15:34:10 +01:00
// Helper function to get process stage data from the batch (now from real backend data)
const getProcessStageData = ( batch : ProductionBatchResponse ) = > {
// Backend now provides these fields in the API response:
// - current_process_stage
// - process_stage_history
// - pending_quality_checks
// - completed_quality_checks
return {
2025-11-19 21:01:06 +01:00
current : batch.current_process_stage as QualityProcessStage || 'mixing' ,
history : batch.process_stage_history ?
batch . process_stage_history . map ( item = > ( {
stage : item.stage as QualityProcessStage ,
start_time : item.start_time || item . timestamp || '' ,
end_time : item.end_time ,
duration : item.duration ,
notes : item.notes ,
personnel : item.personnel
} ) ) : [ ] ,
pendingQualityChecks : batch.pending_quality_checks ?
batch . pending_quality_checks . map ( item = > ( {
id : item.id || '' ,
name : item.name || '' ,
stage : item.stage as QualityProcessStage ,
isRequired : item.is_required || item . isRequired || false ,
isCritical : item.is_critical || item . isCritical || false ,
status : item.status || 'pending' ,
checkType : item.check_type || item . checkType || 'visual'
} ) ) : [ ] ,
completedQualityChecks : batch.completed_quality_checks ?
batch . completed_quality_checks . map ( item = > ( {
id : item.id || '' ,
name : item.name || '' ,
stage : item.stage as QualityProcessStage ,
isRequired : item.is_required || item . isRequired || false ,
isCritical : item.is_critical || item . isCritical || false ,
status : item.status || 'completed' ,
checkType : item.check_type || item . checkType || 'visual'
} ) ) : [ ]
2025-09-23 19:24:22 +02:00
} ;
} ;
2025-11-19 21:01:06 +01:00
// Helper function to calculate total progress percentage
const calculateTotalProgressPercentage = ( batch : ProductionBatchResponse ) : number = > {
const allStages : QualityProcessStage [ ] = [ 'mixing' , 'proofing' , 'shaping' , 'baking' , 'cooling' , 'packaging' , 'finishing' ] ;
const currentStageIndex = allStages . indexOf ( batch . current_process_stage || 'mixing' ) ;
// Base percentage based on completed stages
const completedStages = batch . process_stage_history ? . length || 0 ;
const totalStages = allStages . length ;
const basePercentage = ( completedStages / totalStages ) * 100 ;
// If in the last stage, it should be 100% only if completed
if ( currentStageIndex === totalStages - 1 ) {
return batch . status === 'COMPLETED' ? 100 : Math.min ( 95 , basePercentage + 15 ) ; // Almost complete but not quite until marked as completed
}
// Add partial progress for current stage (estimated as 15% of the remaining percentage)
const remainingPercentage = 100 - basePercentage ;
const currentStageProgress = remainingPercentage * 0.15 ; // Current stage is 15% of remaining
return Math . min ( 100 , Math . round ( basePercentage + currentStageProgress ) ) ;
} ;
// Helper function to calculate estimated time remaining
const calculateEstimatedTimeRemaining = ( batch : ProductionBatchResponse ) : number | undefined = > {
// This would typically come from backend or be calculated based on historical data
// For now, returning a mock value or undefined
if ( batch . status === 'COMPLETED' ) return 0 ;
// Mock calculation based on typical stage times
const allStages : QualityProcessStage [ ] = [ 'mixing' , 'proofing' , 'shaping' , 'baking' , 'cooling' , 'packaging' , 'finishing' ] ;
const currentStageIndex = allStages . indexOf ( batch . current_process_stage || 'mixing' ) ;
if ( currentStageIndex === - 1 ) return undefined ;
// Return a mock value in minutes
const stagesRemaining = allStages . length - currentStageIndex - 1 ;
return stagesRemaining * 15 ; // Assuming ~15 mins per stage as an estimate
} ;
// Helper function to calculate current stage duration
const calculateCurrentStageDuration = ( batch : ProductionBatchResponse ) : number | undefined = > {
const currentStage = batch . current_process_stage ;
if ( ! currentStage || ! batch . process_stage_history ) return undefined ;
const currentStageHistory = batch . process_stage_history . find ( h = > h . stage === currentStage ) ;
if ( ! currentStageHistory || ! currentStageHistory . start_time ) return undefined ;
const startTime = new Date ( currentStageHistory . start_time ) ;
const now = new Date ( ) ;
const diffInMinutes = Math . ceil ( ( now . getTime ( ) - startTime . getTime ( ) ) / ( 1000 * 60 ) ) ;
return diffInMinutes ;
} ;
2025-08-28 10:41:04 +02:00
2025-09-21 07:45:19 +02:00
const batches = activeBatchesData ? . batches || [ ] ;
const filteredBatches = useMemo ( ( ) = > {
2025-09-26 12:12:17 +02:00
let filtered = batches ;
2025-09-21 07:45:19 +02:00
2025-09-26 12:12:17 +02:00
// Apply search filter
if ( searchQuery ) {
const searchLower = searchQuery . toLowerCase ( ) ;
filtered = filtered . filter ( batch = >
batch . product_name . toLowerCase ( ) . includes ( searchLower ) ||
batch . batch_number . toLowerCase ( ) . includes ( searchLower ) ||
( batch . staff_assigned && batch . staff_assigned . some ( staff = >
staff . toLowerCase ( ) . includes ( searchLower )
) )
) ;
}
// Apply status filter
if ( statusFilter ) {
filtered = filtered . filter ( batch = > batch . status === statusFilter ) ;
}
// Apply priority filter
if ( priorityFilter ) {
filtered = filtered . filter ( batch = > batch . priority === priorityFilter ) ;
}
return filtered ;
} , [ batches , searchQuery , statusFilter , priorityFilter ] ) ;
2025-09-21 07:45:19 +02:00
// Calculate production stats from real data
const productionStats = useMemo ( ( ) = > {
if ( ! dashboardData ) {
return {
activeBatches : 0 ,
todaysTarget : 0 ,
capacityUtilization : 0 ,
onTimeCompletion : 0 ,
qualityScore : 0 ,
totalOutput : 0 ,
efficiency : 0
} ;
}
return {
activeBatches : dashboardData.active_batches || 0 ,
todaysTarget : dashboardData.todays_production_plan?.length || 0 ,
capacityUtilization : Math.round ( dashboardData . capacity_utilization || 0 ) ,
onTimeCompletion : Math.round ( dashboardData . on_time_completion_rate || 0 ) ,
qualityScore : Math.round ( dashboardData . average_quality_score || 0 ) ,
totalOutput : dashboardData.total_output_today || 0 ,
efficiency : Math.round ( dashboardData . efficiency_percentage || 0 )
} ;
} , [ dashboardData ] ) ;
// Loading state
if ( ! tenantId || dashboardLoading || batchesLoading ) {
return (
< div className = "flex items-center justify-center min-h-64" >
< LoadingSpinner text = "Cargando producción..." / >
< / div >
) ;
}
// Error state
if ( dashboardError || batchesError ) {
return (
< div className = "text-center py-12" >
< AlertCircle className = "mx-auto h-12 w-12 text-red-500 mb-4" / >
< h3 className = "text-lg font-medium text-[var(--text-primary)] mb-2" >
Error al cargar la producción
< / h3 >
< p className = "text-[var(--text-secondary)] mb-4" >
{ ( dashboardError || batchesError ) ? . message || 'Ha ocurrido un error inesperado' }
< / p >
< Button onClick = { ( ) = > window . location . reload ( ) } >
Reintentar
< / Button >
< / div >
) ;
}
2025-08-28 18:07:16 +02:00
2025-08-28 10:41:04 +02:00
return (
2025-08-30 19:11:15 +02:00
< div className = "space-y-6" >
2025-10-24 13:05:04 +02:00
< PageHeader
title = "Gestión de Producción"
description = "Planifica y controla la producción diaria de tu panadería"
actions = { [
{
id : 'create-batch' ,
label : 'Nueva Orden de Producción' ,
icon : PlusCircle ,
2025-11-19 22:12:51 +01:00
onClick : ( ) = > setIsWizardOpen ( true ) ,
2025-10-24 13:05:04 +02:00
variant : 'primary' ,
size : 'md'
}
] }
/ >
2025-08-28 10:41:04 +02:00
{ /* Production Stats */ }
2025-09-21 07:45:19 +02:00
< StatsGrid
stats = { [
{
title : 'Lotes Activos' ,
value : productionStats.activeBatches ,
variant : 'default' as const ,
icon : Package ,
} ,
{
title : 'Utilización Capacidad' ,
value : ` ${ productionStats . capacityUtilization } % ` ,
variant : productionStats.capacityUtilization >= 80 ? 'success' as const : 'warning' as const ,
icon : Timer ,
} ,
{
title : 'Completado a Tiempo' ,
value : ` ${ productionStats . onTimeCompletion } % ` ,
variant : productionStats.onTimeCompletion >= 90 ? 'success' as const : 'error' as const ,
icon : CheckCircle ,
} ,
{
title : 'Puntuación Calidad' ,
value : ` ${ productionStats . qualityScore } % ` ,
variant : productionStats.qualityScore >= 85 ? 'success' as const : 'warning' as const ,
icon : Package ,
} ,
{
title : 'Producción Hoy' ,
value : formatters.number ( productionStats . totalOutput ) ,
variant : 'info' as const ,
icon : ChefHat ,
} ,
{
title : 'Eficiencia' ,
value : ` ${ productionStats . efficiency } % ` ,
variant : productionStats.efficiency >= 75 ? 'success' as const : 'warning' as const ,
icon : Timer ,
} ,
] }
2025-08-30 19:21:15 +02:00
columns = { 3 }
2025-08-30 19:11:15 +02:00
/ >
2025-08-28 10:41:04 +02:00
2025-09-24 20:14:49 +02:00
{ /* Production Batches Section - No tabs needed */ }
< >
2025-09-26 12:12:17 +02:00
{ /* Search and Filter Controls */ }
< SearchAndFilter
searchValue = { searchQuery }
onSearchChange = { setSearchQuery }
searchPlaceholder = "Buscar lotes por producto, número de lote o personal..."
filters = { [
{
key : 'status' ,
label : 'Estado' ,
type : 'dropdown' ,
value : statusFilter ,
onChange : ( value ) = > setStatusFilter ( value as string ) ,
placeholder : 'Todos los estados' ,
options : Object.values ( ProductionStatusEnum ) . map ( status = > ( {
value : status ,
label : status.replace ( /_/g , ' ' ) . replace ( /\b\w/g , l = > l . toUpperCase ( ) )
} ) )
} ,
{
key : 'priority' ,
label : 'Prioridad' ,
type : 'dropdown' ,
value : priorityFilter ,
onChange : ( value ) = > setPriorityFilter ( value as string ) ,
placeholder : 'Todas las prioridades' ,
options : Object.values ( ProductionPriorityEnum ) . map ( priority = > ( {
value : priority ,
label : priority.charAt ( 0 ) . toUpperCase ( ) + priority . slice ( 1 ) . toLowerCase ( )
} ) )
}
] as FilterConfig [ ] }
/ >
2025-08-30 19:11:15 +02:00
2025-09-21 07:45:19 +02:00
{ /* Production Batches Grid */ }
2025-08-30 19:11:15 +02:00
< div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-3" >
2025-09-23 15:57:22 +02:00
{ filteredBatches . map ( ( batch ) = > (
< ProductionStatusCard
key = { batch . id }
batch = { batch }
onView = { ( batch ) = > {
setSelectedBatch ( batch ) ;
setModalMode ( 'view' ) ;
setShowBatchModal ( true ) ;
} }
onStart = { async ( batch ) = > {
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId : batch.id ,
updates : { status : ProductionStatusEnum.IN_PROGRESS }
} ) ;
} catch ( error ) {
console . error ( 'Error starting batch:' , error ) ;
}
} }
onPause = { async ( batch ) = > {
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId : batch.id ,
updates : { status : ProductionStatusEnum.ON_HOLD }
} ) ;
} catch ( error ) {
console . error ( 'Error pausing batch:' , error ) ;
}
} }
onComplete = { async ( batch ) = > {
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId : batch.id ,
updates : { status : ProductionStatusEnum.QUALITY_CHECK }
} ) ;
} catch ( error ) {
console . error ( 'Error completing batch:' , error ) ;
}
} }
onCancel = { async ( batch ) = > {
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId : batch.id ,
updates : { status : ProductionStatusEnum.CANCELLED }
} ) ;
} catch ( error ) {
console . error ( 'Error cancelling batch:' , error ) ;
}
} }
onQualityCheck = { ( batch ) = > {
setSelectedBatch ( batch ) ;
setShowQualityModal ( true ) ;
} }
showDetailedProgress = { true }
/ >
) ) }
2025-08-28 10:41:04 +02:00
< / div >
2025-08-30 19:11:15 +02:00
{ /* Empty State */ }
2025-09-21 07:45:19 +02:00
{ filteredBatches . length === 0 && (
2025-10-27 16:33:26 +01:00
< EmptyState
icon = { ChefHat }
title = "No se encontraron lotes de producción"
description = {
batches . length === 0
2025-09-21 07:45:19 +02:00
? 'No hay lotes de producción activos. Crea el primer lote para comenzar.'
: 'Intenta ajustar la búsqueda o crear un nuevo lote de producción'
2025-10-27 16:33:26 +01:00
}
actionLabel = "Nueva Orden de Producción"
actionIcon = { Plus }
2025-11-19 22:12:51 +01:00
onAction = { ( ) = > setIsWizardOpen ( true ) }
2025-10-27 16:33:26 +01:00
/ >
2025-08-30 19:11:15 +02:00
) }
< / >
2025-08-28 10:41:04 +02:00
2025-08-30 19:11:15 +02:00
2025-09-23 12:49:35 +02:00
2025-11-19 22:12:51 +01:00
{ /* Production Batch Detail Modal */ }
2025-09-21 07:45:19 +02:00
{ showBatchModal && selectedBatch && (
2025-09-26 07:46:25 +02:00
< EditViewModal
2025-09-21 07:45:19 +02:00
isOpen = { showBatchModal }
2025-08-31 10:46:13 +02:00
onClose = { ( ) = > {
2025-09-21 07:45:19 +02:00
setShowBatchModal ( false ) ;
setSelectedBatch ( null ) ;
2025-08-31 10:46:13 +02:00
setModalMode ( 'view' ) ;
} }
mode = { modalMode }
onModeChange = { setModalMode }
2025-09-21 07:45:19 +02:00
title = { selectedBatch . product_name }
subtitle = { ` Lote de Producción # ${ selectedBatch . batch_number } ` }
2025-09-23 15:57:22 +02:00
statusIndicator = { {
color : statusColors.inProgress.primary ,
2025-09-26 07:46:25 +02:00
text : t ( ` production:status. ${ selectedBatch . status . toLowerCase ( ) } ` ) ,
2025-09-23 15:57:22 +02:00
icon : Package
} }
2025-11-19 22:12:51 +01:00
size = "xl"
2025-08-31 10:46:13 +02:00
sections = { [
{
title : 'Información General' ,
icon : Package ,
fields : [
2025-11-19 22:12:51 +01:00
{
label : 'Producto' ,
value : selectedBatch.product_name ,
highlight : true
} ,
{
label : 'Número de Lote' ,
value : selectedBatch.batch_number
} ,
2025-08-31 10:46:13 +02:00
{
2025-09-21 07:45:19 +02:00
label : 'Cantidad Planificada' ,
value : ` ${ selectedBatch . planned_quantity } unidades ` ,
2025-08-31 10:46:13 +02:00
highlight : true
} ,
{
2025-11-19 22:12:51 +01:00
label : 'Cantidad Producida' ,
2025-09-21 07:45:19 +02:00
value : selectedBatch.actual_quantity
? ` ${ selectedBatch . actual_quantity } unidades `
: 'Pendiente' ,
editable : modalMode === 'edit' ,
type : 'number'
2025-08-31 10:46:13 +02:00
} ,
{
2025-11-19 22:12:51 +01:00
label : 'Estado' ,
value : selectedBatch.status ,
2025-09-21 07:45:19 +02:00
type : 'select' ,
editable : modalMode === 'edit' ,
2025-11-19 22:12:51 +01:00
options : Object.values ( ProductionStatusEnum ) . map ( value = > ( {
2025-09-26 07:46:25 +02:00
value ,
2025-11-19 22:12:51 +01:00
label : t ( ` production:status. ${ value . toLowerCase ( ) } ` )
2025-09-26 07:46:25 +02:00
} ) )
2025-08-31 10:46:13 +02:00
} ,
{
2025-11-19 22:12:51 +01:00
label : 'Prioridad' ,
value : selectedBatch.priority ,
2025-09-21 07:45:19 +02:00
type : 'select' ,
editable : modalMode === 'edit' ,
2025-11-19 22:12:51 +01:00
options : Object.values ( ProductionPriorityEnum ) . map ( value = > ( {
2025-09-26 07:46:25 +02:00
value ,
2025-11-19 22:12:51 +01:00
label : t ( ` production:priority. ${ value . toLowerCase ( ) } ` )
2025-09-26 07:46:25 +02:00
} ) )
2025-09-21 07:45:19 +02:00
} ,
{
label : 'Personal Asignado' ,
value : selectedBatch.staff_assigned?.join ( ', ' ) || 'No asignado' ,
editable : modalMode === 'edit' ,
type : 'text'
2025-11-19 22:12:51 +01:00
} ,
{
label : 'Equipos Utilizados' ,
value : selectedBatch.equipment_used?.join ( ', ' ) || 'No especificado'
2025-08-31 10:46:13 +02:00
}
]
} ,
{
title : 'Cronograma' ,
icon : Clock ,
fields : [
{
2025-09-21 07:45:19 +02:00
label : 'Inicio Planificado' ,
value : selectedBatch.planned_start_time ,
type : 'datetime'
} ,
{
label : 'Fin Planificado' ,
value : selectedBatch.planned_end_time ,
type : 'datetime'
} ,
2025-11-19 22:12:51 +01:00
{
label : 'Duración Planificada' ,
value : selectedBatch.planned_duration_minutes
? ` ${ selectedBatch . planned_duration_minutes } minutos `
: 'No especificada'
} ,
2025-09-21 07:45:19 +02:00
{
label : 'Inicio Real' ,
value : selectedBatch.actual_start_time || 'Pendiente' ,
2025-08-31 10:46:13 +02:00
type : 'datetime'
} ,
{
2025-09-21 07:45:19 +02:00
label : 'Fin Real' ,
value : selectedBatch.actual_end_time || 'Pendiente' ,
2025-08-31 10:46:13 +02:00
type : 'datetime'
2025-11-19 22:12:51 +01:00
} ,
{
label : 'Duración Real' ,
value : selectedBatch.actual_duration_minutes
? ` ${ selectedBatch . actual_duration_minutes } minutos `
: 'Pendiente'
2025-08-31 10:46:13 +02:00
}
]
2025-09-21 07:45:19 +02:00
} ,
2025-09-23 15:57:22 +02:00
{
2025-11-19 22:12:51 +01:00
title : 'Fases del Proceso de Producción' ,
2025-09-23 15:57:22 +02:00
icon : Timer ,
fields : [
{
2025-09-23 19:24:22 +02:00
label : '' ,
value : (
2025-11-19 21:01:06 +01:00
< ProcessStageTracker
processStage = { {
current : selectedBatch.current_process_stage as QualityProcessStage || 'mixing' ,
history : selectedBatch.process_stage_history ? selectedBatch . process_stage_history . map ( ( item : any ) = > ( {
stage : item.stage as QualityProcessStage ,
start_time : item.start_time || item . timestamp ,
end_time : item.end_time ,
duration : item.duration ,
notes : item.notes ,
personnel : item.personnel
} ) ) : [ ] ,
pendingQualityChecks : selectedBatch.pending_quality_checks ? selectedBatch . pending_quality_checks . map ( ( item : any ) = > ( {
id : item.id || '' ,
name : item.name || '' ,
stage : item.stage as QualityProcessStage || 'mixing' ,
isRequired : item.isRequired || item . is_required || false ,
isCritical : item.isCritical || item . is_critical || false ,
status : item.status || 'pending' ,
checkType : item.checkType || item . check_type || 'visual'
} ) ) : [ ] ,
completedQualityChecks : selectedBatch.completed_quality_checks ? selectedBatch . completed_quality_checks . map ( ( item : any ) = > ( {
id : item.id || '' ,
name : item.name || '' ,
stage : item.stage as QualityProcessStage || 'mixing' ,
isRequired : item.isRequired || item . is_required || false ,
isCritical : item.isCritical || item . is_critical || false ,
status : item.status || 'completed' ,
checkType : item.checkType || item . check_type || 'visual'
} ) ) : [ ] ,
totalProgressPercentage : calculateTotalProgressPercentage ( selectedBatch ) ,
estimatedTimeRemaining : calculateEstimatedTimeRemaining ( selectedBatch ) ,
currentStageDuration : calculateCurrentStageDuration ( selectedBatch )
} }
2025-09-23 19:24:22 +02:00
onAdvanceStage = { ( currentStage ) = > handleStageAdvance ( selectedBatch . id , currentStage ) }
onQualityCheck = { ( checkId ) = > {
setShowQualityModal ( true ) ;
console . log ( 'Opening quality check:' , checkId ) ;
} }
2025-11-19 21:01:06 +01:00
onViewStageDetails = { ( stage ) = > {
console . log ( 'View stage details:' , stage ) ;
// This would open a detailed view for the stage
} }
onStageAction = { ( stage , action ) = > {
console . log ( 'Stage action:' , stage , action ) ;
// This would handle stage-specific actions
} }
2025-09-23 19:24:22 +02:00
className = "w-full"
/ >
) ,
span : 2
2025-09-23 15:57:22 +02:00
}
]
} ,
2025-12-13 23:57:54 +01:00
{
title : 'Detalles del Razonamiento' ,
icon : Info ,
fields : [
{
label : 'Causa Principal' ,
value : selectedBatch.reasoning_data?.trigger_type
? t ( ` reasoning:triggers. ${ selectedBatch . reasoning_data . trigger_type . toLowerCase ( ) } ` )
: 'No especificado' ,
span : 2
} ,
{
label : 'Descripción del Razonamiento' ,
value : selectedBatch.reasoning_data?.trigger_description || 'No especificado' ,
type : 'textarea' ,
span : 2
} ,
{
label : 'Factores Clave' ,
value : selectedBatch.reasoning_data?.factors && Array . isArray ( selectedBatch . reasoning_data . factors )
? selectedBatch . reasoning_data . factors . map ( factor = >
t ( ` reasoning:factors. ${ factor . toLowerCase ( ) } ` ) || factor
) . join ( ', ' )
: 'No especificados' ,
span : 2
} ,
{
label : 'Consecuencias Potenciales' ,
value : selectedBatch.reasoning_data?.consequence || 'No especificado' ,
type : 'textarea' ,
span : 2
} ,
{
label : 'Nivel de Confianza' ,
value : selectedBatch.reasoning_data?.confidence_score
? ` ${ selectedBatch . reasoning_data . confidence_score } % `
: 'No especificado'
} ,
{
label : 'Variación Histórica' ,
value : selectedBatch.reasoning_data?.variance
? ` ${ selectedBatch . reasoning_data . variance } % `
: 'No especificado'
} ,
{
label : 'Detalles de la Predicción' ,
value : selectedBatch.reasoning_data?.prediction_details || 'No especificado' ,
type : 'textarea' ,
span : 2
}
]
} ,
2025-09-21 07:45:19 +02:00
{
title : 'Calidad y Costos' ,
icon : CheckCircle ,
fields : [
{
label : 'Puntuación de Calidad' ,
value : selectedBatch.quality_score
? ` ${ selectedBatch . quality_score } /10 `
2025-11-19 22:12:51 +01:00
: 'Pendiente' ,
highlight : selectedBatch.quality_score ? selectedBatch . quality_score >= 8 : false
2025-09-21 07:45:19 +02:00
} ,
{
label : 'Rendimiento' ,
value : selectedBatch.yield_percentage
? ` ${ selectedBatch . yield_percentage } % `
: 'Calculando...'
} ,
{
label : 'Costo Estimado' ,
value : selectedBatch.estimated_cost || 0 ,
type : 'currency'
} ,
{
label : 'Costo Real' ,
value : selectedBatch.actual_cost || 0 ,
type : 'currency'
2025-11-19 22:12:51 +01:00
} ,
{
label : 'Notas de Producción' ,
value : selectedBatch.production_notes || 'Sin notas' ,
type : 'textarea' ,
editable : modalMode === 'edit' ,
span : 2
} ,
{
label : 'Notas de Calidad' ,
value : selectedBatch.quality_notes || 'Sin notas de calidad' ,
type : 'textarea' ,
span : 2
2025-09-21 07:45:19 +02:00
}
]
2025-08-31 10:46:13 +02:00
}
] }
2025-09-21 07:45:19 +02:00
onSave = { async ( ) = > {
try {
// Implementation would depend on specific fields changed
console . log ( 'Saving batch changes:' , selectedBatch . id ) ;
// await updateBatchStatusMutation.mutateAsync({
// batchId: selectedBatch.id,
// updates: selectedBatch
// });
setShowBatchModal ( false ) ;
setSelectedBatch ( null ) ;
setModalMode ( 'view' ) ;
} catch ( error ) {
console . error ( 'Error saving batch:' , error ) ;
}
} }
onFieldChange = { ( sectionIndex , fieldIndex , value ) = > {
if ( ! selectedBatch ) return ;
const fieldMapping : Record < string , string > = {
2025-11-19 22:12:51 +01:00
// General Information
'Cantidad Producida' : 'actual_quantity' ,
2025-09-21 07:45:19 +02:00
'Estado' : 'status' ,
2025-11-19 22:12:51 +01:00
'Prioridad' : 'priority' ,
'Personal Asignado' : 'staff_assigned' ,
2025-12-13 23:57:54 +01:00
// Reasoning section editable fields
'Descripción del Razonamiento' : 'reasoning_data.trigger_description' ,
'Consecuencias Potenciales' : 'reasoning_data.consequence' ,
'Detalles de la Predicción' : 'reasoning_data.prediction_details' ,
2025-11-19 22:12:51 +01:00
// Schedule - most fields are read-only datetime
// Quality and Costs
'Notas de Producción' : 'production_notes' ,
'Notas de Calidad' : 'quality_notes'
2025-09-21 07:45:19 +02:00
} ;
// Get section labels to map back to field names
const sectionLabels = [
2025-11-19 22:12:51 +01:00
[ 'Producto' , 'Número de Lote' , 'Cantidad Planificada' , 'Cantidad Producida' , 'Estado' , 'Prioridad' , 'Personal Asignado' , 'Equipos Utilizados' ] ,
[ 'Inicio Planificado' , 'Fin Planificado' , 'Duración Planificada' , 'Inicio Real' , 'Fin Real' , 'Duración Real' ] ,
[ ] , // Process Stage Tracker section - no editable fields
2025-12-13 23:57:54 +01:00
[ 'Causa Principal' , 'Descripción del Razonamiento' , 'Factores Clave' , 'Consecuencias Potenciales' , 'Nivel de Confianza' , 'Variación Histórica' , 'Detalles de la Predicción' ] , // Reasoning section
2025-11-19 22:12:51 +01:00
[ 'Puntuación de Calidad' , 'Rendimiento' , 'Costo Estimado' , 'Costo Real' , 'Notas de Producción' , 'Notas de Calidad' ]
2025-09-21 07:45:19 +02:00
] ;
const fieldLabel = sectionLabels [ sectionIndex ] ? . [ fieldIndex ] ;
2025-11-19 22:12:51 +01:00
const propertyName = fieldMapping [ fieldLabel ] ;
2025-09-21 07:45:19 +02:00
if ( propertyName ) {
let processedValue : any = value ;
2025-11-19 22:12:51 +01:00
// Process specific field types
2025-09-21 07:45:19 +02:00
if ( propertyName === 'staff_assigned' && typeof value === 'string' ) {
processedValue = value . split ( ',' ) . map ( s = > s . trim ( ) ) . filter ( s = > s . length > 0 ) ;
} else if ( propertyName === 'actual_quantity' ) {
processedValue = parseFloat ( value as string ) || 0 ;
}
2025-12-13 23:57:54 +01:00
// Handle nested reasoning_data fields
if ( propertyName . startsWith ( 'reasoning_data.' ) ) {
const nestedProperty = propertyName . split ( '.' ) [ 1 ] ;
setSelectedBatch ( {
. . . selectedBatch ,
reasoning_data : {
. . . ( selectedBatch . reasoning_data || { } ) ,
[ nestedProperty ] : processedValue
}
} ) ;
} else {
setSelectedBatch ( {
. . . selectedBatch ,
[ propertyName ] : processedValue
} ) ;
}
2025-09-21 07:45:19 +02:00
}
2025-08-31 10:46:13 +02:00
} }
/ >
2025-08-30 19:11:15 +02:00
) }
2025-09-21 07:45:19 +02:00
2025-11-19 22:12:51 +01:00
{ /* Unified Add Wizard for Production Batches */ }
< UnifiedAddWizard
isOpen = { isWizardOpen }
onClose = { ( ) = > setIsWizardOpen ( false ) }
onComplete = { ( itemType : ItemType , data? : any ) = > {
console . log ( 'Production batch created:' , data ) ;
refetchBatches ( ) ;
} }
initialItemType = "production-batch"
2025-09-21 07:45:19 +02:00
/ >
2025-09-23 15:57:22 +02:00
{ /* Quality Check Modal */ }
{ showQualityModal && selectedBatch && (
< QualityCheckModal
isOpen = { showQualityModal }
onClose = { ( ) = > {
setShowQualityModal ( false ) ;
setSelectedBatch ( null ) ;
} }
batch = { selectedBatch }
onComplete = { async ( result ) = > {
console . log ( 'Quality check completed:' , result ) ;
// Optionally update batch status to completed or quality_passed
try {
await updateBatchStatusMutation . mutateAsync ( {
batchId : selectedBatch.id ,
updates : {
status : result.overallPass ? ProductionStatusEnum.COMPLETED : ProductionStatusEnum.ON_HOLD
}
} ) ;
} catch ( error ) {
console . error ( 'Error updating batch status after quality check:' , error ) ;
}
} }
/ >
) }
2025-08-28 10:41:04 +02:00
< / div >
) ;
} ;
2025-10-30 21:08:07 +01:00
export default ProductionPage ;