import React, { useState, useCallback, useMemo } from 'react'; import { Card, Button, Badge, Input, Modal, Table, Select } from '../../ui'; import type { Recipe, RecipeIngredient, RecipeInstruction, NutritionalInfo, DifficultyLevel } from '../../../types/production.types'; interface RecipeDisplayProps { className?: string; recipe: Recipe; editable?: boolean; showNutrition?: boolean; showCosting?: boolean; onScaleChange?: (scaleFactor: number, scaledRecipe: Recipe) => void; onVersionUpdate?: (newVersion: Recipe) => void; onCostCalculation?: (totalCost: number, costPerUnit: number) => void; } interface ScaledIngredient extends RecipeIngredient { scaledQuantity: number; scaledCost?: number; } interface ScaledInstruction extends RecipeInstruction { scaledDuration?: number; } interface ScaledRecipe extends Omit { scaledYieldQuantity: number; scaledTotalTime: number; ingredients: ScaledIngredient[]; instructions: ScaledInstruction[]; scaleFactor: number; estimatedTotalCost?: number; costPerScaledUnit?: number; } interface TimerState { instructionId: string; duration: number; remaining: number; isActive: boolean; isComplete: boolean; } const DIFFICULTY_COLORS = { [DifficultyLevel.BEGINNER]: 'bg-green-100 text-green-800', [DifficultyLevel.INTERMEDIATE]: 'bg-yellow-100 text-yellow-800', [DifficultyLevel.ADVANCED]: 'bg-orange-100 text-orange-800', [DifficultyLevel.EXPERT]: 'bg-red-100 text-red-800', }; const DIFFICULTY_LABELS = { [DifficultyLevel.BEGINNER]: 'Principiante', [DifficultyLevel.INTERMEDIATE]: 'Intermedio', [DifficultyLevel.ADVANCED]: 'Avanzado', [DifficultyLevel.EXPERT]: 'Experto', }; const ALLERGEN_ICONS: Record = { gluten: '🌾', milk: '🥛', eggs: '🥚', nuts: '🥜', soy: '🫘', sesame: '🌰', fish: '🐟', shellfish: '🦐', }; const EQUIPMENT_ICONS: Record = { oven: '🔥', mixer: '🥄', scale: '⚖️', bowl: '🥣', whisk: '🔄', spatula: '🍴', thermometer: '🌡️', timer: '⏰', }; export const RecipeDisplay: React.FC = ({ className = '', recipe, editable = false, showNutrition = true, showCosting = false, onScaleChange, onVersionUpdate, onCostCalculation, }) => { const [scaleFactor, setScaleFactor] = useState(1); const [activeTimers, setActiveTimers] = useState>({}); const [isNutritionModalOpen, setIsNutritionModalOpen] = useState(false); const [isCostingModalOpen, setIsCostingModalOpen] = useState(false); const [isEquipmentModalOpen, setIsEquipmentModalOpen] = useState(false); const [selectedInstruction, setSelectedInstruction] = useState(null); const [showAllergensDetail, setShowAllergensDetail] = useState(false); // Mock ingredient costs for demonstration const ingredientCosts: Record = { flour: 1.2, // €/kg water: 0.001, // €/l salt: 0.8, // €/kg yeast: 8.5, // €/kg sugar: 1.5, // €/kg butter: 6.2, // €/kg milk: 1.3, // €/l eggs: 0.25, // €/unit }; const scaledRecipe = useMemo((): ScaledRecipe => { const scaledIngredients: ScaledIngredient[] = recipe.ingredients.map(ingredient => ({ ...ingredient, scaledQuantity: ingredient.quantity * scaleFactor, scaledCost: ingredientCosts[ingredient.ingredient_id] ? ingredientCosts[ingredient.ingredient_id] * ingredient.quantity * scaleFactor : undefined, })); const scaledInstructions: ScaledInstruction[] = recipe.instructions.map(instruction => ({ ...instruction, scaledDuration: instruction.duration_minutes ? Math.ceil(instruction.duration_minutes * scaleFactor) : undefined, })); const estimatedTotalCost = scaledIngredients.reduce((total, ingredient) => total + (ingredient.scaledCost || 0), 0 ); const scaledYieldQuantity = recipe.yield_quantity * scaleFactor; const costPerScaledUnit = scaledYieldQuantity > 0 ? estimatedTotalCost / scaledYieldQuantity : 0; return { ...recipe, scaledYieldQuantity, scaledTotalTime: Math.ceil(recipe.total_time_minutes * scaleFactor), ingredients: scaledIngredients, instructions: scaledInstructions, scaleFactor, estimatedTotalCost, costPerScaledUnit, }; }, [recipe, scaleFactor, ingredientCosts]); const handleScaleChange = useCallback((newScaleFactor: number) => { setScaleFactor(newScaleFactor); if (onScaleChange) { onScaleChange(newScaleFactor, scaledRecipe as unknown as Recipe); } }, [scaledRecipe, onScaleChange]); const startTimer = (instruction: RecipeInstruction) => { if (!instruction.duration_minutes) return; const timerState: TimerState = { instructionId: instruction.step_number.toString(), duration: instruction.duration_minutes * 60, // Convert to seconds remaining: instruction.duration_minutes * 60, isActive: true, isComplete: false, }; setActiveTimers(prev => ({ ...prev, [instruction.step_number.toString()]: timerState, })); // Start countdown const interval = setInterval(() => { setActiveTimers(prev => { const current = prev[instruction.step_number.toString()]; if (!current || current.remaining <= 0) { clearInterval(interval); return { ...prev, [instruction.step_number.toString()]: { ...current, remaining: 0, isActive: false, isComplete: true, } }; } return { ...prev, [instruction.step_number.toString()]: { ...current, remaining: current.remaining - 1, } }; }); }, 1000); }; const stopTimer = (instructionId: string) => { setActiveTimers(prev => ({ ...prev, [instructionId]: { ...prev[instructionId], isActive: false, } })); }; const formatTime = (seconds: number): string => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; if (hours > 0) { return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; } return `${minutes}:${String(secs).padStart(2, '0')}`; }; const renderScalingControls = () => (

Escalado de receta

handleScaleChange(parseFloat(e.target.value) || 1)} className="w-full" />

