Add more example recipes
All checks were successful
Build and Deploy / build-and-push (push) Successful in 27s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 27s
This commit is contained in:
@@ -523,8 +523,10 @@ function renderDayContent(state) {
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-2 shadow-sm" data-slot-id="${slot.id}" data-entry-id="${eid}">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0 cursor-pointer planner-open-recipe" data-recipe-id="${escapeHtml(recipe.id)}">
|
||||
<div class="w-8 h-8 rounded-lg bg-[#d4d4d4] flex items-center justify-center shrink-0">
|
||||
<span class="text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>
|
||||
<div class="w-8 h-8 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
|
||||
${recipe.image
|
||||
? `<img src="${escapeHtml(recipe.image)}" alt="" class="w-full h-full object-cover">`
|
||||
: `<span class="w-full h-full flex items-center justify-center text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>
|
||||
@@ -629,8 +631,10 @@ function getRecentRecipeIds(plans, limit = 5) {
|
||||
function recipeCardHtml(r) {
|
||||
return `
|
||||
<button type="button" class="planner-pick-recipe w-full flex gap-2.5 p-2.5 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white text-left transition-all" data-recipe-id="${r.id}">
|
||||
<div class="w-11 h-11 rounded-lg bg-[#d4d4d4] flex items-center justify-center shrink-0">
|
||||
<span class="text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>
|
||||
<div class="w-11 h-11 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
|
||||
${r.image
|
||||
? `<img src="${escapeHtml(r.image)}" alt="" class="w-full h-full object-cover">`
|
||||
: `<span class="w-full h-full flex items-center justify-center text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 py-0.5">
|
||||
<p class="text-[13px] font-bold text-gray-900 line-clamp-2">${escapeHtml(r.title)}</p>
|
||||
@@ -872,9 +876,9 @@ function seedDemoIfEmpty(plans) {
|
||||
return {
|
||||
...plans,
|
||||
[todayKey]: {
|
||||
sniadanie: [{ id: newPlanEntryId(), recipeId: 'owsianka', servings: 1 }],
|
||||
obiad: [{ id: newPlanEntryId(), recipeId: 'salatka', servings: 1 }],
|
||||
kolacja: [{ id: newPlanEntryId(), recipeId: 'makaron', servings: 1 }],
|
||||
sniadanie: [{ id: newPlanEntryId(), recipeId: 'jajecznica', servings: 1 }],
|
||||
obiad: [{ id: newPlanEntryId(), recipeId: 'makaron_ricotta', servings: 1 }],
|
||||
kolacja: [{ id: newPlanEntryId(), recipeId: 'kanapka_losos', servings: 1 }],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ export function getRecipeDetailHTML() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rd-hero" class="h-[220px] shrink-0 w-full bg-[#d4d4d4] flex items-center justify-center relative">
|
||||
<span id="rd-hero-label" class="text-white font-medium text-[15px]"></span>
|
||||
<div id="rd-hero" class="h-[220px] shrink-0 w-full bg-[#d4d4d4] relative overflow-hidden">
|
||||
<img id="rd-hero-img" src="" alt="" class="w-full h-full object-cover hidden">
|
||||
<span id="rd-hero-label" class="absolute inset-0 flex items-center justify-center text-white font-medium text-[15px]"></span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-t-3xl -mt-6 relative z-30 pt-6 flex flex-col flex-1 overflow-hidden">
|
||||
@@ -128,7 +129,18 @@ function populateDetail(recipeId) {
|
||||
currentSubstitutions = {};
|
||||
expandedAlternatives.clear();
|
||||
|
||||
document.getElementById('rd-hero-label').textContent = `Zdjęcie: ${recipe.title}`;
|
||||
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();
|
||||
@@ -190,10 +202,11 @@ function renderNutritionLine(nutrition) {
|
||||
|
||||
/* ── ingredients tab with inline nutrition + summary ───── */
|
||||
|
||||
function computeIngredientNutritionTotals(recipe) {
|
||||
function computeEffectiveNutritionTotals(recipe) {
|
||||
let kcal = 0, protein = 0, fat = 0, carbs = 0;
|
||||
for (const ing of recipe.ingredients) {
|
||||
const n = nutritionForAmount(ing.ingredientId, ing.amount * currentServings);
|
||||
const effectiveId = getEffectiveIngredientId(ing.ingredientId);
|
||||
const n = nutritionForAmount(effectiveId, ing.amount * currentServings);
|
||||
if (n) {
|
||||
kcal += n.kcal;
|
||||
protein += n.protein;
|
||||
@@ -210,14 +223,8 @@ function computeIngredientNutritionTotals(recipe) {
|
||||
}
|
||||
|
||||
function renderNutritionSummary(recipe) {
|
||||
const n = recipe.nutritionPerServing;
|
||||
const s = currentServings;
|
||||
const total = {
|
||||
kcal: Math.round(n.kcal * s * 10) / 10,
|
||||
protein: Math.round(n.protein * s * 10) / 10,
|
||||
fat: Math.round(n.fat * s * 10) / 10,
|
||||
carbs: Math.round(n.carbs * s * 10) / 10,
|
||||
};
|
||||
const total = computeEffectiveNutritionTotals(recipe);
|
||||
|
||||
return `
|
||||
<div class="mb-4 pt-1 pb-3 border-b border-gray-100">
|
||||
@@ -255,7 +262,7 @@ function renderIngredientCard(name, amount, unit, nutrition, extra) {
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="${extra?.cls || 'bg-white'} rounded-xl p-2.5 border ${extra?.border || 'border-gray-200'} flex items-center gap-2.5">
|
||||
<div class="${extra?.cls || 'bg-white'} rounded-xl p-2.5 border ${extra?.border || 'border-gray-200'} flex items-center gap-2.5" ${extra?.dataAttrs || ''}>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
${extra?.prefix || ''}
|
||||
@@ -277,35 +284,52 @@ function renderIngredients(recipe) {
|
||||
if (!container) return;
|
||||
|
||||
const rows = recipe.ingredients.map((ing) => {
|
||||
const def = INGREDIENTS[ing.ingredientId];
|
||||
const name = def?.name || ing.ingredientId;
|
||||
const scaledAmount = ing.amount * currentServings;
|
||||
const nutrition = nutritionForAmount(ing.ingredientId, scaledAmount);
|
||||
|
||||
const origId = ing.ingredientId;
|
||||
const hasAlts = ing.alternatives && ing.alternatives.length > 0;
|
||||
const isExpanded = expandedAlternatives.has(ing.ingredientId);
|
||||
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);
|
||||
const isSwapped = effectiveId !== origId;
|
||||
const isExpanded = expandedAlternatives.has(origId);
|
||||
|
||||
const toggleBtn = hasAlts
|
||||
? `<button type="button" class="rd-alt-toggle shrink-0 w-5 h-5 rounded-full ${isExpanded ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-original-id="${escapeHtml(ing.ingredientId)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
||||
? `<button type="button" class="rd-alt-toggle shrink-0 w-5 h-5 rounded-full ${isExpanded ? 'bg-amber-50 text-amber-500' : isSwapped ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-original-id="${escapeHtml(origId)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
||||
: '';
|
||||
|
||||
const cardHtml = renderIngredientCard(name, scaledAmount, ing.unit, nutrition, { suffix: toggleBtn });
|
||||
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) {
|
||||
const altCards = ing.alternatives.map((altId) => {
|
||||
const altDef = INGREDIENTS[altId];
|
||||
const altName = altDef?.name || altId;
|
||||
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);
|
||||
|
||||
const radioDot = `<div class="w-3.5 h-3.5 rounded-full border-2 shrink-0 ${isSelected ? 'border-gray-900' : 'border-gray-300'} flex items-center justify-center">${isSelected ? '<div class="w-1.5 h-1.5 rounded-full bg-gray-900"></div>' : ''}</div>`;
|
||||
const defaultTag = isOriginal ? `<span class="text-[9px] px-1.5 py-0.5 rounded ${isSelected ? 'bg-gray-200 text-gray-600' : 'bg-gray-100 text-gray-400'} font-medium ml-1">Domyślny</span>` : '';
|
||||
|
||||
return renderIngredientCard(altName, scaledAmount, ing.unit, altNutrition, {
|
||||
cls: 'bg-gray-50',
|
||||
border: 'border-gray-100',
|
||||
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 = `
|
||||
<div class="rd-alt-list ${isExpanded ? '' : 'hidden'} mt-1.5 space-y-1.5" data-original-id="${escapeHtml(ing.ingredientId)}">
|
||||
<p class="text-[10px] text-gray-400 font-medium mb-1 pl-1">Można zamienić na:</p>
|
||||
${altCards.join('')}
|
||||
<div class="mt-1.5 space-y-1.5 rd-alt-options" data-original-id="${escapeHtml(origId)}">
|
||||
${optionCards.join('')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -324,12 +348,23 @@ function renderIngredients(recipe) {
|
||||
} else {
|
||||
expandedAlternatives.add(origId);
|
||||
}
|
||||
const list = container.querySelector(`.rd-alt-list[data-original-id="${origId}"]`);
|
||||
if (list) list.classList.toggle('hidden');
|
||||
btn.classList.toggle('bg-gray-100');
|
||||
btn.classList.toggle('text-gray-400');
|
||||
btn.classList.toggle('bg-amber-50');
|
||||
btn.classList.toggle('text-amber-500');
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -625,7 +660,6 @@ export function setupRecipeDetail() {
|
||||
if (!recipe) return;
|
||||
|
||||
document.getElementById('rd-planner-recipe-name').textContent = recipe.title;
|
||||
currentSubstitutions = {};
|
||||
expandedVariantGroups.clear();
|
||||
|
||||
const today = startOfDay(new Date());
|
||||
|
||||
@@ -55,8 +55,10 @@ function renderRecipeCard(recipe) {
|
||||
const labels = slotLabelsFor(recipe);
|
||||
return `
|
||||
<div onclick="openRecipeDetail('${escapeHtml(recipe.id)}')" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>
|
||||
<div class="h-32 bg-[#d4d4d4] relative overflow-hidden">
|
||||
${recipe.image
|
||||
? `<img src="${escapeHtml(recipe.image)}" alt="${escapeHtml(recipe.title)}" class="w-full h-full object-cover">`
|
||||
: `<span class="absolute inset-0 flex items-center justify-center text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">${escapeHtml(recipe.title)}</h3>
|
||||
|
||||
Reference in New Issue
Block a user