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
This commit is contained in:
Claude
2025-11-10 07:43:12 +00:00
parent fa43fddcbd
commit e49577eadb

View File

@@ -1,230 +1,405 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal'; import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import { ClipboardCheck, CheckCircle2, Loader2 } from 'lucide-react'; import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
import { useTenant } from '../../../../stores/tenant.store'; import Tooltip from '../../../ui/Tooltip/Tooltip';
import { qualityTemplateService } from '../../../../api/services/qualityTemplates'; import { Info } from 'lucide-react';
import { QualityCheckTemplateCreate } from '../../../../api/types/qualityTemplates';
import { showToast } from '../../../../utils/toast';
interface WizardDataProps extends WizardStepProps { interface WizardDataProps extends WizardStepProps {
data: Record<string, any>; data: Record<string, any>;
onDataChange: (data: Record<string, any>) => void; onDataChange: (data: Record<string, any>) => void;
} }
const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onComplete }) => { // Single comprehensive step with all fields
const { currentTenant } = useTenant(); const QualityTemplateDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange }) => {
const [templateData, setTemplateData] = useState({ const [templateData, setTemplateData] = useState({
// Required fields
name: data.name || '', name: data.name || '',
scope: data.scope || 'product', checkType: data.checkType || 'product_quality',
frequency: data.frequency || 'batch', weight: data.weight || '5.0',
frequencyTime: data.frequencyTime || '',
// 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 || '', responsibleRole: data.responsibleRole || '',
requiresPhoto: data.requiresPhoto || false,
criticalControlPoint: data.criticalControlPoint || false,
requiredEquipment: data.requiredEquipment || '', requiredEquipment: data.requiredEquipment || '',
acceptanceCriteria: data.acceptanceCriteria || '', acceptanceCriteria: data.acceptanceCriteria || '',
notifyOnFail: data.notifyOnFail || false,
specificConditions: data.specificConditions || '', specificConditions: data.specificConditions || '',
}); });
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSave = async () => { // Auto-generate template code from name if not provided
if (!currentTenant?.id) { useEffect(() => {
setError('No se pudo obtener información del tenant'); if (!templateData.templateCode && templateData.name) {
return; const code = `TPL-${templateData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`;
setTemplateData(prev => ({ ...prev, templateCode: code }));
} }
}, [templateData.name]);
setLoading(true); // Sync with parent wizard state in real-time
setError(null); useEffect(() => {
onDataChange({ ...data, ...templateData });
try { }, [templateData]);
const scopeMapping: Record<string, string> = {
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);
}
};
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="text-center pb-4 border-b border-[var(--border-primary)]"> <div className="text-center pb-4 border-b border-[var(--border-primary)]">
<ClipboardCheck className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" /> <h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Plantilla de Calidad</h3> Quality Template Details
</h3>
<p className="text-sm text-[var(--text-secondary)]">
Fill in the required information to create a quality check template
</p>
</div> </div>
{error && ( {/* Required Fields */}
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{error} <div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Name *
</label>
<input
type="text"
value={templateData.name}
onChange={(e) => 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)]"
/>
</div> </div>
)}
<div className="space-y-6"> <div>
{/* Basic Information */} <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Check Type *
</label>
<select
value={templateData.checkType}
onChange={(e) => setTemplateData({ ...templateData, checkType: e.target.value })}
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)]"
>
<option value="product_quality">Product Quality</option>
<option value="process_hygiene">Process Hygiene</option>
<option value="equipment">Equipment</option>
<option value="safety">Safety</option>
<option value="cleaning">Cleaning</option>
<option value="temperature">Temperature Control</option>
<option value="documentation">Documentation</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Weight *
<Tooltip content="Importance weight for scoring (0.0-10.0)">
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<input
type="number"
value={templateData.weight}
onChange={(e) => 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)]"
/>
</div>
</div>
{/* Basic Information */}
<div className="border-t border-[var(--border-primary)] pt-4">
<h4 className="text-sm font-semibold text-[var(--text-primary)] mb-3">Basic Information</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Template Code
<Tooltip content="Auto-generated from name, or enter custom code">
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<input
type="text"
value={templateData.templateCode}
onChange={(e) => 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)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Version
</label>
<input
type="text"
value={templateData.version}
onChange={(e) => 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)]"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Description
</label>
<textarea
value={templateData.description}
onChange={(e) => setTemplateData({ ...templateData, description: e.target.value })}
placeholder="Detailed description of the quality check template"
rows={2}
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)]"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Applicable Stages
<Tooltip content="Comma-separated list of production stages: e.g., mixing, proofing, baking, cooling">
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<input
type="text"
value={templateData.applicableStages}
onChange={(e) => setTemplateData({ ...templateData, applicableStages: e.target.value })}
placeholder="mixing, proofing, baking, cooling"
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)]"
/>
</div>
</div>
</div>
{/* Scoring Configuration */}
<div className="border-t border-[var(--border-primary)] pt-4">
<h4 className="text-sm font-semibold text-[var(--text-primary)] mb-3">Scoring Configuration</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Scoring Method
</label>
<select
value={templateData.scoringMethod}
onChange={(e) => setTemplateData({ ...templateData, scoringMethod: e.target.value })}
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)]"
>
<option value="weighted_average">Weighted Average</option>
<option value="pass_fail">Pass/Fail</option>
<option value="percentage">Percentage</option>
<option value="points">Points-based</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Pass Threshold (%)
<Tooltip content="Minimum score required to pass (0-100)">
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<input
type="number"
value={templateData.passThreshold}
onChange={(e) => setTemplateData({ ...templateData, passThreshold: e.target.value })}
placeholder="70.0"
step="0.1"
min="0"
max="100"
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)]"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Frequency (days)
<Tooltip content="How often this check should be performed (leave empty for batch-based)">
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<input
type="number"
value={templateData.frequencyDays}
onChange={(e) => setTemplateData({ ...templateData, frequencyDays: e.target.value })}
placeholder="Leave empty for batch-based"
min="1"
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)]"
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
checked={templateData.isRequired}
onChange={(e) => setTemplateData({ ...templateData, isRequired: e.target.checked })}
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
/>
<label className="text-sm text-[var(--text-secondary)]">
Required Check
</label>
</div>
</div>
</div>
{/* Advanced Options */}
<AdvancedOptionsSection
title="Advanced Options"
description="Optional fields for comprehensive quality template configuration"
>
{/* Check Points Configuration */}
<div className="space-y-4"> <div className="space-y-4">
<h4 className="text-sm font-semibold text-[var(--text-primary)] border-b border-[var(--border-secondary)] pb-2"> <h5 className="text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
Información Básica Check Points Configuration
</h4> </h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 gap-4">
<div className="md:col-span-2"> <div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Nombre *</label> <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
<input Check Points (JSON Array)
type="text" <Tooltip content='Array of check points: [{"name": "Visual Check", "description": "...", "weight": 1.0}]'>
value={templateData.name} <Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
onChange={(e) => setTemplateData({ ...templateData, name: e.target.value })} </Tooltip>
placeholder="Ej: Control de Calidad de Pan" </label>
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)]" <textarea
value={templateData.checkPoints}
onChange={(e) => setTemplateData({ ...templateData, checkPoints: e.target.value })}
placeholder='[{"name": "Visual Inspection", "description": "Check appearance", "expected_value": "Golden brown", "measurement_type": "visual", "is_critical": false, "weight": 1.0}]'
rows={4}
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)] font-mono text-xs"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Alcance *</label>
<select
value={templateData.scope}
onChange={(e) => setTemplateData({ ...templateData, scope: e.target.value })}
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)]"
>
<option value="product">Calidad de Producto</option>
<option value="process">Higiene de Proceso</option>
<option value="equipment">Equipo</option>
<option value="safety">Seguridad</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Frecuencia *</label>
<select
value={templateData.frequency}
onChange={(e) => setTemplateData({ ...templateData, frequency: e.target.value })}
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)]"
>
<option value="batch">Cada Lote</option>
<option value="daily">Diario</option>
<option value="weekly">Semanal</option>
</select>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Hora/Condiciones Específicas Acceptance Criteria
</label> </label>
<input <textarea
type="text" value={templateData.acceptanceCriteria}
value={templateData.frequencyTime} onChange={(e) => setTemplateData({ ...templateData, acceptanceCriteria: e.target.value })}
onChange={(e) => setTemplateData({ ...templateData, frequencyTime: e.target.value })} placeholder="E.g., Golden uniform color, fluffy texture, no burns..."
placeholder="Ej: 8:00 AM, antes de hornear, temperatura ambiente" rows={2}
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)]" 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)]"
/> />
</div> </div>
</div> </div>
</div> </div>
{/* JSONB Configuration Fields */}
<div className="space-y-4 border-t border-[var(--border-primary)] pt-4">
<h5 className="text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
Advanced Configuration (JSONB)
</h5>
<div className="grid grid-cols-1 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Parameters (JSON)
<Tooltip content='Template parameters: {"temp_min": 75, "temp_max": 85, "humidity": 65}'>
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<textarea
value={templateData.parameters}
onChange={(e) => setTemplateData({ ...templateData, parameters: e.target.value })}
placeholder='{"temp_min": 75, "temp_max": 85, "humidity": 65}'
rows={2}
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)] font-mono text-xs"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Thresholds (JSON)
<Tooltip content='Threshold values: {"critical": 90, "warning": 70, "acceptable": 50}'>
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<textarea
value={templateData.thresholds}
onChange={(e) => setTemplateData({ ...templateData, thresholds: e.target.value })}
placeholder='{"critical": 90, "warning": 70, "acceptable": 50}'
rows={2}
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)] font-mono text-xs"
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Scoring Criteria (JSON)
<Tooltip content='Custom scoring criteria: {"appearance": 30, "texture": 30, "taste": 40}'>
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
</Tooltip>
</label>
<textarea
value={templateData.scoringCriteria}
onChange={(e) => setTemplateData({ ...templateData, scoringCriteria: e.target.value })}
placeholder='{"appearance": 30, "texture": 30, "taste": 40}'
rows={2}
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)] font-mono text-xs"
/>
</div>
</div>
</div>
{/* Responsibility & Requirements */} {/* Responsibility & Requirements */}
<div className="space-y-4"> <div className="space-y-4 border-t border-[var(--border-primary)] pt-4">
<h4 className="text-sm font-semibold text-[var(--text-primary)] border-b border-[var(--border-secondary)] pb-2"> <h5 className="text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
Responsabilidad y Requisitos Responsibility & Requirements
</h4> </h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Persona/Rol Responsable Responsible Role/Person
</label> </label>
<input <input
type="text" type="text"
value={templateData.responsibleRole} value={templateData.responsibleRole}
onChange={(e) => setTemplateData({ ...templateData, responsibleRole: e.target.value })} onChange={(e) => setTemplateData({ ...templateData, responsibleRole: e.target.value })}
placeholder="Ej: Jefe de Producción, Panadero" placeholder="E.g., Production Manager, Baker"
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)]" 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)]"
/> />
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Equipo/Herramientas Requeridas Required Equipment/Tools
</label> </label>
<input <input
type="text" type="text"
value={templateData.requiredEquipment} value={templateData.requiredEquipment}
onChange={(e) => setTemplateData({ ...templateData, requiredEquipment: e.target.value })} onChange={(e) => setTemplateData({ ...templateData, requiredEquipment: e.target.value })}
placeholder="Ej: Termómetro, báscula, cronómetro" placeholder="E.g., Thermometer, scale, timer"
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)]" 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)]"
/> />
</div> </div>
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> <label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Criterios de Aceptación Specific Conditions or Notes
</label>
<textarea
value={templateData.acceptanceCriteria}
onChange={(e) => setTemplateData({ ...templateData, acceptanceCriteria: e.target.value })}
placeholder="Ej: Color dorado uniforme, textura esponjosa, sin quemaduras..."
rows={3}
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)]"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
Condiciones o Notas Especiales
</label> </label>
<textarea <textarea
value={templateData.specificConditions} value={templateData.specificConditions}
onChange={(e) => setTemplateData({ ...templateData, specificConditions: e.target.value })} onChange={(e) => setTemplateData({ ...templateData, specificConditions: e.target.value })}
placeholder="Ej: Solo aplicable en días húmedos, verificar 30 min después de hornear..." placeholder="E.g., Only applicable on humid days, check 30 min after baking..."
rows={2} rows={2}
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)]" 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)]"
/> />
@@ -233,76 +408,75 @@ const TemplateInfoStep: React.FC<WizardDataProps> = ({ data, onDataChange, onCom
</div> </div>
{/* Control Settings */} {/* Control Settings */}
<div className="space-y-4"> <div className="space-y-4 border-t border-[var(--border-primary)] pt-4">
<h4 className="text-sm font-semibold text-[var(--text-primary)] border-b border-[var(--border-secondary)] pb-2"> <h5 className="text-xs font-semibold text-[var(--text-secondary)] uppercase tracking-wider">
Configuración de Control Control Settings
</h4> </h5>
<div className="space-y-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<label className="flex items-center gap-3 p-3 bg-[var(--bg-secondary)]/30 rounded-lg border border-[var(--border-secondary)] cursor-pointer hover:bg-[var(--bg-secondary)]/50 transition-colors"> <div className="flex items-center gap-2">
<input
type="checkbox"
checked={templateData.isActive}
onChange={(e) => setTemplateData({ ...templateData, isActive: e.target.checked })}
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
/>
<label className="text-sm text-[var(--text-secondary)]">
Active Template
</label>
</div>
<div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={templateData.requiresPhoto} checked={templateData.requiresPhoto}
onChange={(e) => setTemplateData({ ...templateData, requiresPhoto: e.target.checked })} onChange={(e) => setTemplateData({ ...templateData, requiresPhoto: e.target.checked })}
className="w-4 h-4 text-[var(--color-primary)] rounded focus:ring-2 focus:ring-[var(--color-primary)]" className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
/> />
<div> <label className="text-sm text-[var(--text-secondary)]">
<p className="text-sm font-medium text-[var(--text-primary)]">Requiere Fotografía</p> Requires Photo Evidence
<p className="text-xs text-[var(--text-tertiary)]">Se debe adjuntar evidencia fotográfica</p> </label>
</div> </div>
</label>
<label className="flex items-center gap-3 p-3 bg-[var(--bg-secondary)]/30 rounded-lg border border-[var(--border-secondary)] cursor-pointer hover:bg-[var(--bg-secondary)]/50 transition-colors"> <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={templateData.criticalControlPoint} checked={templateData.criticalControlPoint}
onChange={(e) => setTemplateData({ ...templateData, criticalControlPoint: e.target.checked })} onChange={(e) => setTemplateData({ ...templateData, criticalControlPoint: e.target.checked })}
className="w-4 h-4 text-[var(--color-primary)] rounded focus:ring-2 focus:ring-[var(--color-primary)]" className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
/> />
<div> <label className="text-sm text-[var(--text-secondary)]">
<p className="text-sm font-medium text-[var(--text-primary)]">Punto de Control Crítico (PCC)</p> Critical Control Point (CCP)
<p className="text-xs text-[var(--text-tertiary)]">Requiere acción inmediata si falla</p> </label>
</div> </div>
</label>
<label className="flex items-center gap-3 p-3 bg-[var(--bg-secondary)]/30 rounded-lg border border-[var(--border-secondary)] cursor-pointer hover:bg-[var(--bg-secondary)]/50 transition-colors"> <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
checked={templateData.notifyOnFail} checked={templateData.notifyOnFail}
onChange={(e) => setTemplateData({ ...templateData, notifyOnFail: e.target.checked })} onChange={(e) => setTemplateData({ ...templateData, notifyOnFail: e.target.checked })}
className="w-4 h-4 text-[var(--color-primary)] rounded focus:ring-2 focus:ring-[var(--color-primary)]" className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
/> />
<div> <label className="text-sm text-[var(--text-secondary)]">
<p className="text-sm font-medium text-[var(--text-primary)]">Notificar en Caso de Fallo</p> Notify on Failure
<p className="text-xs text-[var(--text-tertiary)]">Enviar alerta cuando el control no pase</p> </label>
</div> </div>
</label>
</div> </div>
</div> </div>
</div> </AdvancedOptionsSection>
<div className="flex justify-end pt-4 border-t border-[var(--border-primary)]">
<button
onClick={handleSave}
disabled={!templateData.name || loading}
className="px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-semibold inline-flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Guardando...
</>
) : (
<>
<CheckCircle2 className="w-5 h-5" />
Crear Plantilla
</>
)}
</button>
</div>
</div> </div>
); );
}; };
export const QualityTemplateWizardSteps = (data: Record<string, any>, setData: (data: Record<string, any>) => void): WizardStep[] => [ export const QualityTemplateWizardSteps = (
{ id: 'template-info', title: 'Información de Plantilla', description: 'Nombre, alcance, frecuencia', component: (props) => <TemplateInfoStep {...props} data={data} onDataChange={setData} /> }, data: Record<string, any>,
setData: (data: Record<string, any>) => void
): WizardStep[] => [
{
id: 'template-details',
title: 'Template Details',
component: (props) => <QualityTemplateDetailsStep {...props} data={data} onDataChange={setData} />,
validate: () => {
return !!(data.name && data.checkType && data.weight);
},
},
]; ];