From 877e0b6b477241fab4eff072be53ab53f4c9d357 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Nov 2025 18:01:11 +0000 Subject: [PATCH] Implement Phase 2: Recipe & Supplier wizard modals (JTBD-driven UX) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../RecipeWizard/RecipeIngredientsStep.tsx | 212 +++++++++++++ .../RecipeWizard/RecipeProductStep.tsx | 187 +++++++++++ .../RecipeWizard/RecipeProductionStep.tsx | 299 ++++++++++++++++++ .../recipes/RecipeWizard/RecipeReviewStep.tsx | 234 ++++++++++++++ .../RecipeWizard/RecipeWizardModal.tsx | 297 +++++++++++++++++ .../domain/recipes/RecipeWizard/index.ts | 5 + .../SupplierWizard/SupplierBasicStep.tsx | 191 +++++++++++ .../SupplierWizard/SupplierDeliveryStep.tsx | 198 ++++++++++++ .../SupplierWizard/SupplierReviewStep.tsx | 225 +++++++++++++ .../SupplierWizard/SupplierWizardModal.tsx | 155 +++++++++ .../domain/suppliers/SupplierWizard/index.ts | 4 + .../components/ui/WizardModal/WizardModal.tsx | 271 ++++++++++++++++ .../src/components/ui/WizardModal/index.ts | 2 + 13 files changed, 2280 insertions(+) create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/RecipeIngredientsStep.tsx create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/RecipeProductStep.tsx create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/RecipeProductionStep.tsx create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/RecipeReviewStep.tsx create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/RecipeWizardModal.tsx create mode 100644 frontend/src/components/domain/recipes/RecipeWizard/index.ts create mode 100644 frontend/src/components/domain/suppliers/SupplierWizard/SupplierBasicStep.tsx create mode 100644 frontend/src/components/domain/suppliers/SupplierWizard/SupplierDeliveryStep.tsx create mode 100644 frontend/src/components/domain/suppliers/SupplierWizard/SupplierReviewStep.tsx create mode 100644 frontend/src/components/domain/suppliers/SupplierWizard/SupplierWizardModal.tsx create mode 100644 frontend/src/components/domain/suppliers/SupplierWizard/index.ts create mode 100644 frontend/src/components/ui/WizardModal/WizardModal.tsx create mode 100644 frontend/src/components/ui/WizardModal/index.ts diff --git a/frontend/src/components/domain/recipes/RecipeWizard/RecipeIngredientsStep.tsx b/frontend/src/components/domain/recipes/RecipeWizard/RecipeIngredientsStep.tsx new file mode 100644 index 00000000..3ad1d37d --- /dev/null +++ b/frontend/src/components/domain/recipes/RecipeWizard/RecipeIngredientsStep.tsx @@ -0,0 +1,212 @@ +import React from 'react'; +import { Package, Plus, Trash2 } from 'lucide-react'; +import type { WizardStepProps } from '../../../ui/WizardModal/WizardModal'; +import type { RecipeCreate, RecipeIngredientCreate, MeasurementUnit } from '../../../../api/types/recipes'; + +interface RecipeIngredientsStepProps extends WizardStepProps { + recipeData: Partial; + onUpdate: (data: Partial) => void; + availableIngredients: Array<{ value: string; label: string }>; + unitOptions: Array<{ value: MeasurementUnit; label: string }>; +} + +export const RecipeIngredientsStep: React.FC = ({ + recipeData, + onUpdate, + onNext, + onBack, + availableIngredients, + unitOptions +}) => { + const ingredients = recipeData.ingredients || []; + + const addIngredient = () => { + const newIngredient: RecipeIngredientCreate = { + ingredient_id: '', + quantity: 1, + unit: 'grams' as MeasurementUnit, + ingredient_order: ingredients.length + 1, + is_optional: false + }; + onUpdate({ ...recipeData, ingredients: [...ingredients, newIngredient] }); + }; + + const removeIngredient = (index: number) => { + if (ingredients.length > 1) { + const updated = ingredients.filter((_, i) => i !== index); + onUpdate({ ...recipeData, ingredients: updated }); + } + }; + + const updateIngredient = (index: number, field: keyof RecipeIngredientCreate, value: any) => { + const updated = ingredients.map((ing, i) => + i === index ? { ...ing, [field]: value } : ing + ); + onUpdate({ ...recipeData, ingredients: updated }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onNext(); + }; + + // Validation: at least one ingredient with valid data + const isValid = + ingredients.length > 0 && + ingredients.some((ing) => ing.ingredient_id && ing.ingredient_id.trim() !== '' && ing.quantity > 0); + + return ( +
+ {/* Header */} +
+
+ +
+
+

+ Ingredientes +

+

+ Agrega los ingredientes necesarios para esta receta +

+
+ +
+ + {/* Ingredients List */} +
+ {ingredients.length === 0 ? ( +
+ +

+ No hay ingredientes agregados +

+ +
+ ) : ( + ingredients.map((ingredient, index) => ( +
+ {/* Header */} +
+ + Ingrediente #{index + 1} + + +
+ + {/* Fields */} +
+ {/* Ingredient Selection */} +
+ + +
+ +
+ {/* Quantity */} +
+ + updateIngredient(index, 'quantity', parseFloat(e.target.value) || 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)] text-sm" + min="0" + step="0.1" + required + /> +
+ + {/* Unit */} +
+ + +
+ + {/* Optional Checkbox */} +
+ +
+
+
+
+ )) + )} +
+ + {/* Validation Message */} + {!isValid && ingredients.length > 0 && ( +
+

+ ⚠️ Atención: Asegúrate de seleccionar al menos un ingrediente con cantidad válida. +

+
+ )} + + {/* Hidden submit button for form handling */} +