import React, { useState, useEffect } from 'react'; import { Edit, ClipboardCheck, Target, Settings, Cog } from 'lucide-react'; import { EditViewModal, EditViewModalSection } from '../../ui/EditViewModal/EditViewModal'; import { QualityCheckType, ProcessStage, type QualityCheckTemplate, type QualityCheckTemplateUpdate } from '../../../api/types/qualityTemplates'; import { statusColors } from '../../../styles/colors'; import { useTranslation } from 'react-i18next'; interface EditQualityTemplateModalProps { isOpen: boolean; onClose: () => void; template: QualityCheckTemplate; onUpdateTemplate: (templateData: QualityCheckTemplateUpdate) => Promise; isLoading?: boolean; } const PROCESS_STAGE_OPTIONS = [ { value: ProcessStage.MIXING, label: 'Mezclado' }, { value: ProcessStage.PROOFING, label: 'Fermentación' }, { value: ProcessStage.SHAPING, label: 'Formado' }, { value: ProcessStage.BAKING, label: 'Horneado' }, { value: ProcessStage.COOLING, label: 'Enfriado' }, { value: ProcessStage.PACKAGING, label: 'Empaquetado' }, { value: ProcessStage.FINISHING, label: 'Acabado' } ]; const QUALITY_CHECK_TYPE_OPTIONS = [ { value: QualityCheckType.VISUAL, label: 'Visual - Inspección visual' }, { value: QualityCheckType.MEASUREMENT, label: 'Medición - Medidas precisas' }, { value: QualityCheckType.TEMPERATURE, label: 'Temperatura - Control térmico' }, { value: QualityCheckType.WEIGHT, label: 'Peso - Control de peso' }, { value: QualityCheckType.BOOLEAN, label: 'Sí/No - Verificación binaria' }, { value: QualityCheckType.TIMING, label: 'Tiempo - Control temporal' } ]; const CATEGORY_OPTIONS_KEYS = [ { value: '', key: '' }, { value: 'appearance', key: 'appearance' }, { value: 'structure', key: 'structure' }, { value: 'texture', key: 'texture' }, { value: 'flavor', key: 'flavor' }, { value: 'safety', key: 'safety' }, { value: 'packaging', key: 'packaging' }, { value: 'temperature', key: 'temperature' }, { value: 'weight', key: 'weight' }, { value: 'dimensions', key: 'dimensions' }, { value: 'weight_check', key: 'weight_check' }, { value: 'temperature_check', key: 'temperature_check' }, { value: 'moisture_check', key: 'moisture_check' }, { value: 'volume_check', key: 'volume_check' }, { value: 'time_check', key: 'time_check' }, { value: 'chemical', key: 'chemical' }, { value: 'hygiene', key: 'hygiene' } ]; // Component for managing process stages selection (multiselect) const ProcessStagesSelector: React.FC<{ value: ProcessStage[]; onChange: (stages: ProcessStage[]) => void; }> = ({ value, onChange }) => { const handleToggle = (stage: ProcessStage) => { const newStages = value.includes(stage) ? value.filter(s => s !== stage) : [...value, stage]; onChange(newStages); }; return (
{PROCESS_STAGE_OPTIONS.map(stage => ( ))}
); }; export const EditQualityTemplateModal: React.FC = ({ isOpen, onClose, template, onUpdateTemplate, isLoading = false }) => { const { t } = useTranslation(); const [mode, setMode] = useState<'view' | 'edit'>('edit'); const [editedTemplate, setEditedTemplate] = useState(template); const [selectedStages, setSelectedStages] = useState( template.applicable_stages || [] ); // Helper function to get translated category label const getCategoryLabel = (category: string | null | undefined): string => { if (!category) return t('production.quality.categories.appearance', 'Sin categoría'); const translationKey = `production.quality.categories.${category}`; const translated = t(translationKey); // If translation is same as key, it means no translation exists, return the original return translated === translationKey ? category : translated; }; // Build category options with translations const getCategoryOptions = () => { return CATEGORY_OPTIONS_KEYS.map(option => ({ value: option.value, label: option.value === '' ? 'Seleccionar categoría' : getCategoryLabel(option.key) })); }; // Update local state when template changes useEffect(() => { if (template) { setEditedTemplate(template); setSelectedStages(template.applicable_stages || []); } }, [template]); const checkType = editedTemplate.check_type; const showMeasurementFields = [ QualityCheckType.MEASUREMENT, QualityCheckType.TEMPERATURE, QualityCheckType.WEIGHT ].includes(checkType); const handleFieldChange = (sectionIndex: number, fieldIndex: number, value: string | number) => { const sections = getSections(); const field = sections[sectionIndex].fields[fieldIndex]; const fieldLabel = field.label; // Map field labels to template properties const fieldMap: Record = { 'Nombre': 'name', 'Código de Plantilla': 'template_code', 'Tipo de Control': 'check_type', 'Categoría': 'category', 'Descripción': 'description', 'Instrucciones para el Personal': 'instructions', 'Valor Mínimo': 'min_value', 'Valor Máximo': 'max_value', 'Valor Objetivo': 'target_value', 'Unidad': 'unit', 'Tolerancia (%)': 'tolerance_percentage', 'Etapas Aplicables': 'stages', 'Peso en Puntuación General': 'weight', 'Plantilla Activa': 'is_active', 'Control Requerido': 'is_required', 'Control Crítico': 'is_critical' }; const propertyName = fieldMap[fieldLabel]; if (propertyName) { if (propertyName === 'stages') { // Handle stages separately through the custom component return; } // Convert string booleans to actual booleans let processedValue: any = value; if (propertyName === 'is_active' || propertyName === 'is_required' || propertyName === 'is_critical') { processedValue = String(value) === 'true'; } setEditedTemplate(prev => ({ ...prev, [propertyName]: processedValue })); } }; const handleSave = async () => { try { // Build update object with only changed fields const updates: QualityCheckTemplateUpdate = {}; // Check each field for changes Object.entries(editedTemplate).forEach(([key, value]) => { const originalValue = (template as any)[key]; if (value !== originalValue && key !== 'id' && key !== 'created_at' && key !== 'updated_at' && key !== 'created_by') { (updates as any)[key] = value; } }); // Handle applicable stages const stagesChanged = JSON.stringify(selectedStages.sort()) !== JSON.stringify((template.applicable_stages || []).sort()); if (stagesChanged) { updates.applicable_stages = selectedStages.length > 0 ? selectedStages : undefined; } // Only submit if there are actual changes if (Object.keys(updates).length > 0) { await onUpdateTemplate(updates); } setMode('view'); } catch (error) { console.error('Error updating template:', error); throw error; } }; const handleCancel = () => { // Reset to original values setEditedTemplate(template); setSelectedStages(template.applicable_stages || []); setMode('view'); }; const getSections = (): EditViewModalSection[] => { const sections: EditViewModalSection[] = [ { title: 'Información Básica', icon: ClipboardCheck, fields: [ { label: 'Nombre', value: editedTemplate.name, type: 'text', highlight: true, editable: true, required: true, placeholder: 'Ej: Control Visual de Pan' }, { label: 'Código de Plantilla', value: editedTemplate.template_code || '', type: 'text', editable: true, placeholder: 'Ej: CV_PAN_01' }, { label: 'Tipo de Control', value: editedTemplate.check_type, type: 'select', editable: true, required: true, options: QUALITY_CHECK_TYPE_OPTIONS }, { label: 'Categoría', value: editedTemplate.category || '', type: 'select', editable: true, options: getCategoryOptions() }, { label: 'Descripción', value: editedTemplate.description || '', type: 'textarea', editable: true, placeholder: 'Describe qué evalúa esta plantilla de calidad', span: 2 }, { label: 'Instrucciones para el Personal', value: editedTemplate.instructions || '', type: 'textarea', editable: true, placeholder: 'Instrucciones detalladas para realizar este control de calidad', span: 2, helpText: 'Pasos específicos que debe seguir el operario' } ] } ]; // Add measurement configuration section if applicable if (showMeasurementFields) { sections.push({ title: 'Configuración de Medición', icon: Target, fields: [ { label: 'Valor Mínimo', value: editedTemplate.min_value || 0, type: 'number', editable: true, placeholder: '0', helpText: 'Valor mínimo aceptable para la medición' }, { label: 'Valor Máximo', value: editedTemplate.max_value || 0, type: 'number', editable: true, placeholder: '100', helpText: 'Valor máximo aceptable para la medición' }, { label: 'Valor Objetivo', value: editedTemplate.target_value || 0, type: 'number', editable: true, placeholder: '50', helpText: 'Valor ideal que se busca alcanzar' }, { label: 'Unidad', value: editedTemplate.unit || '', type: 'text', editable: true, placeholder: '°C / g / cm', helpText: 'Unidad de medida (ej: °C para temperatura)' }, { label: 'Tolerancia (%)', value: editedTemplate.tolerance_percentage || 0, type: 'number', editable: true, placeholder: '5', helpText: 'Porcentaje de tolerancia permitido' } ] }); } // Process stages section with custom component sections.push({ title: 'Etapas del Proceso', icon: Settings, description: 'Selecciona las etapas donde se debe aplicar este control. Si no seleccionas ninguna, se aplicará a todas las etapas.', fields: [ { label: 'Etapas Aplicables', value: selectedStages.length > 0 ? selectedStages.map(s => PROCESS_STAGE_OPTIONS.find(opt => opt.value === s)?.label || s).join(', ') : 'Todas las etapas', type: 'component', editable: true, component: ProcessStagesSelector, componentProps: { value: selectedStages, onChange: setSelectedStages }, span: 2 } ] }); // Configuration section sections.push({ title: 'Configuración Avanzada', icon: Cog, fields: [ { label: 'Peso en Puntuación General', value: editedTemplate.weight, type: 'number', editable: true, placeholder: '1.0', helpText: 'Mayor peso = mayor importancia en la puntuación final (0-10)', validation: (value: string | number) => { const num = Number(value); return num < 0 || num > 10 ? 'El peso debe estar entre 0 y 10' : null; } }, { label: 'Plantilla Activa', value: editedTemplate.is_active, type: 'select', editable: true, options: [ { value: 'true', label: 'Sí' }, { value: 'false', label: 'No' } ] }, { label: 'Control Requerido', value: editedTemplate.is_required, type: 'select', editable: true, options: [ { value: 'true', label: 'Sí' }, { value: 'false', label: 'No' } ], helpText: 'Si es requerido, debe completarse obligatoriamente' }, { label: 'Control Crítico', value: editedTemplate.is_critical, type: 'select', editable: true, options: [ { value: 'true', label: 'Sí' }, { value: 'false', label: 'No' } ], helpText: 'Si es crítico, bloquea la producción si falla' } ] }); // Template metadata section sections.push({ title: 'Información de la Plantilla', icon: ClipboardCheck, collapsible: true, collapsed: true, fields: [ { label: 'ID', value: editedTemplate.id, type: 'text', editable: false }, { label: 'Creado', value: editedTemplate.created_at, type: 'datetime', editable: false }, { label: 'Última Actualización', value: editedTemplate.updated_at, type: 'datetime', editable: false } ] }); return sections; }; const getStatusIndicator = () => { const typeColors: Record = { [QualityCheckType.VISUAL]: '#3B82F6', [QualityCheckType.MEASUREMENT]: '#10B981', [QualityCheckType.TEMPERATURE]: '#EF4444', [QualityCheckType.WEIGHT]: '#A855F7', [QualityCheckType.BOOLEAN]: '#6B7280', [QualityCheckType.TIMING]: '#F59E0B', [QualityCheckType.CHECKLIST]: '#6366F1' }; return { color: editedTemplate.is_active ? typeColors[editedTemplate.check_type] : '#6B7280', text: editedTemplate.is_active ? 'Activa' : 'Inactiva', icon: Edit, isCritical: editedTemplate.is_critical, isHighlight: editedTemplate.is_required }; }; return ( ); }; export default EditQualityTemplateModal;