import { RECIPES, INGREDIENTS } from '../data/catalog.js?v=8';
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 `
`;
}
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) => `
`).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');
};
}