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])); const RD_THEME = Object.freeze({ surface: '#393937', surfaceSoft: '#2f2f2d', surfaceActive: '#23221e', border: '#444442', borderSoft: '#56534f', borderStrong: '#787876', textPrimary: '#ddd6ca', textSecondary: '#d7d2c8', textMuted: '#9b978f', }); function forceBg(bg) { return `background:${bg} !important; background-image:none !important; box-shadow:none !important;`; } function forceBgBorder(bg, border) { return `${forceBg(bg)} border:1px solid ${border} !important;`; } function renderTagChip(label) { return `${escapeHtml(label)}`; } function setTabButtonState(btn, active) { if (!btn) return; btn.style.color = active ? RD_THEME.textPrimary : RD_THEME.textMuted; btn.style.borderBottomColor = active ? RD_THEME.borderStrong : 'transparent'; btn.style.fontWeight = active ? '600' : '500'; } export function getRecipeDetailHTML() { return `

`; } /* ── 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`; const tagsHtml = []; for (const slotId of recipe.allowedSlots) { const label = slotLabelMap[slotId]; if (label) tagsHtml.push(renderTagChip(label)); } for (const tag of (recipe.tags || [])) { tagsHtml.push(renderTagChip(tag)); } document.getElementById('rd-tags').innerHTML = tagsHtml.join(''); renderIngredients(recipe); renderSteps(recipe); const tabBtns = document.querySelectorAll('.rd-tab-btn'); const tabs = document.querySelectorAll('.rd-tab-content'); tabBtns.forEach((b) => { setTabButtonState(b, false); }); setTabButtonState(tabBtns[0], true); 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 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, }; } function fmtAmt(n) { return Number.isInteger(n) ? String(n) : String(parseFloat(n.toFixed(1))); } /* ── 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 total = computeEffectiveNutritionTotals(recipe); return `

Wartości odżywcze

${total.kcal}kcal
${total.protein}gBiałko
${total.carbs}gWęgle
${total.fat}gTłuszcze

Porcje

${currentServings}
`; } 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 isExpanded = expandedAlternatives.has(origId); const rowStyle = 'background:#393937 !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;'; const toggleBtn = hasAlts ? `` : ''; let rowHtml = `
`; rowHtml += '
'; rowHtml += `
${escapeHtml(effectiveName)}
`; rowHtml += '
'; rowHtml += toggleBtn; rowHtml += `
${fmtAmt(scaledAmount)} ${escapeHtml(ing.unit)}
`; rowHtml += '
'; rowHtml += '
'; 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 altNutrition = nutritionForAmount(altId, scaledAmount, ing.unit); const checkbox = ` ${isSelected ? '' : ''} `; const nutritionLine = altNutrition ? `
${altNutrition.kcal} kcal · ${altNutrition.protein}g B · ${altNutrition.fat}g T · ${altNutrition.carbs}g W
` : ''; return ``; }); altListHtml = `
${optionCards.join('')}
`; } rowHtml += altListHtml; rowHtml += '
'; return `
  • ${rowHtml}
  • `; }).join(''); container.innerHTML = ` ${renderNutritionSummary(recipe)} `; container.querySelector('#rd-serv-minus')?.addEventListener('click', () => { if (currentServings <= 1) return; currentServings--; renderIngredients(recipe); }); container.querySelector('#rd-serv-plus')?.addEventListener('click', () => { if (currentServings >= 12) return; currentServings++; renderIngredients(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) => { setTabButtonState(b, false); }); setTabButtonState(btn, true); }); }); /* ── tab-bar scroll shadow ─────────────────── */ const scrollContainer = document.querySelector('#recipe-detail-view .overflow-y-auto'); const tabBar = document.getElementById('rd-tab-bar'); if (scrollContainer && tabBar) { scrollContainer.addEventListener('scroll', () => { tabBar.classList.toggle('rd-scrolled', scrollContainer.scrollTop > 2); }); } /* ── 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'); }; }