Add meal plan editor
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m19s
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m19s
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
weekContains,
|
||||
} from '../services/dateUtils.js';
|
||||
import {
|
||||
computeEntryNutrition,
|
||||
computeFullForecast,
|
||||
countDayShortfalls,
|
||||
dayHasAnyMeal,
|
||||
@@ -502,8 +503,7 @@ function renderDayContent(state) {
|
||||
|
||||
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));
|
||||
if (entry?.recipeId && RECIPES[entry.recipeId]) slotKcal += computeEntryNutrition(entry).kcal;
|
||||
});
|
||||
const kcalBadge = slotKcal > 0
|
||||
? `<span class="text-[10px] font-semibold text-amber-600 tabular-nums shrink-0 ml-auto">${slotKcal} kcal</span>`
|
||||
@@ -516,9 +516,14 @@ function renderDayContent(state) {
|
||||
const recipe = entry && entry.recipeId ? RECIPES[entry.recipeId] : null;
|
||||
if (!recipe) return '';
|
||||
const servings = Math.max(1, Number(entry.servings) || 1);
|
||||
const n = recipe.nutritionPerServing;
|
||||
const kcal = Math.round(n.kcal * servings);
|
||||
const entryN = computeEntryNutrition(entry);
|
||||
const eid = escapeHtml(entry.id);
|
||||
const hasCustom = (entry.excludedIngredients?.length > 0) ||
|
||||
(entry.amountOverrides && Object.keys(entry.amountOverrides).length > 0) ||
|
||||
(entry.addedIngredients?.length > 0) ||
|
||||
(entry.substitutions && Object.keys(entry.substitutions).length > 0);
|
||||
const customDot = hasCustom ? '<span class="w-1.5 h-1.5 rounded-full bg-amber-400 inline-block shrink-0 ml-1"></span>' : '';
|
||||
const servLabel = servings > 1 ? `<span class="mx-1.5 text-gray-300">·</span>×${servings}` : '';
|
||||
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">
|
||||
@@ -529,24 +534,21 @@ function renderDayContent(state) {
|
||||
: `<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>
|
||||
<div class="flex items-center"><p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>${customDot}</div>
|
||||
<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>
|
||||
<i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${kcal} kcal
|
||||
<i class="fas fa-fire text-gray-400 mr-0.5" aria-hidden="true"></i>${entryN.kcal} kcal${servLabel}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="planner-clear-meal w-6 h-6 shrink-0 rounded-full border border-gray-200 text-gray-400 hover:text-red-600 hover:border-red-200 hover:bg-red-50 transition-colors flex items-center justify-center" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Usuń ten przepis">
|
||||
<i class="fas fa-times text-[9px]" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2 mt-1.5 pt-1.5 border-t border-gray-100">
|
||||
<span class="text-[11px] font-medium text-gray-500">Porcje</span>
|
||||
<div class="flex items-center gap-0.5 bg-gray-100 p-0.5 rounded-lg">
|
||||
<button type="button" class="planner-serv-minus w-6 h-6 bg-white rounded-md shadow-sm flex items-center justify-center text-gray-600 hover:text-black" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Mniej porcji"><i class="fas fa-minus text-[9px]"></i></button>
|
||||
<span class="planner-serv-count font-bold text-gray-900 text-xs w-5 text-center tabular-nums">${servings}</span>
|
||||
<button type="button" class="planner-serv-plus w-6 h-6 bg-white rounded-md shadow-sm flex items-center justify-center text-gray-600 hover:text-black" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Więcej porcji"><i class="fas fa-plus text-[9px]"></i></button>
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<button type="button" class="planner-edit-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-gray-900 hover:border-gray-400 hover:bg-gray-50 flex items-center justify-center transition-colors" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Edytuj ten przepis">
|
||||
<i class="fas fa-pencil text-[9px]" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" class="planner-clear-meal w-6 h-6 rounded-full border border-gray-200 text-gray-400 hover:text-red-600 hover:border-red-200 hover:bg-red-50 transition-colors flex items-center justify-center" data-slot-id="${slot.id}" data-entry-id="${eid}" aria-label="Usuń ten przepis">
|
||||
<i class="fas fa-times text-[9px]" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -1016,6 +1018,24 @@ export function setupMealPlanner() {
|
||||
openSheet(pickerBackdrop, pickerSheet);
|
||||
return;
|
||||
}
|
||||
const editBtn = e.target.closest('.planner-edit-meal');
|
||||
if (editBtn) {
|
||||
const slotId = editBtn.getAttribute('data-slot-id');
|
||||
const entryId = editBtn.getAttribute('data-entry-id');
|
||||
const key = dateKey(state.selected);
|
||||
const arr = state.plans[key]?.[slotId];
|
||||
if (!Array.isArray(arr)) return;
|
||||
const entry = arr.find((x) => x && x.id === entryId);
|
||||
if (!entry) return;
|
||||
window.openMealPlanEditor?.({
|
||||
mode: 'edit',
|
||||
recipeId: entry.recipeId,
|
||||
date: state.selected,
|
||||
slotId,
|
||||
entry,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const clearBtn = e.target.closest('.planner-clear-meal');
|
||||
if (clearBtn) {
|
||||
const slotId = clearBtn.getAttribute('data-slot-id');
|
||||
@@ -1031,21 +1051,6 @@ export function setupMealPlanner() {
|
||||
persist();
|
||||
return;
|
||||
}
|
||||
const minus = e.target.closest('.planner-serv-minus');
|
||||
const plus = e.target.closest('.planner-serv-plus');
|
||||
const slotId = (minus || plus)?.getAttribute('data-slot-id');
|
||||
const entryId = (minus || plus)?.getAttribute('data-entry-id');
|
||||
if (!slotId || !entryId) return;
|
||||
const key = dateKey(state.selected);
|
||||
const arr = state.plans[key]?.[slotId];
|
||||
if (!Array.isArray(arr)) return;
|
||||
const entry = arr.find((x) => x && x.id === entryId);
|
||||
if (!entry) return;
|
||||
let s = Math.max(1, Number(entry.servings) || 1);
|
||||
if (minus) s = Math.max(1, s - 1);
|
||||
if (plus) s = Math.min(12, s + 1);
|
||||
entry.servings = s;
|
||||
persist();
|
||||
});
|
||||
|
||||
const closePicker = () => {
|
||||
@@ -1069,13 +1074,16 @@ export function setupMealPlanner() {
|
||||
if (!pick || !state.pickerSlot) return;
|
||||
const recipeId = pick.getAttribute('data-recipe-id');
|
||||
if (!recipeId || !RECIPES[recipeId]) return;
|
||||
const key = dateKey(state.selected);
|
||||
if (!state.plans[key]) state.plans[key] = {};
|
||||
const slotId = state.pickerSlot;
|
||||
if (!state.plans[key][slotId]) state.plans[key][slotId] = [];
|
||||
state.plans[key][slotId].push({ id: newPlanEntryId(), recipeId, servings: 1 });
|
||||
closePicker();
|
||||
persist();
|
||||
setTimeout(() => {
|
||||
window.openMealPlanEditor?.({
|
||||
mode: 'add',
|
||||
recipeId,
|
||||
date: state.selected,
|
||||
slotId,
|
||||
});
|
||||
}, 320);
|
||||
});
|
||||
|
||||
document.getElementById('planner-open-ingredients')?.addEventListener('click', () => {
|
||||
|
||||
Reference in New Issue
Block a user