Files
bakery-ia/frontend/src/components/domain/suppliers/SupplierWizard/SupplierBasicStep.tsx
Claude 877e0b6b47 Implement Phase 2: Recipe & Supplier wizard modals (JTBD-driven UX)
Following Jobs-To-Be-Done analysis, break down complex forms into multi-step wizards to reduce cognitive load for non-technical bakery owners.

**Core Infrastructure:**
- Add reusable WizardModal component with progress tracking, validation, and navigation
  - Multi-step progress bar with clickable previous steps
  - Per-step validation with clear error messaging
  - Back/Next/Complete navigation with loading states
  - Optional step skipping support
  - Responsive modal design (sm/md/lg/xl/2xl sizes)

**Recipe Wizard (4 steps):**
- Step 1 (Product): Name, category, finished product, cuisine type, difficulty, description
- Step 2 (Ingredients): Dynamic ingredient list with add/remove, quantities, units, optional flags
- Step 3 (Production): Times (prep/cook/rest), yield, batch sizes, temperature, humidity, special flags
- Step 4 (Review): Instructions, storage, nutritional info, allergens, final summary

**Supplier Wizard (3 steps):**
- Step 1 (Basic): Name, type, status, contact person, email, phone, tax ID, registration
- Step 2 (Delivery): Payment terms, lead time, minimum order, delivery schedule, address
- Step 3 (Review): Certifications, sustainability practices, notes, summary

**Benefits:**
- Reduces form overwhelm from 8 sections to 4 sequential steps (recipes) and 3 steps (suppliers)
- Clear progress indication and next actions
- Validation feedback per step instead of at end
- Summary review before final submission
- Matches mental model of "configure then review" workflow

Files:
- WizardModal: Reusable wizard infrastructure
- RecipeWizard: 4-step recipe creation (Product → Ingredients → Production → Review)
- SupplierWizard: 3-step supplier creation (Basic → Delivery → Review)

Related to Phase 1 (ConfigurationProgressWidget) for post-onboarding guidance.
2025-11-06 18:01:11 +00:00

192 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import { Users } from 'lucide-react';
import type { WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import type { SupplierCreate, SupplierType, SupplierStatus, PaymentTerms } from '../../../../api/types/suppliers';
interface SupplierBasicStepProps extends WizardStepProps {
supplierData: Partial<SupplierCreate>;
onUpdate: (data: Partial<SupplierCreate>) => void;
}
export const SupplierBasicStep: React.FC<SupplierBasicStepProps> = ({
supplierData,
onUpdate,
onNext
}) => {
const handleFieldChange = (field: keyof SupplierCreate, value: any) => {
onUpdate({ ...supplierData, [field]: value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onNext();
};
const isValid = supplierData.name && supplierData.name.trim().length >= 1 && supplierData.supplier_type;
return (
<form onSubmit={handleSubmit} className="space-y-6">
{/* Header */}
<div className="flex items-start gap-4 pb-4 border-b border-[var(--border-secondary)]">
<div className="w-12 h-12 rounded-full bg-[var(--color-primary)]/10 flex items-center justify-center flex-shrink-0">
<Users className="w-6 h-6 text-[var(--color-primary)]" />
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-1">
Información Básica
</h3>
<p className="text-sm text-[var(--text-secondary)]">
Configura los datos esenciales del proveedor
</p>
</div>
</div>
{/* Form Fields */}
<div className="space-y-5">
{/* Supplier Name */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Nombre del Proveedor <span className="text-red-500">*</span>
</label>
<input
type="text"
value={supplierData.name || ''}
onChange={(e) => handleFieldChange('name', e.target.value)}
placeholder="Ej: Molinos La Victoria"
className="w-full px-4 py-2.5 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)]"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
{/* Supplier Type */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Tipo de Proveedor <span className="text-red-500">*</span>
</label>
<select
value={supplierData.supplier_type || ''}
onChange={(e) => handleFieldChange('supplier_type', e.target.value as SupplierType)}
className="w-full px-4 py-2.5 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)]"
required
>
<option value="">Seleccionar...</option>
<option value="ingredients">Ingredientes</option>
<option value="packaging">Empaque</option>
<option value="equipment">Equipo</option>
<option value="utilities">Servicios Públicos</option>
<option value="services">Servicios</option>
<option value="logistics">Logística</option>
<option value="other">Otro</option>
</select>
</div>
{/* Status */}
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Estado <span className="text-red-500">*</span>
</label>
<select
value={supplierData.status || 'pending_approval'}
onChange={(e) => handleFieldChange('status', e.target.value as SupplierStatus)}
className="w-full px-4 py-2.5 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="pending_approval">Pendiente de Aprobación</option>
<option value="active">Activo</option>
<option value="inactive">Inactivo</option>
<option value="suspended">Suspendido</option>
<option value="terminated">Terminado</option>
<option value="evaluation">En Evaluación</option>
</select>
</div>
</div>
{/* Contact Information */}
<div className="grid grid-cols-1 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Persona de Contacto
</label>
<input
type="text"
value={supplierData.contact_person || ''}
onChange={(e) => handleFieldChange('contact_person', e.target.value)}
placeholder="Nombre del representante"
className="w-full px-4 py-2.5 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="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Email
</label>
<input
type="email"
value={supplierData.email || ''}
onChange={(e) => handleFieldChange('email', e.target.value)}
placeholder="correo@ejemplo.com"
className="w-full px-4 py-2.5 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-primary)] mb-1.5">
Teléfono
</label>
<input
type="tel"
value={supplierData.phone || ''}
onChange={(e) => handleFieldChange('phone', e.target.value)}
placeholder="+34 600 000 000"
className="w-full px-4 py-2.5 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>
{/* Tax & Registration */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
NIF/CIF (Opcional)
</label>
<input
type="text"
value={supplierData.tax_id || ''}
onChange={(e) => handleFieldChange('tax_id', e.target.value)}
placeholder="Ej: B12345678"
className="w-full px-4 py-2.5 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-primary)] mb-1.5">
Número de Registro (Opcional)
</label>
<input
type="text"
value={supplierData.registration_number || ''}
onChange={(e) => handleFieldChange('registration_number', e.target.value)}
placeholder="Número de registro oficial"
className="w-full px-4 py-2.5 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>
{/* Validation Message */}
{!isValid && (
<div className="p-3 bg-[var(--color-warning)]/10 border border-[var(--color-warning)]/30 rounded-lg">
<p className="text-sm text-[var(--text-primary)]">
<span className="font-medium"> Campos requeridos:</span> Asegúrate de completar el nombre y tipo de proveedor.
</p>
</div>
)}
{/* Hidden submit button for form handling */}
<button type="submit" className="hidden" disabled={!isValid} />
</form>
);
};