import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import type { SetupStepProps } from '../SetupWizard'; import { useRecipes, useCreateRecipe, useUpdateRecipe, useDeleteRecipe } from '../../../../api/hooks/recipes'; import { useIngredients } from '../../../../api/hooks/inventory'; import { useCurrentTenant } from '../../../../stores/tenant.store'; import { useAuthUser } from '../../../../stores/auth.store'; import { MeasurementUnit } from '../../../../api/types/recipes'; import type { RecipeCreate, RecipeIngredientCreate } from '../../../../api/types/recipes'; import { getAllRecipeTemplates, matchIngredientToTemplate, type RecipeTemplate } from '../data/recipeTemplates'; import { QuickAddIngredientModal } from '../../inventory/QuickAddIngredientModal'; interface RecipeIngredientForm { ingredient_id: string; quantity: string; unit: MeasurementUnit; ingredient_order: number; } export const RecipesSetupStep: React.FC = ({ onUpdate, onComplete, canContinue }) => { const { t } = useTranslation(); // Get tenant ID const currentTenant = useCurrentTenant(); const user = useAuthUser(); const tenantId = currentTenant?.id || user?.tenant_id || ''; // Fetch recipes and ingredients const { data: recipesData, isLoading: recipesLoading } = useRecipes(tenantId); const { data: ingredientsData, isLoading: ingredientsLoading } = useIngredients(tenantId); const recipes = recipesData || []; const ingredients = ingredientsData || []; // Mutations const createRecipeMutation = useCreateRecipe(tenantId); const updateRecipeMutation = useUpdateRecipe(tenantId); const deleteRecipeMutation = useDeleteRecipe(tenantId); // Form state const [isAdding, setIsAdding] = useState(false); const [formData, setFormData] = useState({ name: '', description: '', finished_product_id: '', yield_quantity: '', yield_unit: MeasurementUnit.UNITS, category: '', }); const [recipeIngredients, setRecipeIngredients] = useState([]); const [errors, setErrors] = useState>({}); // Template state const [showTemplates, setShowTemplates] = useState(ingredients.length >= 3 && recipes.length === 0); const [selectedTemplate, setSelectedTemplate] = useState(null); const allTemplates = getAllRecipeTemplates(); // Quick add ingredient modal state const [showQuickAddModal, setShowQuickAddModal] = useState(false); const [pendingIngredientIndex, setPendingIngredientIndex] = useState(null); // Notify parent when count changes useEffect(() => { const count = recipes.length; onUpdate?.({ itemsCount: count, canContinue: count >= 1, }); }, [recipes.length, onUpdate]); // Validation const validateForm = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = t('setup_wizard:recipes.errors.name_required', 'Recipe name is required'); } if (!formData.finished_product_id) { newErrors.finished_product_id = t('setup_wizard:recipes.errors.finished_product_required', 'Finished product is required'); } if (!formData.yield_quantity || isNaN(Number(formData.yield_quantity)) || Number(formData.yield_quantity) <= 0) { newErrors.yield_quantity = t('setup_wizard:recipes.errors.yield_invalid', 'Yield must be a positive number'); } if (recipeIngredients.length === 0) { newErrors.ingredients = t('setup_wizard:recipes.errors.ingredients_required', 'At least one ingredient is required'); } // Validate each ingredient recipeIngredients.forEach((ing, index) => { if (!ing.ingredient_id) { newErrors[`ingredient_${index}_id`] = t('setup_wizard:recipes.errors.ingredient_required', 'Ingredient is required'); } if (!ing.quantity || isNaN(Number(ing.quantity)) || Number(ing.quantity) <= 0) { newErrors[`ingredient_${index}_quantity`] = t('setup_wizard:recipes.errors.quantity_invalid', 'Quantity must be positive'); } }); setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Form handlers const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) return; try { const recipeData: RecipeCreate = { name: formData.name, description: formData.description || undefined, finished_product_id: formData.finished_product_id, yield_quantity: Number(formData.yield_quantity), yield_unit: formData.yield_unit, category: formData.category || undefined, ingredients: recipeIngredients.map((ing) => ({ ingredient_id: ing.ingredient_id, quantity: Number(ing.quantity), unit: ing.unit, ingredient_order: ing.ingredient_order, is_optional: false, } as RecipeIngredientCreate)), }; await createRecipeMutation.mutateAsync(recipeData); // Reset form resetForm(); } catch (error) { console.error('Error saving recipe:', error); } }; const resetForm = () => { setFormData({ name: '', description: '', finished_product_id: '', yield_quantity: '', yield_unit: MeasurementUnit.UNITS, category: '', }); setRecipeIngredients([]); setErrors({}); setIsAdding(false); }; const handleDelete = async (recipeId: string) => { if (!window.confirm(t('setup_wizard:recipes.confirm_delete', 'Are you sure you want to delete this recipe?'))) { return; } try { await deleteRecipeMutation.mutateAsync(recipeId); } catch (error) { console.error('Error deleting recipe:', error); } }; const addIngredient = () => { setRecipeIngredients([ ...recipeIngredients, { ingredient_id: '', quantity: '', unit: MeasurementUnit.GRAMS, ingredient_order: recipeIngredients.length + 1, }, ]); }; const removeIngredient = (index: number) => { setRecipeIngredients(recipeIngredients.filter((_, i) => i !== index)); }; const updateIngredient = (index: number, field: keyof RecipeIngredientForm, value: any) => { const updated = [...recipeIngredients]; updated[index] = { ...updated[index], [field]: value }; setRecipeIngredients(updated); }; // Template handlers const handleUseTemplate = (template: RecipeTemplate) => { // Find first ingredient that matches the finished product (usually the main ingredient) const finishedProductIngredient = ingredients.find( ing => ing.name.toLowerCase().includes(template.name.toLowerCase().split(' ')[0]) ); // Map template ingredients to actual ingredient IDs const mappedIngredients: RecipeIngredientForm[] = []; let order = 1; for (const templateIng of template.ingredients) { const ingredientId = matchIngredientToTemplate(templateIng, ingredients); if (ingredientId) { mappedIngredients.push({ ingredient_id: ingredientId, quantity: templateIng.quantity.toString(), unit: templateIng.unit, ingredient_order: order++, }); } } setFormData({ name: template.name, description: template.description, finished_product_id: finishedProductIngredient?.id || '', yield_quantity: template.yieldQuantity.toString(), yield_unit: template.yieldUnit, category: template.category, }); setRecipeIngredients(mappedIngredients); setSelectedTemplate(template); setIsAdding(true); setShowTemplates(false); }; const handlePreviewTemplate = (template: RecipeTemplate) => { setSelectedTemplate(template); }; // Quick add ingredient handlers const handleQuickAddIngredient = (index: number) => { setPendingIngredientIndex(index); setShowQuickAddModal(true); }; const handleIngredientCreated = async (ingredient: any) => { // Ingredient is already created in the database by the modal // Now we need to select it for the recipe if (pendingIngredientIndex === -1) { // This was for the finished product setFormData({ ...formData, finished_product_id: ingredient.id }); } else if (pendingIngredientIndex !== null) { // Update the ingredient at the pending index updateIngredient(pendingIngredientIndex, 'ingredient_id', ingredient.id); } // Clear pending state setPendingIngredientIndex(null); setShowQuickAddModal(false); }; const unitOptions = [ { value: MeasurementUnit.GRAMS, label: t('recipes:unit.g', 'Grams (g)') }, { value: MeasurementUnit.KILOGRAMS, label: t('recipes:unit.kg', 'Kilograms (kg)') }, { value: MeasurementUnit.MILLILITERS, label: t('recipes:unit.ml', 'Milliliters (ml)') }, { value: MeasurementUnit.LITERS, label: t('recipes:unit.l', 'Liters (l)') }, { value: MeasurementUnit.UNITS, label: t('recipes:unit.units', 'Units') }, { value: MeasurementUnit.PIECES, label: t('recipes:unit.pieces', 'Pieces') }, { value: MeasurementUnit.CUPS, label: t('recipes:unit.cups', 'Cups') }, { value: MeasurementUnit.TABLESPOONS, label: t('recipes:unit.tbsp', 'Tablespoons') }, { value: MeasurementUnit.TEASPOONS, label: t('recipes:unit.tsp', 'Teaspoons') }, ]; return (
{/* Why This Matters */}

{t('setup_wizard:why_this_matters', 'Why This Matters')}

{t('setup_wizard:recipes.why', 'Recipes connect your inventory to production. The system will calculate exact costs per item, track ingredient consumption, and help you optimize your menu profitability.')}

{/* Recipe Templates */} {showTemplates && ingredients.length >= 3 && (

{t('setup_wizard:recipes.quick_start', 'Recipe Templates')}

{t('setup_wizard:recipes.quick_start_desc', 'Start with proven recipes and customize to your needs')}

{Object.entries(allTemplates).map(([categoryKey, templates]) => (

{t(`setup_wizard:recipes.category.${categoryKey}`, categoryKey)}

{templates.map((template) => (
{template.name}

{template.description}

{[...Array(template.difficulty)].map((_, i) => ( ))}
{template.totalTime} min {template.ingredients.length} ingredients Yield: {template.yieldQuantity} {template.yieldUnit}
{selectedTemplate?.id === template.id && (

Ingredients:

    {template.ingredients.map((ing, idx) => (
  • {ing.quantity} {ing.unit} {ing.ingredientName}
  • ))}
{template.instructions && (

Instructions:

{template.instructions}

)} {template.tips && template.tips.length > 0 && (

Tips:

    {template.tips.map((tip, idx) => (
  • {tip}
  • ))}
)}
)}
))}
))}
{t('setup_wizard:recipes.templates_hint', 'Templates will automatically match your ingredients. Review and adjust as needed.')}
)} {/* Show templates button when hidden */} {!showTemplates && recipes.length > 0 && ingredients.length >= 3 && ( )} {/* Prerequisites check */} {ingredients.length < 2 && !ingredientsLoading && (

{t('setup_wizard:recipes.prerequisites_title', 'More ingredients needed')}

{t('setup_wizard:recipes.prerequisites_desc', 'You need at least 2 ingredients in your inventory before creating recipes. Go back to the Inventory step to add more ingredients.')}

)} {/* Progress indicator */}
{t('setup_wizard:recipes.added_count', { count: recipes.length, defaultValue: '{{count}} recipe added' })}
{recipes.length >= 1 && (
{t('setup_wizard:recipes.minimum_met', 'Minimum requirement met')}
)}
{/* Recipes list */} {recipes.length > 0 && (

{t('setup_wizard:recipes.your_recipes', 'Your Recipes')}

{recipes.map((recipe) => (
{recipe.name}
{recipe.category && ( {recipe.category} )}
{t('setup_wizard:recipes.yield_label', 'Yield')}: {recipe.yield_quantity} {recipe.yield_unit} {recipe.estimated_cost_per_unit && ( ${Number(recipe.estimated_cost_per_unit).toFixed(2)}/unit )}
))}
)} {/* Add form */} {isAdding ? (

{t('setup_wizard:recipes.add_recipe', 'Add Recipe')}

{/* Recipe Name */}
setFormData({ ...formData, name: e.target.value })} className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.name ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} placeholder={t('setup_wizard:recipes.placeholders.name', 'e.g., Baguette, Croissant')} /> {errors.name &&

{errors.name}

}
{/* Finished Product */}
{errors.finished_product_id &&

{errors.finished_product_id}

}
{/* Yield */}
setFormData({ ...formData, yield_quantity: e.target.value })} className={`w-full px-3 py-2 bg-[var(--bg-primary)] border ${errors.yield_quantity ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} placeholder="10" /> {errors.yield_quantity &&

{errors.yield_quantity}

}
{/* Ingredients */}
{errors.ingredients &&

{errors.ingredients}

}
{recipeIngredients.map((ing, index) => (
{errors[`ingredient_${index}_id`] &&

{errors[`ingredient_${index}_id`]}

}
updateIngredient(index, 'quantity', e.target.value)} placeholder="Qty" className={`w-full px-2 py-1.5 text-sm bg-[var(--bg-secondary)] border ${errors[`ingredient_${index}_quantity`] ? 'border-[var(--color-error)]' : 'border-[var(--border-secondary)]'} rounded focus:outline-none focus:ring-1 focus:ring-[var(--color-primary)] text-[var(--text-primary)]`} /> {errors[`ingredient_${index}_quantity`] &&

{errors[`ingredient_${index}_quantity`]}

}
))} {recipeIngredients.length === 0 && (
{t('setup_wizard:recipes.no_ingredients', 'No ingredients added yet')}
)}
) : ( )} {/* Loading state */} {(recipesLoading || ingredientsLoading) && recipes.length === 0 && (

{t('common:loading', 'Loading...')}

)} {/* Navigation - Show Next button when minimum requirement met */} {recipes.length >= 1 && !isAdding && (
{t('setup_wizard:recipes.minimum_met', '{{count}} recipe(s) added - Ready to continue!', { count: recipes.length })}
)} {/* Quick Add Ingredient Modal */} { setShowQuickAddModal(false); setPendingIngredientIndex(null); }} onCreated={handleIngredientCreated} tenantId={tenantId} context="recipe" /> {/* Continue button - only shown when used in onboarding context */} {onComplete && (
)}
); };