{recipe.yield_quantity} {recipe.yield_unit}

{scaledRecipe.scaledYieldQuantity} {recipe.yield_unit}

{scaleFactor !== 1 && (

Tiempo total escalado: {Math.ceil(scaledRecipe.scaledTotalTime / 60)}h {scaledRecipe.scaledTotalTime % 60}m

{showCosting && scaledRecipe.estimatedTotalCost && (

Costo estimado: €{scaledRecipe.estimatedTotalCost.toFixed(2)} (€{scaledRecipe.costPerScaledUnit?.toFixed(3)}/{recipe.yield_unit})

)}
)}
); const renderRecipeHeader = () => (

{recipe.name}

{recipe.description}

{DIFFICULTY_LABELS[recipe.difficulty_level]} v{recipe.version}

Preparación

{recipe.prep_time_minutes} min

Cocción

{recipe.cook_time_minutes} min

Total

{Math.ceil(recipe.total_time_minutes / 60)}h {recipe.total_time_minutes % 60}m

Rendimiento

{scaledRecipe.scaledYieldQuantity} {recipe.yield_unit}

{recipe.allergen_warnings.length > 0 && (

Alérgenos:

{recipe.allergen_warnings.map((allergen) => ( {ALLERGEN_ICONS[allergen]} {allergen} ))}
{showAllergensDetail && (

⚠️ Este producto contiene los siguientes alérgenos. Revisar cuidadosamente antes del consumo si existe alguna alergia o intolerancia alimentaria.

)}
)} {recipe.storage_instructions && (

Conservación: {recipe.storage_instructions} {recipe.shelf_life_hours && ( • Vida útil: {Math.ceil(recipe.shelf_life_hours / 24)} días )}

)}
{showNutrition && ( )} {showCosting && ( )} {editable && onVersionUpdate && ( )}
); const renderIngredients = () => (

Ingredientes

{scaledRecipe.ingredients.map((ingredient, index) => (

{ingredient.ingredient_name}

{ingredient.preparation_notes && (

{ingredient.preparation_notes}

)} {ingredient.is_optional && ( Opcional )}

{ingredient.scaledQuantity.toFixed(ingredient.scaledQuantity < 1 ? 2 : 0)} {ingredient.unit}

{scaleFactor !== 1 && (

(original: {ingredient.quantity} {ingredient.unit})

)} {showCosting && ingredient.scaledCost && (

€{ingredient.scaledCost.toFixed(2)}

)}
))}
{showCosting && scaledRecipe.estimatedTotalCost && (
Costo total estimado: €{scaledRecipe.estimatedTotalCost.toFixed(2)}

€{scaledRecipe.costPerScaledUnit?.toFixed(3)} por {recipe.yield_unit}

)}
); const renderInstructions = () => (

Instrucciones

{scaledRecipe.instructions.map((instruction, index) => { const timer = activeTimers[instruction.step_number.toString()]; return (
{instruction.step_number}
{instruction.duration_minutes && ( ⏱️ {instruction.scaledDuration || instruction.duration_minutes} min )} {instruction.temperature && ( 🌡️ {instruction.temperature}°C )} {instruction.critical_control_point && ( 🚨 PCC )}
{instruction.duration_minutes && (
{!timer?.isActive && !timer?.isComplete && ( )} {timer?.isActive && (
{formatTime(timer.remaining)}
)} {timer?.isComplete && ( ✅ Completado )}
)}

{instruction.instruction}

{instruction.equipment && instruction.equipment.length > 0 && (

Equipo necesario:

{instruction.equipment.map((equipment, idx) => ( {EQUIPMENT_ICONS[equipment.toLowerCase()] || '🔧'} {equipment} ))}
)} {instruction.tips && (

💡 Tip: {instruction.tips}

)}
); })}
); const renderNutritionModal = () => ( setIsNutritionModalOpen(false)} title="Información Nutricional" > {recipe.nutritional_info ? (

Calorías por porción

{recipe.nutritional_info.calories_per_serving || 'N/A'}

Tamaño de porción

{recipe.nutritional_info.serving_size || 'N/A'}

Proteínas

{recipe.nutritional_info.protein_g || 0}g

Carbohidratos

{recipe.nutritional_info.carbohydrates_g || 0}g

Grasas

{recipe.nutritional_info.fat_g || 0}g

Fibra

{recipe.nutritional_info.fiber_g || 0}g

Azúcares

{recipe.nutritional_info.sugar_g || 0}g

Sodio

{recipe.nutritional_info.sodium_mg || 0}mg

Porciones por lote escalado: { recipe.nutritional_info.servings_per_batch ? Math.ceil(recipe.nutritional_info.servings_per_batch * scaleFactor) : 'N/A' }

) : (

No hay información nutricional disponible para esta receta.

)}
); const renderCostingModal = () => ( setIsCostingModalOpen(false)} title="Análisis de Costos" >
Costo total €{scaledRecipe.estimatedTotalCost?.toFixed(2)}

