557 lines
25 KiB
JavaScript
557 lines
25 KiB
JavaScript
import {
|
||
INGREDIENTS,
|
||
CATEGORY_LABELS,
|
||
PRODUCTS,
|
||
pantryQtyStep,
|
||
getProductsForIngredient,
|
||
ingredientHasProducts,
|
||
} from '../data/catalog.js?v=6';
|
||
import { addIngredientToKitchenList, categoryLabel, loadPantry, setPantryQty, setPantryProductQty, getPantryTotal, getPantryProducts, getPantryGeneric } from '../services/pantryShopping.js?v=2';
|
||
import { showAppToast } from '../ui/toast.js';
|
||
|
||
/* ── helpers ── */
|
||
|
||
function esc(s) {
|
||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
function unitLabel(u) {
|
||
return u === 'szt' ? 'szt.' : u;
|
||
}
|
||
|
||
function normalizeSearch(q) {
|
||
return String(q).trim().toLowerCase();
|
||
}
|
||
|
||
const CATEGORY_ICONS = {
|
||
pieczywo: 'fa-bread-slice',
|
||
nabial: 'fa-cheese',
|
||
mieso_ryby: 'fa-drumstick-bite',
|
||
warzywa: 'fa-carrot',
|
||
owoce: 'fa-apple-whole',
|
||
suche: 'fa-wheat-awn',
|
||
przyprawy: 'fa-leaf',
|
||
inne: 'fa-jar',
|
||
};
|
||
|
||
/* ── state ── */
|
||
|
||
let showOnlyStock = false;
|
||
let editingId = null;
|
||
/** @type {Set<string>} */
|
||
const selectedCategories = new Set();
|
||
|
||
let editShopStep = 1;
|
||
let editShopUsesPacks = false;
|
||
|
||
const BOTTOM = '5.25rem';
|
||
const HIDDEN_Y = `translateY(calc(100% + ${BOTTOM}))`;
|
||
|
||
/* ══════════════════════ HTML SHELL ══════════════════════ */
|
||
|
||
export function getPantryHTML() {
|
||
return `
|
||
<div id="pantry-view" class="hidden flex flex-col h-full absolute inset-0 overflow-hidden bg-gray-50 z-10 pb-24">
|
||
<div class="shrink-0 bg-white border-b border-gray-100 mt-3 px-4 pt-2 pb-2.5 space-y-2">
|
||
<div class="flex items-center gap-2.5 bg-gray-100 rounded-2xl px-3.5 py-2.5 focus-within:ring-2 focus-within:ring-gray-900/10 transition-all">
|
||
<i class="fas fa-search text-gray-400 text-xs"></i>
|
||
<input type="search" id="pantry-search" autocomplete="off" placeholder="Szukaj produktu…"
|
||
class="flex-1 bg-transparent outline-none text-sm text-gray-800 placeholder-gray-400" />
|
||
</div>
|
||
<div id="pantry-category-chips" class="flex gap-2 overflow-x-auto no-scrollbar -mx-1 px-1 pb-0.5"></div>
|
||
<div class="flex items-center justify-end">
|
||
<label class="flex items-center gap-2 cursor-pointer select-none">
|
||
<span class="text-xs font-medium text-gray-500">Tylko na stanie</span>
|
||
<button type="button" id="pantry-stock-toggle" role="switch" aria-checked="false"
|
||
class="relative w-10 h-[22px] rounded-full bg-gray-200 transition-colors duration-200 shrink-0">
|
||
<span class="absolute left-0.5 top-0.5 w-[18px] h-[18px] bg-white rounded-full shadow-sm transition-transform duration-200"></span>
|
||
</button>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="pantry-scroll" class="flex-1 overflow-y-auto no-scrollbar">
|
||
<div id="pantry-board" class="px-4 pt-3 pb-4 space-y-2"></div>
|
||
</div>
|
||
|
||
<!-- ── product sheet ── -->
|
||
<div id="pv2-edit-bg" class="absolute inset-0 z-[38] bg-black/40 hidden opacity-0 transition-opacity duration-200"></div>
|
||
<div id="pv2-edit-sheet" class="absolute left-0 right-0 z-[40] bg-white rounded-t-3xl shadow-[0_-10px_40px_rgba(0,0,0,0.12)] px-5 pt-2 pb-4 flex flex-col gap-2.5 max-h-[75%] min-h-0 overflow-y-auto no-scrollbar"
|
||
style="bottom:${BOTTOM};transform:${HIDDEN_Y};transition:transform 300ms cubic-bezier(.32,.72,0,1)">
|
||
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto shrink-0"></div>
|
||
|
||
<div class="shrink-0">
|
||
<h2 id="pv2-edit-name" class="text-[15px] font-bold text-gray-900 leading-snug"></h2>
|
||
<p id="pv2-edit-meta" class="text-[11px] text-gray-500"></p>
|
||
</div>
|
||
|
||
<div class="shrink-0 flex items-center gap-2">
|
||
<span class="text-[10px] font-semibold uppercase tracking-wider text-gray-400 w-[3.2rem] shrink-0">Zapas</span>
|
||
<button type="button" id="pv2-edit-minus" class="w-9 h-9 rounded-xl bg-gray-100 text-gray-700 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0">
|
||
<i class="fas fa-minus text-xs"></i>
|
||
</button>
|
||
<div class="flex items-baseline gap-0.5">
|
||
<input type="number" id="pv2-edit-qty" min="0" step="1" inputmode="decimal"
|
||
class="w-14 text-center text-lg font-bold tabular-nums bg-transparent outline-none" value="0" />
|
||
<span id="pv2-edit-unit" class="text-xs text-gray-400 font-medium"></span>
|
||
</div>
|
||
<button type="button" id="pv2-edit-plus" class="w-9 h-9 rounded-xl bg-gray-100 text-gray-700 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0">
|
||
<i class="fas fa-plus text-xs"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<div id="pv2-product-breakdown" class="shrink-0"></div>
|
||
|
||
<div class="border-t border-gray-100 shrink-0"></div>
|
||
|
||
<div class="shrink-0 space-y-1">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-[10px] font-semibold uppercase tracking-wider text-gray-400 w-[3.2rem] shrink-0">Lista</span>
|
||
<button type="button" id="pv2-shop-minus" class="w-9 h-9 rounded-xl bg-gray-100 text-gray-700 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0">
|
||
<i class="fas fa-minus text-xs"></i>
|
||
</button>
|
||
<div class="flex items-baseline gap-0.5">
|
||
<input type="number" id="pv2-shop-qty" min="1" step="1" inputmode="numeric"
|
||
class="w-14 text-center text-lg font-bold tabular-nums bg-transparent outline-none" value="1" />
|
||
<span id="pv2-shop-unit" class="text-xs text-gray-400 font-medium"></span>
|
||
</div>
|
||
<button type="button" id="pv2-shop-plus" class="w-9 h-9 rounded-xl bg-gray-100 text-gray-700 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0">
|
||
<i class="fas fa-plus text-xs"></i>
|
||
</button>
|
||
<button type="button" id="pv2-shop-add" class="ml-auto shrink-0 px-3.5 py-2 rounded-xl bg-gray-900 text-white text-[11px] font-semibold hover:bg-black transition-colors active:scale-95">
|
||
<i class="fas fa-cart-plus text-[9px] mr-1"></i>Dodaj
|
||
</button>
|
||
</div>
|
||
<p id="pv2-shop-hint" class="text-[10px] text-gray-400 pl-[3.5rem]"></p>
|
||
</div>
|
||
|
||
<div id="pv2-edit-nutrition" class="shrink-0"></div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ══════════════════════ CATEGORY CHIPS (multi-select) ══════════════════════ */
|
||
|
||
function allCategoryKeys() {
|
||
const s = new Set();
|
||
Object.values(INGREDIENTS).forEach(d => s.add(d.category));
|
||
return [...s].sort((a, b) => categoryLabel(a).localeCompare(categoryLabel(b)));
|
||
}
|
||
|
||
function renderCategoryChips() {
|
||
const wrap = document.getElementById('pantry-category-chips');
|
||
if (!wrap) return;
|
||
|
||
const keys = allCategoryKeys();
|
||
wrap.innerHTML = keys.map(k => {
|
||
const active = selectedCategories.has(k);
|
||
const icon = CATEGORY_ICONS[k] || 'fa-jar';
|
||
const cls = active
|
||
? 'shrink-0 inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full text-xs font-semibold bg-gray-900 text-white transition-colors'
|
||
: 'shrink-0 inline-flex items-center gap-1.5 px-3.5 py-2 rounded-full text-xs font-semibold bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors';
|
||
return `<button type="button" data-cat="${esc(k)}" class="pv2-cat-chip ${cls}"><i class="fas ${icon} text-[10px]"></i>${esc(categoryLabel(k))}</button>`;
|
||
}).join('');
|
||
|
||
wrap.querySelectorAll('.pv2-cat-chip').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const cat = btn.dataset.cat;
|
||
if (selectedCategories.has(cat)) selectedCategories.delete(cat);
|
||
else selectedCategories.add(cat);
|
||
renderCategoryChips();
|
||
renderBoard();
|
||
});
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════ BOARD RENDERING ══════════════════════ */
|
||
|
||
function getFilteredIds(searchRaw) {
|
||
const q = normalizeSearch(searchRaw);
|
||
return Object.keys(INGREDIENTS).filter(id => {
|
||
const d = INGREDIENTS[id];
|
||
if (selectedCategories.size > 0 && !selectedCategories.has(d.category)) return false;
|
||
if (!q) return true;
|
||
return d.name.toLowerCase().includes(q) || (CATEGORY_LABELS[d.category] || '').toLowerCase().includes(q);
|
||
}).sort((a, b) => INGREDIENTS[a].name.localeCompare(INGREDIENTS[b].name, 'pl'));
|
||
}
|
||
|
||
function chipHtml(id, pantry) {
|
||
const def = INGREDIENTS[id];
|
||
const qty = getPantryTotal(id, pantry);
|
||
const u = unitLabel(def.pantryUnit);
|
||
|
||
if (qty > 0) {
|
||
return `<button type="button" class="pv2-chip inline-flex flex-col items-start px-3.5 py-2.5 rounded-xl bg-emerald-50 border border-emerald-200/80 text-left hover:bg-emerald-100/80 transition-colors active:scale-[0.96]" data-id="${esc(id)}">
|
||
<span class="text-[13px] font-semibold text-gray-900 leading-tight whitespace-nowrap">${esc(def.name)}</span>
|
||
<span class="text-[11px] text-emerald-600 font-semibold tabular-nums leading-tight mt-0.5">${Math.round(qty)} ${esc(u)}</span>
|
||
</button>`;
|
||
}
|
||
|
||
return `<button type="button" class="pv2-chip inline-flex items-center px-3.5 py-2.5 rounded-xl border border-dashed border-gray-200 text-left hover:border-gray-300 hover:bg-white transition-colors active:scale-[0.96] group" data-id="${esc(id)}">
|
||
<span class="text-[13px] font-medium text-gray-400 group-hover:text-gray-600 whitespace-nowrap transition-colors">${esc(def.name)}</span>
|
||
<i class="fas fa-plus text-[8px] text-gray-300 group-hover:text-gray-500 ml-1.5 transition-colors"></i>
|
||
</button>`;
|
||
}
|
||
|
||
function groupByCategory(ids) {
|
||
/** @type {Map<string, string[]>} */
|
||
const groups = new Map();
|
||
for (const id of ids) {
|
||
const cat = INGREDIENTS[id].category;
|
||
if (!groups.has(cat)) groups.set(cat, []);
|
||
groups.get(cat).push(id);
|
||
}
|
||
return [...groups.keys()]
|
||
.sort((a, b) => categoryLabel(a).localeCompare(categoryLabel(b)))
|
||
.map(cat => ({ cat, ids: groups.get(cat) }));
|
||
}
|
||
|
||
function renderBoard() {
|
||
const root = document.getElementById('pantry-board');
|
||
if (!root) return;
|
||
|
||
const q = document.getElementById('pantry-search')?.value || '';
|
||
const pantry = loadPantry();
|
||
const allFiltered = getFilteredIds(q);
|
||
const visible = showOnlyStock
|
||
? allFiltered.filter(id => getPantryTotal(id, pantry) > 0)
|
||
: allFiltered;
|
||
|
||
if (visible.length === 0) {
|
||
root.innerHTML = showOnlyStock
|
||
? `<div class="flex flex-col items-center justify-center py-16 text-center">
|
||
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
|
||
<i class="fas fa-box-open text-2xl text-gray-300"></i>
|
||
</div>
|
||
<p class="text-sm font-semibold text-gray-700">Nic na stanie</p>
|
||
<p class="text-xs text-gray-500 mt-1 max-w-[220px] leading-relaxed">Wyłącz filtr, aby zobaczyć cały katalog produktów</p>
|
||
</div>`
|
||
: `<p class="text-sm text-gray-500 text-center py-10">Brak wyników — zmień wyszukiwanie lub filtry.</p>`;
|
||
return;
|
||
}
|
||
|
||
const groups = groupByCategory(visible);
|
||
let html = '';
|
||
for (const { cat, ids } of groups) {
|
||
const icon = CATEGORY_ICONS[cat] || 'fa-jar';
|
||
html += `
|
||
<div class="mb-4 last:mb-0">
|
||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 px-0.5">
|
||
<i class="fas ${icon} text-[10px] mr-1"></i>${esc(categoryLabel(cat))}
|
||
</p>
|
||
<div class="flex flex-wrap gap-2">${ids.map(id => chipHtml(id, pantry)).join('')}</div>
|
||
</div>`;
|
||
}
|
||
|
||
root.innerHTML = html;
|
||
|
||
root.querySelectorAll('.pv2-chip').forEach(btn => {
|
||
btn.addEventListener('click', () => openEditSheet(btn.dataset.id));
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════ STOCK TOGGLE ══════════════════════ */
|
||
|
||
function updateToggleVisuals() {
|
||
const btn = document.getElementById('pantry-stock-toggle');
|
||
if (!btn) return;
|
||
const thumb = btn.querySelector('span');
|
||
btn.setAttribute('aria-checked', String(showOnlyStock));
|
||
if (showOnlyStock) {
|
||
btn.classList.remove('bg-gray-200');
|
||
btn.classList.add('bg-emerald-500');
|
||
thumb?.classList.add('translate-x-[18px]');
|
||
} else {
|
||
btn.classList.add('bg-gray-200');
|
||
btn.classList.remove('bg-emerald-500');
|
||
thumb?.classList.remove('translate-x-[18px]');
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════ EDIT BOTTOM SHEET ══════════════════════ */
|
||
|
||
function openEditSheet(ingredientId) {
|
||
const def = INGREDIENTS[ingredientId];
|
||
if (!def) return;
|
||
editingId = ingredientId;
|
||
|
||
const pantry = loadPantry();
|
||
const qty = getPantryTotal(ingredientId, pantry);
|
||
const u = unitLabel(def.pantryUnit);
|
||
const step = pantryQtyStep(ingredientId);
|
||
const pack = def.purchasePack;
|
||
|
||
const nameEl = document.getElementById('pv2-edit-name');
|
||
if (nameEl) nameEl.textContent = def.name;
|
||
|
||
const metaEl = document.getElementById('pv2-edit-meta');
|
||
if (metaEl) {
|
||
let meta = categoryLabel(def.category);
|
||
if (pack) meta += ` · ${pack.label || `${pack.amount} ${u}`}`;
|
||
metaEl.textContent = meta;
|
||
}
|
||
|
||
const qtyEl = document.getElementById('pv2-edit-qty');
|
||
if (qtyEl) qtyEl.value = qty > 0 ? String(Math.round(qty)) : '0';
|
||
|
||
const unitEl = document.getElementById('pv2-edit-unit');
|
||
if (unitEl) unitEl.textContent = u;
|
||
|
||
editShopUsesPacks = Boolean(pack && pack.amount > 0);
|
||
editShopStep = editShopUsesPacks ? 1 : step;
|
||
|
||
const shopQtyEl = document.getElementById('pv2-shop-qty');
|
||
if (shopQtyEl) shopQtyEl.value = String(editShopStep);
|
||
|
||
const shopUnitEl = document.getElementById('pv2-shop-unit');
|
||
if (shopUnitEl) shopUnitEl.textContent = editShopUsesPacks ? 'opak.' : u;
|
||
|
||
const shopHintEl = document.getElementById('pv2-shop-hint');
|
||
if (shopHintEl) {
|
||
if (editShopUsesPacks) {
|
||
const lab = pack.label || `${pack.amount} ${u}`;
|
||
shopHintEl.textContent = `1 opak. = ${lab}`;
|
||
} else {
|
||
shopHintEl.textContent = '';
|
||
}
|
||
}
|
||
|
||
// Hide main +/- when products exist (total is sum of product rows)
|
||
const hasProds = ingredientHasProducts(ingredientId);
|
||
const mainMinus = document.getElementById('pv2-edit-minus');
|
||
const mainPlus = document.getElementById('pv2-edit-plus');
|
||
const mainQtyInput = document.getElementById('pv2-edit-qty');
|
||
if (mainMinus) mainMinus.classList.toggle('hidden', hasProds);
|
||
if (mainPlus) mainPlus.classList.toggle('hidden', hasProds);
|
||
if (mainQtyInput) mainQtyInput.readOnly = hasProds;
|
||
|
||
renderProductBreakdown(ingredientId, pantry);
|
||
renderNutritionInSheet(def);
|
||
|
||
const bg = document.getElementById('pv2-edit-bg');
|
||
const sheet = document.getElementById('pv2-edit-sheet');
|
||
if (!bg || !sheet) return;
|
||
bg.classList.remove('hidden');
|
||
sheet.classList.remove('hidden');
|
||
requestAnimationFrame(() => {
|
||
bg.classList.remove('opacity-0');
|
||
sheet.style.transform = 'translateY(0)';
|
||
});
|
||
}
|
||
|
||
function nutritionListRow(label, valueHtml) {
|
||
return `<li class="flex items-baseline justify-between gap-3 py-0.5 border-b border-gray-100/80 last:border-0">
|
||
<span class="text-gray-500 shrink-0">${esc(label)}</span>
|
||
<span class="text-right font-semibold tabular-nums text-gray-800">${valueHtml}</span>
|
||
</li>`;
|
||
}
|
||
|
||
function renderProductBreakdown(ingredientId, pantry) {
|
||
const wrap = document.getElementById('pv2-product-breakdown');
|
||
if (!wrap) return;
|
||
const products = getProductsForIngredient(ingredientId);
|
||
if (products.length === 0) { wrap.innerHTML = ''; return; }
|
||
|
||
const def = INGREDIENTS[ingredientId];
|
||
const u = unitLabel(def.pantryUnit);
|
||
const pantryProducts = getPantryProducts(ingredientId, pantry);
|
||
const generic = getPantryGeneric(ingredientId, pantry);
|
||
|
||
const productQty = (pid) => {
|
||
const item = pantryProducts.find(i => i.productId === pid);
|
||
return item ? item.qty : 0;
|
||
};
|
||
|
||
const rows = products.map(p => {
|
||
const q = Math.round(productQty(p.id));
|
||
return `<div class="flex items-center gap-1.5 py-1" data-product-row="${esc(p.id)}">
|
||
<span class="flex-1 text-[12px] text-gray-700 truncate" title="${esc(p.name)}">${esc(p.name)}</span>
|
||
<button type="button" class="pv2-prod-minus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="${esc(p.id)}" data-step="${p.packSize}">
|
||
<i class="fas fa-minus text-[9px]"></i>
|
||
</button>
|
||
<span class="pv2-prod-qty w-12 text-center text-[13px] font-semibold tabular-nums text-gray-800">${q} ${esc(u)}</span>
|
||
<button type="button" class="pv2-prod-plus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="${esc(p.id)}" data-step="${p.packSize}">
|
||
<i class="fas fa-plus text-[9px]"></i>
|
||
</button>
|
||
</div>`;
|
||
}).join('');
|
||
|
||
const genericRow = `<div class="flex items-center gap-1.5 py-1" data-product-row="_generic">
|
||
<span class="flex-1 text-[12px] text-gray-500 italic truncate">Nieokreślony</span>
|
||
<button type="button" class="pv2-prod-minus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="_generic" data-step="${pantryQtyStep(ingredientId)}">
|
||
<i class="fas fa-minus text-[9px]"></i>
|
||
</button>
|
||
<span class="pv2-prod-qty w-12 text-center text-[13px] font-semibold tabular-nums text-gray-800">${Math.round(generic)} ${esc(u)}</span>
|
||
<button type="button" class="pv2-prod-plus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="_generic" data-step="${pantryQtyStep(ingredientId)}">
|
||
<i class="fas fa-plus text-[9px]"></i>
|
||
</button>
|
||
</div>`;
|
||
|
||
wrap.innerHTML = `
|
||
<div class="space-y-0.5 px-0.5">
|
||
<p class="text-[9px] font-semibold uppercase tracking-wide text-gray-400 mb-1">Produkty</p>
|
||
${rows}
|
||
${genericRow}
|
||
</div>`;
|
||
|
||
// Bind product +/- buttons
|
||
wrap.querySelectorAll('.pv2-prod-plus, .pv2-prod-minus').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
if (!editingId) return;
|
||
const pid = btn.dataset.pid;
|
||
const step = Number(btn.dataset.step) || 1;
|
||
const isPlus = btn.classList.contains('pv2-prod-plus');
|
||
const pantry = loadPantry();
|
||
|
||
if (pid === '_generic') {
|
||
const cur = getPantryGeneric(editingId, pantry);
|
||
const next = Math.max(0, cur + (isPlus ? step : -step));
|
||
setPantryQty(editingId, next);
|
||
} else {
|
||
const items = getPantryProducts(editingId, pantry);
|
||
const cur = items.find(i => i.productId === pid)?.qty || 0;
|
||
const next = Math.max(0, cur + (isPlus ? step : -step));
|
||
setPantryProductQty(editingId, pid, next);
|
||
}
|
||
|
||
// Re-render breakdown and total
|
||
const freshPantry = loadPantry();
|
||
renderProductBreakdown(editingId, freshPantry);
|
||
const totalQty = getPantryTotal(editingId, freshPantry);
|
||
setEditQty(totalQty);
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderNutritionInSheet(def) {
|
||
const wrap = document.getElementById('pv2-edit-nutrition');
|
||
if (!wrap) return;
|
||
const n = def.nutritionPer100g;
|
||
if (!n) { wrap.innerHTML = ''; return; }
|
||
|
||
const refLabel = def.pantryUnit === 'ml' ? '100 ml produktu' : '100 g produktu';
|
||
wrap.innerHTML = `
|
||
<div class="text-[10px] leading-snug mt-0.5 pt-2 border-t border-gray-100 space-y-1">
|
||
<p class="text-[9px] font-semibold uppercase tracking-wide text-gray-500 px-0.5">${esc(refLabel)}</p>
|
||
<ul class="space-y-0 rounded-lg bg-white/70 px-2 py-1 ring-1 ring-gray-100/90">
|
||
${nutritionListRow('Energia', `${n.kcal} kcal`)}
|
||
${nutritionListRow('Białko', `${n.protein} g`)}
|
||
${nutritionListRow('Tłuszcz', `${n.fat} g`)}
|
||
${nutritionListRow('Węglowodany', `${n.carbs} g`)}
|
||
</ul>
|
||
</div>`;
|
||
}
|
||
|
||
function closeEditSheet() {
|
||
editingId = null;
|
||
const bg = document.getElementById('pv2-edit-bg');
|
||
const sheet = document.getElementById('pv2-edit-sheet');
|
||
if (sheet) sheet.style.transform = HIDDEN_Y;
|
||
if (bg) bg.classList.add('opacity-0');
|
||
setTimeout(() => {
|
||
bg?.classList.add('hidden');
|
||
sheet?.classList.add('hidden');
|
||
}, 300);
|
||
renderBoard();
|
||
}
|
||
|
||
function getEditQty() {
|
||
const el = document.getElementById('pv2-edit-qty');
|
||
return Math.max(0, parseFloat(String(el?.value).replace(',', '.')) || 0);
|
||
}
|
||
|
||
function setEditQty(v) {
|
||
const el = document.getElementById('pv2-edit-qty');
|
||
if (el) el.value = String(Math.max(0, Math.round(v)));
|
||
}
|
||
|
||
function applyEditQty(newQty) {
|
||
if (!editingId) return;
|
||
const v = Math.max(0, Math.round(Number(newQty) * 1000) / 1000 || 0);
|
||
setPantryQty(editingId, v);
|
||
setEditQty(v);
|
||
}
|
||
|
||
function readShopQty() {
|
||
const el = document.getElementById('pv2-shop-qty');
|
||
return Math.max(1, Math.round(parseFloat(String(el?.value).replace(',', '.')) || 0));
|
||
}
|
||
|
||
function setShopQty(v) {
|
||
const el = document.getElementById('pv2-shop-qty');
|
||
if (el) el.value = String(Math.max(1, Math.round(Number(v))));
|
||
}
|
||
|
||
function bindEditSheet() {
|
||
document.getElementById('pv2-edit-bg')?.addEventListener('click', closeEditSheet);
|
||
|
||
document.getElementById('pv2-edit-minus')?.addEventListener('click', () => {
|
||
if (!editingId) return;
|
||
applyEditQty(Math.max(0, getEditQty() - pantryQtyStep(editingId)));
|
||
});
|
||
|
||
document.getElementById('pv2-edit-plus')?.addEventListener('click', () => {
|
||
if (!editingId) return;
|
||
applyEditQty(getEditQty() + pantryQtyStep(editingId));
|
||
});
|
||
|
||
document.getElementById('pv2-edit-qty')?.addEventListener('change', () => {
|
||
applyEditQty(getEditQty());
|
||
});
|
||
|
||
document.getElementById('pv2-shop-minus')?.addEventListener('click', () => {
|
||
setShopQty(Math.max(1, readShopQty() - editShopStep));
|
||
});
|
||
|
||
document.getElementById('pv2-shop-plus')?.addEventListener('click', () => {
|
||
setShopQty(readShopQty() + editShopStep);
|
||
});
|
||
|
||
document.getElementById('pv2-shop-qty')?.addEventListener('change', () => {
|
||
setShopQty(readShopQty());
|
||
});
|
||
|
||
document.getElementById('pv2-shop-add')?.addEventListener('click', () => {
|
||
if (!editingId) return;
|
||
const def = INGREDIENTS[editingId];
|
||
if (!def) return;
|
||
const count = readShopQty();
|
||
const u = unitLabel(def.pantryUnit);
|
||
|
||
if (editShopUsesPacks && def.purchasePack) {
|
||
const packAmt = def.purchasePack.amount;
|
||
const total = count * packAmt;
|
||
const note = `${count}× ${def.purchasePack.label || `${packAmt} ${u}`}`;
|
||
addIngredientToKitchenList(editingId, total, note);
|
||
showAppToast(`Dodano ${count} op. (${total} ${u}) na listę.`);
|
||
} else {
|
||
addIngredientToKitchenList(editingId, count);
|
||
showAppToast(`Dodano ${count} ${u} na listę.`);
|
||
}
|
||
window.refreshShopping?.();
|
||
});
|
||
}
|
||
|
||
/* ══════════════════════ PUBLIC API ══════════════════════ */
|
||
|
||
export function refreshPantry() {
|
||
renderCategoryChips();
|
||
renderBoard();
|
||
}
|
||
|
||
export function setupPantry() {
|
||
renderCategoryChips();
|
||
renderBoard();
|
||
bindEditSheet();
|
||
|
||
document.getElementById('pantry-search')?.addEventListener('input', () => renderBoard());
|
||
|
||
document.getElementById('pantry-stock-toggle')?.addEventListener('click', () => {
|
||
showOnlyStock = !showOnlyStock;
|
||
updateToggleVisuals();
|
||
renderBoard();
|
||
});
|
||
|
||
window.refreshPantry = refreshPantry;
|
||
}
|