import { RECIPES, INGREDIENTS } from '../data/catalog.js?v=2'; import { MEAL_SLOTS } from '../planner/mealSlots.js'; function escapeHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } const slotLabelMap = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label])); export function getRecipeDetailHTML() { return `

1
`; } /* ── state ─────────────────────────────────────────────── */ let currentRecipeId = null; let currentServings = 1; let currentSubstitutions = {}; let expandedAlternatives = new Set(); function getEffectiveIngredientId(originalId) { return currentSubstitutions[originalId] || originalId; } /* ── populate ──────────────────────────────────────────── */ function populateDetail(recipeId) { const recipe = RECIPES[recipeId]; if (!recipe) return; currentRecipeId = recipeId; currentServings = 1; currentSubstitutions = {}; expandedAlternatives.clear(); const heroImg = document.getElementById('rd-hero-img'); const heroLabel = document.getElementById('rd-hero-label'); if (recipe.image) { heroImg.src = recipe.image; heroImg.alt = recipe.title; heroImg.classList.remove('hidden'); heroLabel.textContent = ''; } else { heroImg.classList.add('hidden'); heroImg.src = ''; heroLabel.textContent = `Zdjęcie: ${recipe.title}`; } document.getElementById('rd-title').textContent = recipe.title; document.getElementById('rd-time').textContent = `${recipe.minutes} min`; updateKcalDisplay(); const tagsHtml = []; for (const slotId of recipe.allowedSlots) { const label = slotLabelMap[slotId]; if (label) tagsHtml.push(`${escapeHtml(label)}`); } for (const tag of (recipe.tags || [])) { tagsHtml.push(`${escapeHtml(tag)}`); } document.getElementById('rd-tags').innerHTML = tagsHtml.join(''); document.getElementById('rd-servings').textContent = '1'; renderIngredients(recipe); renderSteps(recipe); const tabBtns = document.querySelectorAll('.rd-tab-btn'); const tabs = document.querySelectorAll('.rd-tab-content'); tabBtns.forEach((b) => { b.classList.remove('text-gray-900', 'border-gray-900', 'font-semibold'); b.classList.add('text-gray-500', 'border-transparent', 'font-medium'); }); tabBtns[0]?.classList.remove('text-gray-500', 'border-transparent', 'font-medium'); tabBtns[0]?.classList.add('text-gray-900', 'border-gray-900', 'font-semibold'); tabs.forEach((t) => { t.classList.add('hidden'); t.classList.remove('block'); }); document.getElementById('rd-tab-ingredients')?.classList.remove('hidden'); document.getElementById('rd-tab-ingredients')?.classList.add('block'); } /* ── helpers ───────────────────────────────────────────── */ function updateKcalDisplay() { const el = document.getElementById('rd-kcal'); if (!el) return; const recipe = RECIPES[currentRecipeId]; if (!recipe) return; const kcal = Math.round(recipe.nutritionPerServing.kcal * currentServings); el.textContent = `${kcal} kcal`; } function nutritionForAmount(ingredientId, amount, unit) { const def = INGREDIENTS[ingredientId]; if (!def?.nutritionPer100g) return null; let grams = amount; if ((unit === 'szt.' || unit === 'szt') && def.weightPerPiece) { grams = amount * def.weightPerPiece; } const f = grams / 100; return { kcal: Math.round(def.nutritionPer100g.kcal * f), protein: Math.round(def.nutritionPer100g.protein * f * 10) / 10, fat: Math.round(def.nutritionPer100g.fat * f * 10) / 10, carbs: Math.round(def.nutritionPer100g.carbs * f * 10) / 10, }; } /* ── ingredients tab with inline nutrition + summary ───── */ function computeEffectiveNutritionTotals(recipe) { let kcal = 0, protein = 0, fat = 0, carbs = 0; for (const ing of recipe.ingredients) { const effectiveId = getEffectiveIngredientId(ing.ingredientId); const n = nutritionForAmount(effectiveId, ing.amount * currentServings, ing.unit); if (n) { kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs; } } return { kcal: Math.round(kcal), protein: Math.round(protein * 10) / 10, fat: Math.round(fat * 10) / 10, carbs: Math.round(carbs * 10) / 10, }; } function renderNutritionSummary(recipe) { const s = currentServings; const total = computeEffectiveNutritionTotals(recipe); return `
${s > 1 ? `

Wartości dla ${s} porcji

` : ''}
${total.kcal} Kalorie
${total.protein} g Białko
${total.carbs} g Węglo.
${total.fat} g Tłuszcze
`; } function renderIngredientCard(name, amount, unit, nutrition, extra) { const displayAmount = Number.isInteger(amount) ? amount : parseFloat(amount.toFixed(1)); const macroHtml = nutrition ? `
${nutrition.protein}g B${nutrition.fat}g T${nutrition.carbs}g W
` : ''; const kcalHtml = nutrition ? `${nutrition.kcal} kcal` : ''; return `
${extra?.prefix || ''} ${escapeHtml(name)} ${extra?.suffix || ''}
${macroHtml} ${extra?.badge || ''}
${displayAmount} ${escapeHtml(unit)} ${kcalHtml}
`; } function renderIngredients(recipe) { const container = document.getElementById('rd-tab-ingredients'); if (!container) return; const rows = recipe.ingredients.map((ing) => { const origId = ing.ingredientId; const hasAlts = ing.alternatives && ing.alternatives.length > 0; const effectiveId = hasAlts ? getEffectiveIngredientId(origId) : origId; const effectiveDef = INGREDIENTS[effectiveId]; const effectiveName = effectiveDef?.name || effectiveId; const scaledAmount = ing.amount * currentServings; const nutrition = nutritionForAmount(effectiveId, scaledAmount, ing.unit); const isSwapped = effectiveId !== origId; const isExpanded = expandedAlternatives.has(origId); const toggleBtn = hasAlts ? `` : ''; const cardBorder = isSwapped ? 'border-amber-200' : 'border-gray-200'; const cardCls = isSwapped ? 'bg-amber-50/30' : 'bg-white'; const cardHtml = renderIngredientCard(effectiveName, scaledAmount, ing.unit, nutrition, { suffix: toggleBtn, border: cardBorder, cls: cardCls, }); let altListHtml = ''; if (hasAlts && isExpanded) { const allOptions = [origId, ...ing.alternatives]; const optionCards = allOptions.map((altId) => { const def = INGREDIENTS[altId]; const altName = def?.name || altId; const isSelected = effectiveId === altId; const isOriginal = altId === origId; const altNutrition = nutritionForAmount(altId, scaledAmount, ing.unit); const radioDot = `
${isSelected ? '
' : ''}
`; const defaultTag = isOriginal ? `Domyślny` : ''; return renderIngredientCard(altName, scaledAmount, ing.unit, altNutrition, { cls: isSelected ? 'bg-gray-50' : 'bg-white hover:bg-gray-50 cursor-pointer', border: isSelected ? 'border-gray-900 ring-1 ring-gray-900' : 'border-gray-100', prefix: radioDot, badge: defaultTag, dataAttrs: `data-original-id="${escapeHtml(origId)}" data-alt-id="${escapeHtml(altId)}"`, }); }); altListHtml = `
${optionCards.join('')}
`; } return `
  • ${cardHtml}${altListHtml}
  • `; }).join(''); container.innerHTML = ` ${renderNutritionSummary(recipe)} `; container.querySelectorAll('.rd-alt-toggle').forEach((btn) => { btn.addEventListener('click', () => { const origId = btn.dataset.originalId; if (expandedAlternatives.has(origId)) { expandedAlternatives.delete(origId); } else { expandedAlternatives.add(origId); } renderIngredients(recipe); }); }); container.querySelectorAll('.rd-alt-options').forEach((group) => { group.querySelectorAll('[data-alt-id]').forEach((card) => { card.addEventListener('click', () => { const originalId = card.dataset.originalId; const altId = card.dataset.altId; if (altId === originalId) { delete currentSubstitutions[originalId]; } else { currentSubstitutions[originalId] = altId; } expandedAlternatives.delete(originalId); renderIngredients(recipe); }); }); }); } /* ── steps tab ─────────────────────────────────────────── */ function renderSteps(recipe) { const container = document.getElementById('rd-tab-steps'); if (!container) return; const steps = recipe.steps || []; if (steps.length === 0) { container.innerHTML = '

    Brak kroków przygotowania.

    '; return; } container.innerHTML = `
    ${steps.map((step, i) => `
    ${i + 1}

    ${escapeHtml(step)}

    `).join('')}
    `; } /* ── setup ─────────────────────────────────────────────── */ export function setupRecipeDetail() { document.querySelectorAll('.rd-tab-btn').forEach((btn) => { btn.addEventListener('click', () => { const tabId = btn.dataset.rdTab; document.querySelectorAll('.rd-tab-content').forEach((el) => { el.classList.add('hidden'); el.classList.remove('block'); }); const target = document.getElementById(`rd-tab-${tabId}`); if (target) { target.classList.remove('hidden'); target.classList.add('block'); target.parentElement.scrollTop = 0; } document.querySelectorAll('.rd-tab-btn').forEach((b) => { b.classList.remove('text-gray-900', 'border-gray-900', 'font-semibold'); b.classList.add('text-gray-500', 'border-transparent', 'font-medium'); }); btn.classList.remove('text-gray-500', 'border-transparent', 'font-medium'); btn.classList.add('text-gray-900', 'border-gray-900', 'font-semibold'); }); }); document.getElementById('rd-serv-minus')?.addEventListener('click', () => { if (currentServings <= 1) return; currentServings--; document.getElementById('rd-servings').textContent = currentServings; const recipe = RECIPES[currentRecipeId]; if (recipe) { renderIngredients(recipe); updateKcalDisplay(); } }); document.getElementById('rd-serv-plus')?.addEventListener('click', () => { if (currentServings >= 12) return; currentServings++; document.getElementById('rd-servings').textContent = currentServings; const recipe = RECIPES[currentRecipeId]; if (recipe) { renderIngredients(recipe); updateKcalDisplay(); } }); /* ── planner — delegate to MealPlanEditor ─────── */ document.getElementById('rd-add-to-planner-btn')?.addEventListener('click', () => { if (!currentRecipeId) return; window.openMealPlanEditor?.({ mode: 'add', recipeId: currentRecipeId, servings: currentServings, substitutions: { ...currentSubstitutions }, }); }); window.openRecipeDetail = (recipeId) => { if (!recipeId || !RECIPES[recipeId]) return; populateDetail(recipeId); const view = document.getElementById('recipe-detail-view'); view.classList.remove('translate-x-full', 'opacity-0', 'pointer-events-none'); view.classList.add('translate-x-0', 'opacity-100', 'pointer-events-auto'); }; window.closeRecipeDetail = () => { window.closeMealPlanEditor?.(); const view = document.getElementById('recipe-detail-view'); view.classList.remove('translate-x-0', 'opacity-100', 'pointer-events-auto'); view.classList.add('translate-x-full', 'opacity-0', 'pointer-events-none'); }; }