From e49577eadb8d4c62bdd248d3638e2994d67a116e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Nov 2025 07:43:12 +0000 Subject: [PATCH] feat: Rewrite QualityTemplateWizard with comprehensive field support Complete rewrite following the established pattern. Key improvements: - Removed internal "Crear Plantilla" button and API call - Added validate prop with required field checks (name, checkType, weight) - Real-time data sync with parent wizard using useEffect - Auto-generation of template_code from name (TPL-XXX-1234) - Added ALL 25 backend fields from research: * Required: name, checkType, weight * Basic: templateCode, description, applicableStages * Check Points: checkPoints (JSON array configuration) * Scoring: scoringMethod, passThreshold, isRequired, frequencyDays * Advanced Config (JSONB): parameters, thresholds, scoringCriteria * Status: isActive, version * Helper fields: requiresPhoto, criticalControlPoint, notifyOnFail, responsibleRole, requiredEquipment, acceptanceCriteria, specificConditions - Organized fields using AdvancedOptionsSection for progressive disclosure - Added tooltips for complex fields using existing Tooltip component - Expanded check_type options (7 types vs original 4) - Comprehensive validation for required fields only - Note: API integration removed from wizard step, should be handled by parent component on wizard completion --- .../wizards/QualityTemplateWizard.tsx | 596 +++++++++++------- 1 file changed, 385 insertions(+), 211 deletions(-) diff --git a/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx index 2be85e72..7f2e8481 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/QualityTemplateWizard.tsx @@ -1,230 +1,405 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; -import { ClipboardCheck, CheckCircle2, Loader2 } from 'lucide-react'; -import { useTenant } from '../../../../stores/tenant.store'; -import { qualityTemplateService } from '../../../../api/services/qualityTemplates'; -import { QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates'; -import { showToast } from '../../../../utils/toast'; +import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection'; +import Tooltip from '../../../ui/Tooltip/Tooltip'; +import { Info } from 'lucide-react'; interface WizardDataProps extends WizardStepProps { data: Record; onDataChange: (data: Record) => void; } -const TemplateInfoStep: React.FC = ({ data, onDataChange, onComplete }) => { - const { currentTenant } = useTenant(); +// Single comprehensive step with all fields +const QualityTemplateDetailsStep: React.FC = ({ data, onDataChange }) => { const [templateData, setTemplateData] = useState({ + // Required fields name: data.name || '', - scope: data.scope || 'product', - frequency: data.frequency || 'batch', - frequencyTime: data.frequencyTime || '', + checkType: data.checkType || 'product_quality', + weight: data.weight || '5.0', + + // Basic fields + templateCode: data.templateCode || '', + description: data.description || '', + applicableStages: data.applicableStages || '', + + // Check points configuration + checkPoints: data.checkPoints || '', + + // Scoring configuration + scoringMethod: data.scoringMethod || 'weighted_average', + passThreshold: data.passThreshold || '70.0', + isRequired: data.isRequired ?? false, + frequencyDays: data.frequencyDays || '', + + // Advanced configuration (JSONB fields) + parameters: data.parameters || '', + thresholds: data.thresholds || '', + scoringCriteria: data.scoringCriteria || '', + + // Status + isActive: data.isActive ?? true, + version: data.version || '1.0', + + // Helper fields for UI + requiresPhoto: data.requiresPhoto ?? false, + criticalControlPoint: data.criticalControlPoint ?? false, + notifyOnFail: data.notifyOnFail ?? false, responsibleRole: data.responsibleRole || '', - requiresPhoto: data.requiresPhoto || false, - criticalControlPoint: data.criticalControlPoint || false, requiredEquipment: data.requiredEquipment || '', acceptanceCriteria: data.acceptanceCriteria || '', - notifyOnFail: data.notifyOnFail || false, specificConditions: data.specificConditions || '', }); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const handleSave = async () => { - if (!currentTenant?.id) { - setError('No se pudo obtener información del tenant'); - return; + // Auto-generate template code from name if not provided + useEffect(() => { + if (!templateData.templateCode && templateData.name) { + const code = `TPL-${templateData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`; + setTemplateData(prev => ({ ...prev, templateCode: code })); } + }, [templateData.name]); - setLoading(true); - setError(null); - - try { - const scopeMapping: Record = { - product: 'product_quality', - process: 'process_hygiene', - equipment: 'equipment', - safety: 'safety' - }; - - // Build comprehensive description - let description = `Plantilla de ${templateData.scope} con frecuencia ${templateData.frequency}.`; - if (templateData.frequencyTime) { - description += ` ${templateData.frequencyTime}.`; - } - if (templateData.responsibleRole) { - description += ` Responsable: ${templateData.responsibleRole}.`; - } - if (templateData.requiredEquipment) { - description += ` Equipo requerido: ${templateData.requiredEquipment}.`; - } - if (templateData.acceptanceCriteria) { - description += ` Criterios: ${templateData.acceptanceCriteria}.`; - } - if (templateData.specificConditions) { - description += ` Condiciones: ${templateData.specificConditions}.`; - } - if (templateData.requiresPhoto) { - description += ` Requiere fotografía.`; - } - - const templateCreateData: QualityCheckTemplateCreate = { - name: templateData.name, - description: description, - check_type: scopeMapping[templateData.scope] || 'product_quality', - applicable_stages: [], - check_points: [ - { - name: templateData.acceptanceCriteria ? 'Verificación de Criterios' : 'Verificación General', - description: templateData.acceptanceCriteria || 'Punto de verificación inicial', - expected_value: 'Conforme', - measurement_type: 'pass_fail', - is_critical: templateData.criticalControlPoint, - weight: 1.0 - } - ], - scoring_method: 'weighted_average', - pass_threshold: 70.0, - weight: templateData.criticalControlPoint ? 10.0 : 5.0, - is_required: templateData.frequency === 'batch' || templateData.criticalControlPoint, - is_active: true, - frequency_days: templateData.frequency === 'daily' ? 1 : templateData.frequency === 'weekly' ? 7 : undefined - }; - - await qualityTemplateService.createTemplate(currentTenant.id, templateCreateData); - - showToast.success('Plantilla de calidad creada exitosamente'); - onDataChange({ ...data, ...templateData }); - onComplete(); - } catch (err: any) { - console.error('Error creating quality template:', err); - const errorMessage = err.response?.data?.detail || 'Error al crear la plantilla de calidad'; - setError(errorMessage); - showToast.error(errorMessage); - } finally { - setLoading(false); - } - }; + // Sync with parent wizard state in real-time + useEffect(() => { + onDataChange({ ...data, ...templateData }); + }, [templateData]); return (
- -

Plantilla de Calidad

+

+ Quality Template Details +

+

+ Fill in the required information to create a quality check template +

- {error && ( -
- {error} + {/* Required Fields */} +
+
+ + setTemplateData({ ...templateData, name: e.target.value })} + placeholder="E.g., Bread Quality Control, Hygiene Inspection" + className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" + />
- )} -
- {/* Basic Information */} +
+ + +
+ +
+ + setTemplateData({ ...templateData, weight: e.target.value })} + placeholder="5.0" + step="0.1" + min="0" + max="10" + className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" + /> +
+
+ + {/* Basic Information */} +
+

Basic Information

+
+
+ + setTemplateData({ ...templateData, templateCode: e.target.value })} + placeholder="TPL-XXX-1234" + className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" + /> +
+ +
+ + setTemplateData({ ...templateData, version: e.target.value })} + placeholder="1.0" + className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" + /> +
+ +
+ +