Add products
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { INGREDIENTS, RECIPES, PRODUCTS, getProductsForIngredient } from '../data/catalog.js?v=6';
|
||||
import { INGREDIENTS, RECIPES, PRODUCTS, CATEGORY_LABELS, getProductsForIngredient } from '../data/catalog.js?v=8';
|
||||
import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
||||
import {
|
||||
addDays,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
newPlanEntryId,
|
||||
savePlans,
|
||||
} from '../services/planStore.js?v=2';
|
||||
import { dayHasAnyMeal, autoSelectProducts, saveLastProductSelection } from '../services/planIngredients.js?v=3';
|
||||
import { dayHasAnyMeal, autoSelectProducts, saveLastProductSelection } from '../services/planIngredients.js?v=4';
|
||||
import { loadPantry } from '../services/pantryShopping.js?v=2';
|
||||
import { showAppToast } from './toast.js';
|
||||
import {
|
||||
@@ -34,6 +34,115 @@ function esc(s) {
|
||||
|
||||
const slotLabel = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label]));
|
||||
|
||||
/* ── Product Card Popup ────────────────────────────── */
|
||||
|
||||
function getProductCardHTML() {
|
||||
return `
|
||||
<div id="mpe-product-card-overlay" class="fixed inset-0 z-[70] bg-black/50 hidden flex items-center justify-center p-6" style="pointer-events:none">
|
||||
<div id="mpe-product-card" class="relative w-full max-w-xs bg-[#2d2e2b] rounded-2xl shadow-2xl overflow-hidden" style="pointer-events:auto; max-height:80vh; overflow-y:auto;">
|
||||
<div id="mpe-pc-hero" class="relative w-full h-[180px] bg-gray-800 overflow-hidden">
|
||||
<img id="mpe-pc-img" class="w-full h-full object-cover hidden" alt="" />
|
||||
<div id="mpe-pc-fallback" class="w-full h-full flex items-center justify-center">
|
||||
<i class="fas fa-box-open text-3xl text-gray-600"></i>
|
||||
</div>
|
||||
<button type="button" id="mpe-pc-close" class="absolute top-3 right-3 w-8 h-8 rounded-full bg-black/50 text-white flex items-center justify-center hover:bg-black/70 transition-colors">
|
||||
<i class="fas fa-times text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-4 pt-3 pb-4 space-y-3">
|
||||
<div>
|
||||
<p id="mpe-pc-brand" class="text-[10px] font-semibold uppercase tracking-wider text-emerald-400"></p>
|
||||
<h3 id="mpe-pc-name" class="text-[15px] font-bold text-gray-100 leading-snug mt-0.5"></h3>
|
||||
<p id="mpe-pc-category" class="text-[11px] text-gray-500 mt-0.5"></p>
|
||||
</div>
|
||||
<div id="mpe-pc-pack" class="hidden">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-wider text-gray-500">Opakowanie</span>
|
||||
<p id="mpe-pc-pack-val" class="text-[13px] font-semibold text-gray-300 mt-0.5"></p>
|
||||
</div>
|
||||
<div>
|
||||
<span id="mpe-pc-nut-label" class="text-[10px] font-semibold uppercase tracking-wider text-gray-500">Wartości odżywcze na 100 g</span>
|
||||
<div class="grid grid-cols-4 gap-2 mt-1.5">
|
||||
<div class="bg-[#393937] rounded-xl px-2.5 py-2 text-center">
|
||||
<p id="mpe-pc-kcal" class="text-[15px] font-bold text-gray-100 tabular-nums"></p>
|
||||
<p class="text-[9px] text-gray-500 font-medium mt-0.5">kcal</p>
|
||||
</div>
|
||||
<div class="bg-[#393937] rounded-xl px-2.5 py-2 text-center">
|
||||
<p id="mpe-pc-protein" class="text-[15px] font-bold text-blue-400 tabular-nums"></p>
|
||||
<p class="text-[9px] text-gray-500 font-medium mt-0.5">białko</p>
|
||||
</div>
|
||||
<div class="bg-[#393937] rounded-xl px-2.5 py-2 text-center">
|
||||
<p id="mpe-pc-fat" class="text-[15px] font-bold text-amber-400 tabular-nums"></p>
|
||||
<p class="text-[9px] text-gray-500 font-medium mt-0.5">tłuszcz</p>
|
||||
</div>
|
||||
<div class="bg-[#393937] rounded-xl px-2.5 py-2 text-center">
|
||||
<p id="mpe-pc-carbs" class="text-[15px] font-bold text-orange-400 tabular-nums"></p>
|
||||
<p class="text-[9px] text-gray-500 font-medium mt-0.5">węgl.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function openProductCard(ingredientId, productId) {
|
||||
const overlay = document.getElementById('mpe-product-card-overlay');
|
||||
if (!overlay) return;
|
||||
|
||||
const def = INGREDIENTS[ingredientId];
|
||||
const product = productId ? PRODUCTS[productId] : null;
|
||||
const name = product?.name || def?.name || ingredientId;
|
||||
const brand = product?.brand || '';
|
||||
const category = CATEGORY_LABELS[def?.category] || '';
|
||||
const nutrition = product?.nutritionPer100g || def?.nutritionPer100g;
|
||||
const image = product?.image || def?.image;
|
||||
const packLabel = product?.packLabel || def?.purchasePack?.label || '';
|
||||
const nutUnit = def?.pantryUnit === 'ml' ? '100 ml' : '100 g';
|
||||
|
||||
document.getElementById('mpe-pc-name').textContent = name;
|
||||
document.getElementById('mpe-pc-brand').textContent = brand;
|
||||
document.getElementById('mpe-pc-category').textContent = category;
|
||||
document.getElementById('mpe-pc-nut-label').textContent = `Wartości odżywcze na ${nutUnit}`;
|
||||
|
||||
const img = document.getElementById('mpe-pc-img');
|
||||
const fallback = document.getElementById('mpe-pc-fallback');
|
||||
if (image) {
|
||||
img.src = image;
|
||||
img.alt = name;
|
||||
img.classList.remove('hidden');
|
||||
fallback.classList.add('hidden');
|
||||
} else {
|
||||
img.classList.add('hidden');
|
||||
fallback.classList.remove('hidden');
|
||||
}
|
||||
|
||||
const packWrap = document.getElementById('mpe-pc-pack');
|
||||
if (packLabel) {
|
||||
packWrap.classList.remove('hidden');
|
||||
document.getElementById('mpe-pc-pack-val').textContent = packLabel;
|
||||
} else {
|
||||
packWrap.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (nutrition) {
|
||||
document.getElementById('mpe-pc-kcal').textContent = nutrition.kcal;
|
||||
document.getElementById('mpe-pc-protein').textContent = nutrition.protein + 'g';
|
||||
document.getElementById('mpe-pc-fat').textContent = nutrition.fat + 'g';
|
||||
document.getElementById('mpe-pc-carbs').textContent = nutrition.carbs + 'g';
|
||||
}
|
||||
|
||||
overlay.classList.remove('hidden');
|
||||
overlay.style.pointerEvents = 'auto';
|
||||
}
|
||||
|
||||
function closeProductCard() {
|
||||
const overlay = document.getElementById('mpe-product-card-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.add('hidden');
|
||||
overlay.style.pointerEvents = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/* ── HTML template ──────────────────────────────────── */
|
||||
|
||||
export function getMealPlanEditorHTML() {
|
||||
@@ -85,7 +194,8 @@ export function getMealPlanEditorHTML() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
</div>
|
||||
${getProductCardHTML()}`;
|
||||
}
|
||||
|
||||
/* ── Setup ──────────────────────────────────────────── */
|
||||
@@ -304,7 +414,7 @@ export function setupMealPlanEditor() {
|
||||
: '';
|
||||
|
||||
html += `<div class="flex items-center gap-2">`;
|
||||
html += `<div class="flex-1 min-w-0"><span class="text-[12px] font-semibold text-gray-900 truncate block">${esc(eName)}</span>${productBadge}</div>`;
|
||||
html += `<div class="flex-1 min-w-0 mpe-open-product-card cursor-pointer" data-eid="${esc(eid)}" data-pid="${esc(selectedProductId || '')}"><span class="text-[12px] font-semibold text-gray-900 truncate block">${esc(eName)}</span>${productBadge}</div>`;
|
||||
html += `<div class="shrink-0 flex items-center gap-2">`;
|
||||
html += shuffleBtn;
|
||||
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-orig-id="${esc(id)}" data-type="recipe">`;
|
||||
@@ -342,7 +452,12 @@ export function setupMealPlanEditor() {
|
||||
const disp = a.amount * S.servings;
|
||||
html += `<div class="mpe-ing-row rounded-xl p-2.5" style="background:#393937 !important; background-image:none !important; box-shadow:0 2px 8px rgba(0,0,0,0.25) !important; border:none !important;" data-ing-id="${esc(a.ingredientId)}" data-type="added">`;
|
||||
html += `<div class="flex items-center gap-2">`;
|
||||
html += `<div class="flex-1 min-w-0 flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span><span class="shrink-0 inline-flex items-center justify-center text-[#8f8b84]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span></div>`;
|
||||
const addedPid = S.productSelections[a.ingredientId] || '';
|
||||
const addedProduct = addedPid ? PRODUCTS[addedPid] : null;
|
||||
const addedBadge = addedProduct
|
||||
? `<div class="flex items-center gap-1 mt-0.5"><span class="text-[10px] text-emerald-400 truncate">${esc(addedProduct.name)}</span></div>`
|
||||
: '';
|
||||
html += `<div class="flex-1 min-w-0 mpe-open-product-card cursor-pointer" data-eid="${esc(a.ingredientId)}" data-pid="${esc(addedPid)}"><div class="flex items-center gap-1.5"><span class="text-[12px] font-semibold text-gray-900 truncate">${esc(name)}</span><span class="shrink-0 inline-flex items-center justify-center text-[#8f8b84]" title="Dodany składnik" aria-label="Dodany składnik"><i class="fas fa-plus text-[8px]"></i></span></div>${addedBadge}</div>`;
|
||||
html += `<button type="button" class="mpe-edit-amt shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg hover:bg-gray-100 transition-colors" data-ing-id="${esc(a.ingredientId)}" data-type="added">`;
|
||||
html += `<span class="text-[12px] font-semibold text-gray-900 tabular-nums">${fmtAmt(disp)}</span>`;
|
||||
html += `<span class="text-[11px] text-gray-500">${esc(a.unit)}</span></button>`;
|
||||
@@ -421,12 +536,22 @@ export function setupMealPlanEditor() {
|
||||
<div class="h-full pb-2 flex flex-col" style="background:#2d2e2b !important; background-image:none !important; box-shadow:none !important;">
|
||||
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-2">Wartości odżywcze</p>
|
||||
<div class="flex-1 flex items-center">
|
||||
<div class="rounded-xl border px-3 py-2" style="background:#2f2f2d !important; border-color:#444442 !important; box-shadow:0 2px 8px rgba(0,0,0,0.25);">
|
||||
<div class="grid grid-flow-col auto-cols-max gap-3 text-left">
|
||||
<div><span class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">${n.kcal}</span><span class="text-[9px] text-gray-500">kcal</span></div>
|
||||
<div><span class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">${n.protein}<span class="ml-0.5 text-[12px] font-medium text-[#9b978f]">g</span></span><span class="text-[9px] text-gray-500">Białko</span></div>
|
||||
<div><span class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">${n.carbs}<span class="ml-0.5 text-[12px] font-medium text-[#9b978f]">g</span></span><span class="text-[9px] text-gray-500">Węgle</span></div>
|
||||
<div><span class="block text-[15px] font-semibold text-[#ddd6ca] tabular-nums leading-none">${n.fat}<span class="ml-0.5 text-[12px] font-medium text-[#9b978f]">g</span></span><span class="text-[9px] text-gray-500">Tłuszcze</span></div>
|
||||
<div class="grid grid-cols-4 gap-1.5 w-full">
|
||||
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
|
||||
<p class="text-[15px] font-bold text-gray-100 tabular-nums leading-tight">${n.kcal}</p>
|
||||
<p class="text-[9px] text-gray-500 font-medium">kcal</p>
|
||||
</div>
|
||||
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
|
||||
<p class="text-[15px] font-bold text-blue-400 tabular-nums leading-tight">${n.protein}g</p>
|
||||
<p class="text-[9px] text-gray-500 font-medium">białko</p>
|
||||
</div>
|
||||
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
|
||||
<p class="text-[15px] font-bold text-amber-400 tabular-nums leading-tight">${n.fat}g</p>
|
||||
<p class="text-[9px] text-gray-500 font-medium">tłuszcz</p>
|
||||
</div>
|
||||
<div class="rounded-xl px-2 py-1.5 text-center" style="background:#393937;">
|
||||
<p class="text-[15px] font-bold text-orange-400 tabular-nums leading-tight">${n.carbs}g</p>
|
||||
<p class="text-[9px] text-gray-500 font-medium">węgl.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -636,11 +761,28 @@ export function setupMealPlanEditor() {
|
||||
renderNutrition();
|
||||
});
|
||||
|
||||
/* ── Product card ────────────────────────────── */
|
||||
|
||||
document.getElementById('mpe-pc-close')?.addEventListener('click', closeProductCard);
|
||||
document.getElementById('mpe-product-card-overlay')?.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'mpe-product-card-overlay') closeProductCard();
|
||||
});
|
||||
|
||||
/* ── Ingredient section delegation ────────────── */
|
||||
|
||||
const ingSec = document.getElementById('mpe-ing-section');
|
||||
|
||||
ingSec?.addEventListener('click', (e) => {
|
||||
// Check "zmień" and other inner buttons before the card wrapper
|
||||
const changeProdEarly = e.target.closest('.mpe-change-product');
|
||||
if (!changeProdEarly) {
|
||||
const cardBtn = e.target.closest('.mpe-open-product-card');
|
||||
if (cardBtn) {
|
||||
openProductCard(cardBtn.dataset.eid, cardBtn.dataset.pid || null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const remove = e.target.closest('.mpe-remove-ing');
|
||||
if (remove) {
|
||||
if (remove.dataset.type === 'added') {
|
||||
|
||||
Reference in New Issue
Block a user