€{scaledRecipe.costPerScaledUnit?.toFixed(3)} por {recipe.yield_unit}

Desglose por ingrediente

{scaledRecipe.ingredients.map((ingredient, index) => (

{ingredient.ingredient_name}

{ingredient.scaledQuantity.toFixed(2)} {ingredient.unit}

€{ingredient.scaledCost?.toFixed(2) || '0.00'}

))}

Análisis de rentabilidad

• Costo de ingredientes: €{scaledRecipe.estimatedTotalCost?.toFixed(2)}

• Margen sugerido (300%): €{((scaledRecipe.estimatedTotalCost || 0) * 3).toFixed(2)}

• Precio de venta sugerido por {recipe.yield_unit}: €{((scaledRecipe.costPerScaledUnit || 0) * 4).toFixed(2)}

); const renderEquipmentModal = () => ( setIsEquipmentModalOpen(false)} title="Equipo Necesario" >

Equipo general de la receta

{recipe.equipment_needed.map((equipment, index) => (
{EQUIPMENT_ICONS[equipment.toLowerCase()] || '🔧'} {equipment}
))}

Equipo por instrucción

{recipe.instructions .filter(instruction => instruction.equipment && instruction.equipment.length > 0) .map((instruction) => (

Paso {instruction.step_number}

{instruction.equipment?.map((equipment, idx) => ( {EQUIPMENT_ICONS[equipment.toLowerCase()] || '🔧'} {equipment} ))}
))}
); return (
{renderRecipeHeader()} {renderScalingControls()}
{renderIngredients()}
{renderInstructions()}
{renderNutritionModal()} {renderCostingModal()} {renderEquipmentModal()}
); }; export default RecipeDisplay;