Reorganise the views and prepare summary
All checks were successful
Build and Deploy / build-and-push (push) Successful in 23s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 23s
This commit is contained in:
@@ -35,8 +35,8 @@ const WEEKDAYS_LONG = [
|
||||
'Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota',
|
||||
];
|
||||
|
||||
/** Odstęp od dołu planera = miejsce na dolną nawigację. Ten sam w `bottom`, `max-height` i w `translateY(calc(100% + …))` przy zamknięciu — inaczej zostaje widoczny uchwyt. */
|
||||
const PLANNER_SHEET_BOTTOM_INSET = '5.25rem';
|
||||
const PLANNER_SHEET_MAX_HEIGHT = '70vh';
|
||||
const PLANNER_SHEET_OFF_TRANSFORM = `translateY(calc(100% + ${PLANNER_SHEET_BOTTOM_INSET}))`;
|
||||
|
||||
function recipesForSlot(slotId) {
|
||||
@@ -100,7 +100,12 @@ export function getMealPlannerHTML() {
|
||||
</div>
|
||||
|
||||
<div id="planner-scroll" class="flex-1 overflow-y-auto px-4 pt-3 pb-4">
|
||||
<p id="planner-day-heading" class="text-[13px] font-semibold text-gray-900 tabular-nums mb-2"></p>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p id="planner-day-heading" class="text-[13px] font-semibold text-gray-900 tabular-nums"></p>
|
||||
<button type="button" id="planner-copy-day" class="shrink-0 text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2.5 py-1 rounded-lg hover:bg-gray-100 transition-colors flex items-center gap-1.5">
|
||||
<i class="fas fa-copy text-[9px]"></i>Kopiuj dzień
|
||||
</button>
|
||||
</div>
|
||||
<div id="planner-summary-card" class="rounded-xl border border-amber-200/80 bg-gradient-to-br from-amber-50 to-white p-2.5 shadow-sm mb-3">
|
||||
<div class="flex items-start justify-between gap-2 mb-2">
|
||||
<div>
|
||||
@@ -144,17 +149,20 @@ export function getMealPlannerHTML() {
|
||||
</div>
|
||||
|
||||
<div id="planner-picker-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
|
||||
<div id="planner-picker-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col min-h-0 will-change-transform" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}; max-height: calc(100% - ${PLANNER_SHEET_BOTTOM_INSET}); transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-picker-title" aria-modal="true">
|
||||
<div id="planner-picker-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-picker-title" aria-modal="true">
|
||||
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
|
||||
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
|
||||
<h2 id="planner-picker-title" class="text-[15px] font-bold text-gray-900 leading-tight pr-2">Wybierz przepis</h2>
|
||||
<p id="planner-picker-sub" class="text-[11px] text-gray-500 mt-1"></p>
|
||||
</div>
|
||||
<div class="shrink-0 px-4 pt-2 pb-2">
|
||||
<input type="text" id="planner-picker-search" class="w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-sm outline-none focus:border-gray-400 placeholder:text-gray-400" placeholder="Szukaj przepisu…" />
|
||||
</div>
|
||||
<div id="planner-picker-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
|
||||
</div>
|
||||
|
||||
<div id="planner-ing-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
|
||||
<div id="planner-ing-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col min-h-0 will-change-transform" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}; max-height: calc(100% - ${PLANNER_SHEET_BOTTOM_INSET}); transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true">
|
||||
<div id="planner-ing-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-labelledby="planner-ing-title" aria-modal="true">
|
||||
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone aria-label="Przeciągnij w dół, by zamknąć">
|
||||
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5" aria-hidden="true"></div>
|
||||
<h2 id="planner-ing-title" class="text-[15px] font-bold text-gray-900 leading-tight pr-2">Składniki i spiżarnia</h2>
|
||||
@@ -173,6 +181,16 @@ export function getMealPlannerHTML() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="planner-copy-backdrop" class="absolute left-0 right-0 top-0 z-[45] bg-black/45 hidden opacity-0 transition-opacity duration-200" style="bottom: ${PLANNER_SHEET_BOTTOM_INSET}" aria-hidden="true"></div>
|
||||
<div id="planner-copy-sheet" class="absolute left-0 right-0 z-[50] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] flex flex-col will-change-transform" style="visibility: hidden; bottom: ${PLANNER_SHEET_BOTTOM_INSET}; height: auto; max-height: ${PLANNER_SHEET_MAX_HEIGHT}; transform: ${PLANNER_SHEET_OFF_TRANSFORM}; transition: transform 300ms cubic-bezier(0.32, 0.72, 0, 1)" role="dialog" aria-modal="true">
|
||||
<div class="shrink-0 px-4 pt-3 pb-2 border-b border-gray-100 touch-none cursor-grab active:cursor-grabbing select-none" data-planner-sheet-drag-zone>
|
||||
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-2.5"></div>
|
||||
<h2 class="text-[15px] font-bold text-gray-900 leading-tight">Kopiuj plan dnia</h2>
|
||||
<p id="planner-copy-sub" class="text-[11px] text-gray-500 mt-1">Wybierz dzień docelowy.</p>
|
||||
</div>
|
||||
<div id="planner-copy-list" class="min-h-0 flex-1 overflow-y-auto no-scrollbar px-4 py-2.5 pb-8 space-y-2"></div>
|
||||
</div>
|
||||
|
||||
<div id="planner-toast" class="pointer-events-none absolute left-4 right-4 bottom-28 z-[55] opacity-0 translate-y-2 transition-all duration-300" role="status">
|
||||
<div class="rounded-xl bg-gray-900 text-white text-sm font-medium px-4 py-3 shadow-lg text-center" id="planner-toast-text"></div>
|
||||
</div>
|
||||
@@ -351,6 +369,7 @@ function showPlannerToast(message) {
|
||||
|
||||
function openSheet(backdrop, sheet) {
|
||||
if (!backdrop || !sheet) return;
|
||||
sheet.style.visibility = 'visible';
|
||||
sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)';
|
||||
sheet.style.transform = 'translateY(0)';
|
||||
backdrop.classList.remove('hidden');
|
||||
@@ -364,7 +383,10 @@ function closeSheet(backdrop, sheet) {
|
||||
sheet.style.transition = 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)';
|
||||
sheet.style.transform = PLANNER_SHEET_OFF_TRANSFORM;
|
||||
backdrop.classList.add('opacity-0');
|
||||
setTimeout(() => backdrop.classList.add('hidden'), 300);
|
||||
setTimeout(() => {
|
||||
backdrop.classList.add('hidden');
|
||||
sheet.style.visibility = 'hidden';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/** Zamykanie panelu: przeciągnięcie nagłówka w dół (pointer). */
|
||||
@@ -472,10 +494,22 @@ function renderDayContent(state) {
|
||||
const slotsRoot = document.getElementById('planner-meal-slots');
|
||||
if (!slotsRoot) return;
|
||||
|
||||
const skipped = dayPlan._skipped || {};
|
||||
|
||||
slotsRoot.innerHTML = MEAL_SLOTS.map((slot) => {
|
||||
const entries = Array.isArray(dayPlan[slot.id]) ? dayPlan[slot.id] : [];
|
||||
const isSkipped = skipped[slot.id] === true;
|
||||
const entries = isSkipped ? [] : (Array.isArray(dayPlan[slot.id]) ? dayPlan[slot.id] : []);
|
||||
|
||||
let slotKcal = 0;
|
||||
entries.forEach((entry) => {
|
||||
const r = entry?.recipeId ? RECIPES[entry.recipeId] : null;
|
||||
if (r) slotKcal += Math.round(r.nutritionPerServing.kcal * Math.max(1, Number(entry.servings) || 1));
|
||||
});
|
||||
const kcalBadge = slotKcal > 0
|
||||
? `<span class="text-[10px] font-semibold text-amber-600 tabular-nums shrink-0 ml-auto">${slotKcal} kcal</span>`
|
||||
: '';
|
||||
const countLabel = entries.length > 1
|
||||
? `<span class="text-[10px] font-semibold text-gray-400 tabular-nums shrink-0 ml-auto">${entries.length} dania</span>`
|
||||
? `<span class="text-[10px] font-semibold text-gray-400 tabular-nums shrink-0">${entries.length} dania</span>`
|
||||
: '';
|
||||
|
||||
const entryCards = entries.map((entry) => {
|
||||
@@ -488,12 +522,12 @@ function renderDayContent(state) {
|
||||
return `
|
||||
<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">
|
||||
<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>
|
||||
<div class="min-w-0">
|
||||
<p class="text-[13px] font-bold text-gray-900 truncate">${escapeHtml(recipe.title)}</p>
|
||||
<p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>
|
||||
<p class="text-[11px] text-gray-500 mt-0.5 tabular-nums">
|
||||
<i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${recipe.minutes} min
|
||||
<span class="mx-1.5 text-gray-300">·</span>
|
||||
@@ -516,11 +550,35 @@ function renderDayContent(state) {
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
const addLabel = entries.length === 0 ? 'Dodaj przepis' : 'Dodaj kolejny przepis';
|
||||
if (isSkipped) {
|
||||
return `
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden opacity-60" data-slot-id="${slot.id}">
|
||||
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90">
|
||||
<span class="w-7 h-7 rounded-lg bg-gray-100 flex items-center justify-center text-gray-400 shrink-0">
|
||||
<i class="fas ${slot.icon} text-[13px]" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="text-[13px] font-semibold text-gray-400 truncate min-w-0 flex-1">${slot.label}</span>
|
||||
</div>
|
||||
<div class="p-2.5 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-400 italic"><i class="fas fa-forward text-[9px] mr-1.5"></i>Pominięto</span>
|
||||
<button type="button" class="planner-unskip text-[11px] font-semibold text-gray-500 hover:text-gray-900 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}">Cofnij</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const addLabel = entries.length === 0 ? 'Dodaj przepis' : 'Dodaj kolejny';
|
||||
const addClasses = entries.length === 0
|
||||
? 'planner-add-meal w-full py-2 rounded-lg border border-dashed border-gray-200 text-[13px] font-semibold text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-colors'
|
||||
? 'planner-add-meal flex-1 py-2 rounded-lg border border-dashed border-gray-200 text-[13px] font-semibold text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-colors'
|
||||
: 'planner-add-meal w-full py-1.5 rounded-lg border border-dashed border-gray-200 text-xs font-semibold text-gray-600 hover:bg-gray-50 hover:border-gray-300 transition-colors';
|
||||
|
||||
const skipBtn = entries.length === 0
|
||||
? `<button type="button" class="planner-skip-meal shrink-0 py-2 px-3 rounded-lg text-[11px] font-semibold text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors" data-slot-id="${slot.id}"><i class="fas fa-forward text-[9px] mr-1"></i>Pomijam</button>`
|
||||
: '';
|
||||
|
||||
const bottomRow = entries.length === 0
|
||||
? `<div class="flex gap-2">${`<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`}${skipBtn}</div>`
|
||||
: `<button type="button" class="${addClasses}" data-slot-id="${slot.id}"><i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>${addLabel}</button>`;
|
||||
|
||||
return `
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm overflow-hidden" data-slot-id="${slot.id}">
|
||||
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-100 bg-gray-50/90">
|
||||
@@ -529,13 +587,11 @@ function renderDayContent(state) {
|
||||
</span>
|
||||
<span class="text-[13px] font-semibold text-gray-900 truncate min-w-0 flex-1">${slot.label}</span>
|
||||
${countLabel}
|
||||
${kcalBadge}
|
||||
</div>
|
||||
<div class="p-2.5 space-y-2">
|
||||
${entryCards}
|
||||
<button type="button" class="${addClasses}" data-slot-id="${slot.id}">
|
||||
<i class="fas fa-plus text-[10px] mr-1 opacity-70" aria-hidden="true"></i>
|
||||
${addLabel}
|
||||
</button>
|
||||
${bottomRow}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
@@ -549,23 +605,29 @@ function escapeHtml(s) {
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function renderPickerList(slotId) {
|
||||
const slot = MEAL_SLOTS.find((s) => s.id === slotId);
|
||||
const list = document.getElementById('planner-picker-list');
|
||||
const title = document.getElementById('planner-picker-title');
|
||||
const sub = document.getElementById('planner-picker-sub');
|
||||
if (!list || !title || !sub) return;
|
||||
|
||||
title.textContent = 'Wybierz przepis';
|
||||
sub.textContent = slot ? `Dla: ${slot.label}. Przeciągnij nagłówek w dół lub dotknij tła, by zamknąć.` : '';
|
||||
|
||||
const recipes = recipesForSlot(slotId);
|
||||
if (recipes.length === 0) {
|
||||
list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak dopasowanych przepisów.</p>';
|
||||
return;
|
||||
function getRecentRecipeIds(plans, limit = 5) {
|
||||
const seen = new Map();
|
||||
const keys = Object.keys(plans).sort().reverse();
|
||||
for (const key of keys) {
|
||||
const day = plans[key];
|
||||
if (!day) continue;
|
||||
for (const slotId of Object.keys(day)) {
|
||||
if (slotId === '_skipped') continue;
|
||||
const entries = day[slotId];
|
||||
if (!Array.isArray(entries)) continue;
|
||||
for (const e of entries) {
|
||||
if (e?.recipeId && RECIPES[e.recipeId] && !seen.has(e.recipeId)) {
|
||||
seen.set(e.recipeId, true);
|
||||
if (seen.size >= limit) return [...seen.keys()];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...seen.keys()];
|
||||
}
|
||||
|
||||
list.innerHTML = recipes.map((r) => `
|
||||
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>
|
||||
@@ -578,8 +640,55 @@ function renderPickerList(slotId) {
|
||||
<i class="fas fa-clock text-gray-400 mr-0.5" aria-hidden="true"></i>${r.minutes} min
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
`).join('');
|
||||
</button>`;
|
||||
}
|
||||
|
||||
let _pickerSlotRecipes = [];
|
||||
let _pickerPlans = {};
|
||||
|
||||
function renderPickerList(slotId, plans, query = '') {
|
||||
const slot = MEAL_SLOTS.find((s) => s.id === slotId);
|
||||
const list = document.getElementById('planner-picker-list');
|
||||
const title = document.getElementById('planner-picker-title');
|
||||
const sub = document.getElementById('planner-picker-sub');
|
||||
if (!list || !title || !sub) return;
|
||||
|
||||
title.textContent = 'Wybierz przepis';
|
||||
sub.textContent = slot ? `Dla: ${slot.label}` : '';
|
||||
|
||||
const allRecipes = recipesForSlot(slotId);
|
||||
_pickerSlotRecipes = allRecipes;
|
||||
_pickerPlans = plans;
|
||||
|
||||
const q = query.trim().toLowerCase();
|
||||
const filtered = q
|
||||
? allRecipes.filter((r) => r.title.toLowerCase().includes(q) || (r.tags || []).some((t) => t.toLowerCase().includes(q)))
|
||||
: allRecipes;
|
||||
|
||||
if (filtered.length === 0 && q) {
|
||||
list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak wyników.</p>';
|
||||
return;
|
||||
}
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = '<p class="text-sm text-gray-500 text-center py-6">Brak dopasowanych przepisów.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
if (!q) {
|
||||
const recentIds = getRecentRecipeIds(plans);
|
||||
const recentInSlot = recentIds.map((id) => RECIPES[id]).filter((r) => r && r.allowedSlots.includes(slotId));
|
||||
if (recentInSlot.length > 0) {
|
||||
html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1"><i class="fas fa-history text-[9px] mr-1"></i>Ostatnio używane</p>`;
|
||||
html += recentInSlot.map(recipeCardHtml).join('');
|
||||
html += `<div class="border-t border-gray-100 my-2"></div>`;
|
||||
html += `<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider px-0.5 pt-1 pb-1">Wszystkie</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += filtered.map(recipeCardHtml).join('');
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
function plIngredientWord(n) {
|
||||
@@ -792,6 +901,8 @@ export function setupMealPlanner() {
|
||||
const pickerSheet = document.getElementById('planner-picker-sheet');
|
||||
const ingBackdrop = document.getElementById('planner-ing-backdrop');
|
||||
const ingSheet = document.getElementById('planner-ing-sheet');
|
||||
const copyBackdrop = document.getElementById('planner-copy-backdrop');
|
||||
const copySheet = document.getElementById('planner-copy-sheet');
|
||||
|
||||
const rerender = () => {
|
||||
syncModeToggle(state.mode);
|
||||
@@ -859,11 +970,45 @@ export function setupMealPlanner() {
|
||||
});
|
||||
|
||||
document.getElementById('planner-meal-slots')?.addEventListener('click', (e) => {
|
||||
const skipBtn = e.target.closest('.planner-skip-meal');
|
||||
if (skipBtn) {
|
||||
const slotId = skipBtn.getAttribute('data-slot-id');
|
||||
if (!slotId) return;
|
||||
const key = dateKey(state.selected);
|
||||
if (!state.plans[key]) state.plans[key] = {};
|
||||
if (!state.plans[key]._skipped) state.plans[key]._skipped = {};
|
||||
state.plans[key]._skipped[slotId] = true;
|
||||
persist();
|
||||
return;
|
||||
}
|
||||
const unskipBtn = e.target.closest('.planner-unskip');
|
||||
if (unskipBtn) {
|
||||
const slotId = unskipBtn.getAttribute('data-slot-id');
|
||||
if (!slotId) return;
|
||||
const key = dateKey(state.selected);
|
||||
if (state.plans[key]?._skipped) {
|
||||
delete state.plans[key]._skipped[slotId];
|
||||
if (Object.keys(state.plans[key]._skipped).length === 0) delete state.plans[key]._skipped;
|
||||
if (Object.keys(state.plans[key]).length === 0) delete state.plans[key];
|
||||
}
|
||||
persist();
|
||||
return;
|
||||
}
|
||||
const openRecipe = e.target.closest('.planner-open-recipe');
|
||||
if (openRecipe) {
|
||||
const recipeId = openRecipe.getAttribute('data-recipe-id');
|
||||
if (recipeId && window.openRecipeDetail) {
|
||||
window.openRecipeDetail(recipeId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const addBtn = e.target.closest('.planner-add-meal');
|
||||
if (addBtn) {
|
||||
const slotId = addBtn.getAttribute('data-slot-id');
|
||||
state.pickerSlot = slotId;
|
||||
renderPickerList(slotId);
|
||||
const searchInput = document.getElementById('planner-picker-search');
|
||||
if (searchInput) searchInput.value = '';
|
||||
renderPickerList(slotId, state.plans);
|
||||
openSheet(pickerBackdrop, pickerSheet);
|
||||
return;
|
||||
}
|
||||
@@ -904,6 +1049,12 @@ export function setupMealPlanner() {
|
||||
closeSheet(pickerBackdrop, pickerSheet);
|
||||
};
|
||||
|
||||
document.getElementById('planner-picker-search')?.addEventListener('input', (e) => {
|
||||
if (state.pickerSlot) {
|
||||
renderPickerList(state.pickerSlot, state.plans, e.target.value);
|
||||
}
|
||||
});
|
||||
|
||||
bindPlannerSheetDragClose(pickerSheet, closePicker);
|
||||
bindPlannerSheetDragClose(ingSheet, () => closeSheet(ingBackdrop, ingSheet));
|
||||
|
||||
@@ -933,6 +1084,63 @@ export function setupMealPlanner() {
|
||||
closeSheet(ingBackdrop, ingSheet);
|
||||
});
|
||||
|
||||
const closeCopy = () => closeSheet(copyBackdrop, copySheet);
|
||||
bindPlannerSheetDragClose(copySheet, closeCopy);
|
||||
copyBackdrop?.addEventListener('click', closeCopy);
|
||||
|
||||
document.getElementById('planner-copy-day')?.addEventListener('click', () => {
|
||||
const srcKey = dateKey(state.selected);
|
||||
const srcPlan = state.plans[srcKey];
|
||||
if (!srcPlan || Object.keys(srcPlan).length === 0) {
|
||||
showPlannerToast('Ten dzień jest pusty — nie ma co kopiować.');
|
||||
return;
|
||||
}
|
||||
const copyList = document.getElementById('planner-copy-list');
|
||||
if (!copyList) return;
|
||||
const days = [];
|
||||
for (let i = -3; i <= 10; i++) {
|
||||
if (i === 0) continue;
|
||||
const d = addDays(state.selected, i);
|
||||
days.push(d);
|
||||
}
|
||||
copyList.innerHTML = days.map((d) => {
|
||||
const wd = WEEKDAYS_LONG[d.getDay()];
|
||||
const label = `${wd}, ${d.getDate()} ${MONTHS_SHORT[d.getMonth()]}`;
|
||||
const hasMeals = dayHasAnyMeal(state.plans, d);
|
||||
const badge = hasMeals ? '<span class="text-[10px] text-amber-600 font-semibold">ma posiłki</span>' : '';
|
||||
return `<button type="button" class="planner-copy-target w-full flex items-center justify-between gap-2 p-3 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white transition-all text-left" data-target-ts="${d.getTime()}">
|
||||
<span class="text-[13px] font-semibold text-gray-900">${escapeHtml(label)}</span>
|
||||
${badge}
|
||||
</button>`;
|
||||
}).join('');
|
||||
openSheet(copyBackdrop, copySheet);
|
||||
});
|
||||
|
||||
document.getElementById('planner-copy-list')?.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.planner-copy-target');
|
||||
if (!btn) return;
|
||||
const targetDate = new Date(Number(btn.getAttribute('data-target-ts')));
|
||||
const srcKey = dateKey(state.selected);
|
||||
const tgtKey = dateKey(targetDate);
|
||||
const srcPlan = state.plans[srcKey];
|
||||
if (!srcPlan) return;
|
||||
|
||||
const copy = {};
|
||||
for (const [slotId, entries] of Object.entries(srcPlan)) {
|
||||
if (slotId === '_skipped') {
|
||||
copy._skipped = { ...entries };
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(entries)) {
|
||||
copy[slotId] = entries.map((e) => ({ ...e, id: newPlanEntryId() }));
|
||||
}
|
||||
}
|
||||
state.plans[tgtKey] = copy;
|
||||
closeCopy();
|
||||
persist();
|
||||
showPlannerToast('Plan skopiowany!');
|
||||
});
|
||||
|
||||
ingSheet?.addEventListener('click', (e) => {
|
||||
const row = e.target.closest('.planner-ing-row');
|
||||
if (!row || !ingSheet.contains(row)) return;
|
||||
@@ -983,6 +1191,11 @@ export function setupMealPlanner() {
|
||||
|
||||
rerender();
|
||||
|
||||
window.refreshPlanner = () => {
|
||||
state.plans = loadPlans();
|
||||
rerender();
|
||||
};
|
||||
|
||||
bindCalendarSwipeGesture(state, rerender);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
Reference in New Issue
Block a user