From 3b66bb869a13bf9020d0775bc73ec24dba3d8de2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Nov 2025 07:28:20 +0000 Subject: [PATCH] feat: Completely rewrite RecipeWizard with comprehensive improvements Major improvements: 1. Fixed 'a.map is not a function' error (line 387: result.templates) 2. Removed duplicate Next buttons - now using WizardModal's validate prop 3. Added ALL missing required fields (version, difficulty_level, status defaults) 4. Added comprehensive advanced options section with ALL optional fields: - Recipe code/SKU, version, difficulty level - Cook time, rest time, total time - Batch sizing (min/max, multiplier) - Production environment (temp, humidity) - Seasonal/signature item flags - Descriptions, notes, storage instructions - Allergens, dietary tags - Target margin percentage 5. Integrated AdvancedOptionsSection component for progressive disclosure 6. Added tooltips for complex fields using existing Tooltip component 7. Proper form validation on each step 8. Real-time data synchronization with useEffect 9. English labels (per project standards) 10. All fields map correctly to backend RecipeCreate schema Technical changes: - Created reusable AdvancedOptionsSection component - Steps now validate using WizardModal's validate prop - No internal "Continuar" buttons - cleaner UX - Quality Templates step marked as optional (isOptional: true) - Ingredients step validates all required data - Seasonal month selectors conditional on isSeasonal checkbox This implementation follows UX best practices for progressive disclosure and reduces cognitive load while maintaining access to all backend fields. --- .../unified-wizard/wizards/RecipeWizard.tsx | 726 ++++++++++++++---- .../AdvancedOptionsSection.tsx | 67 ++ .../ui/AdvancedOptionsSection/index.ts | 1 + 3 files changed, 642 insertions(+), 152 deletions(-) create mode 100644 frontend/src/components/ui/AdvancedOptionsSection/AdvancedOptionsSection.tsx create mode 100644 frontend/src/components/ui/AdvancedOptionsSection/index.ts diff --git a/frontend/src/components/domain/unified-wizard/wizards/RecipeWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/RecipeWizard.tsx index 15cac73f..ecb0c605 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/RecipeWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/RecipeWizard.tsx @@ -6,25 +6,54 @@ import { recipesService } from '../../../../api/services/recipes'; import { inventoryService } from '../../../../api/services/inventory'; import { qualityTemplateService } from '../../../../api/services/qualityTemplates'; import { IngredientResponse } from '../../../../api/types/inventory'; -import { RecipeCreate, RecipeIngredientCreate, MeasurementUnit, RecipeQualityConfiguration } from '../../../../api/types/recipes'; +import { RecipeCreate, RecipeIngredientCreate, MeasurementUnit, RecipeQualityConfiguration, RecipeStatus } from '../../../../api/types/recipes'; import { QualityCheckTemplateResponse } from '../../../../api/types/qualityTemplates'; import { showToast } from '../../../../utils/toast'; +import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection'; +import Tooltip from '../../../ui/Tooltip/Tooltip'; interface WizardDataProps extends WizardStepProps { data: Record; onDataChange: (data: Record) => void; } -const RecipeDetailsStep: React.FC = ({ data, onDataChange, onNext }) => { +const RecipeDetailsStep: React.FC = ({ data, onDataChange }) => { const { currentTenant } = useTenant(); const [recipeData, setRecipeData] = useState({ + // Required fields name: data.name || '', - category: data.category || 'bread', + finishedProductId: data.finishedProductId || '', yieldQuantity: data.yieldQuantity || '', yieldUnit: data.yieldUnit || 'units', + + // Optional basic fields + category: data.category || 'bread', prepTime: data.prepTime || '', - finishedProductId: data.finishedProductId || '', instructions: data.instructions || '', + + // Advanced optional fields + recipeCode: data.recipeCode || '', + version: data.version || '1.0', + difficultyLevel: data.difficultyLevel || 3, + cookTime: data.cookTime || '', + restTime: data.restTime || '', + totalTime: data.totalTime || '', + description: data.description || '', + preparationNotes: data.preparationNotes || '', + storageInstructions: data.storageInstructions || '', + servesCount: data.servesCount || '', + batchSizeMultiplier: data.batchSizeMultiplier || 1.0, + minBatchSize: data.minBatchSize || '', + maxBatchSize: data.maxBatchSize || '', + optimalProductionTemp: data.optimalProductionTemp || '', + optimalHumidity: data.optimalHumidity || '', + isSeasonal: data.isSeasonal || false, + isSignatureItem: data.isSignatureItem || false, + seasonStartMonth: data.seasonStartMonth || '', + seasonEndMonth: data.seasonEndMonth || '', + allergens: data.allergens || '', + dietaryTags: data.dietaryTags || '', + targetMargin: data.targetMargin || '', }); const [finishedProducts, setFinishedProducts] = useState([]); const [loading, setLoading] = useState(false); @@ -33,6 +62,10 @@ const RecipeDetailsStep: React.FC = ({ data, onDataChange, onNe fetchFinishedProducts(); }, []); + useEffect(() => { + onDataChange({ ...data, ...recipeData }); + }, [recipeData]); + const fetchFinishedProducts = async () => { if (!currentTenant?.id) return; setLoading(true); @@ -52,75 +85,106 @@ const RecipeDetailsStep: React.FC = ({ data, onDataChange, onNe
-

Detalles de la Receta

+

Recipe Details

+

Essential information about your recipe

-
-
- + + {/* Required Fields */} +
+
+ setRecipeData({ ...recipeData, name: e.target.value })} - placeholder="Ej: Baguette Tradicional" + placeholder="e.g., Traditional Baguette" 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)]" />
-
- - + +
+
+ + +
+ +
+ + +
-
- - + +
+
+ + setRecipeData({ ...recipeData, yieldQuantity: e.target.value })} + placeholder="12" + 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)]" + min="0.01" + step="0.01" + /> +
+ +
+ + +
+
- - setRecipeData({ ...recipeData, yieldQuantity: e.target.value })} - placeholder="12" - 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)]" - min="1" - /> -
-
- - -
-
- + = ({ data, onDataChange, onNe min="0" />
-
- + +
+