import { INGREDIENTS, CATEGORY_LABELS, PRODUCTS, pantryQtyStep, getProductsForIngredient, ingredientHasProducts, } from '../data/catalog.js?v=8'; 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, '"'); } 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', }; const SEARCH_SHELL_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)'; /* ── state ── */ let showOnlyStock = false; let editingId = null; /** @type {Set} */ 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 ` `; } /* ══════════════════════ FILTER POPUP ══════════════════════ */ 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))); } let filterCloseTimer = null; function renderFilterCategories() { const wrap = document.getElementById('pantry-filter-categories'); if (!wrap) return; const keys = allCategoryKeys(); wrap.innerHTML = keys.map(k => { const active = selectedCategories.has(k); const icon = CATEGORY_ICONS[k] || 'fa-jar'; const bg = active ? '#23221e' : '#2f2f2d'; const border = active ? '#787876' : '#444442'; const text = active ? '#f2efe8' : '#d7d2c8'; return ``; }).join(''); } function renderFilterStockCheck() { const el = document.getElementById('pantry-filter-stock-check'); if (!el) return; el.innerHTML = showOnlyStock ? '' : ''; el.style.background = showOnlyStock ? '#23221e' : 'transparent'; el.style.borderColor = showOnlyStock ? '#787876' : '#56534f'; } function updateFilterBadge() { const btn = document.getElementById('pantry-filter-btn'); if (!btn) return; const count = selectedCategories.size + (showOnlyStock ? 1 : 0); btn.style.color = count > 0 ? '#6ee7b7' : '#c9c3b8'; } function positionFilterPanel() { const panel = document.getElementById('pantry-filter-panel'); const shell = document.getElementById('pantry-search-shell'); const view = document.getElementById('pantry-view'); if (!panel || !shell || !view) return; const viewRect = view.getBoundingClientRect(); const shellRect = shell.getBoundingClientRect(); const gap = 8; const margin = 12; const width = Math.min(shellRect.width, viewRect.width - margin * 2); const top = shellRect.bottom - viewRect.top + gap; const left = Math.max(margin, Math.min(shellRect.left - viewRect.left, viewRect.width - width - margin)); panel.style.width = `${width}px`; panel.style.left = `${left}px`; panel.style.top = `${top}px`; } function openFilterPopup() { const overlay = document.getElementById('pantry-filter-overlay'); const panel = document.getElementById('pantry-filter-panel'); if (!overlay || !panel) return; clearTimeout(filterCloseTimer); renderFilterCategories(); renderFilterStockCheck(); positionFilterPanel(); overlay.classList.remove('hidden'); overlay.style.pointerEvents = 'auto'; requestAnimationFrame(() => { overlay.classList.add('opacity-100'); panel.style.opacity = '1'; panel.style.transform = 'translateY(0) scale(1)'; }); } function closeFilterPopup() { const overlay = document.getElementById('pantry-filter-overlay'); const panel = document.getElementById('pantry-filter-panel'); if (!overlay || !panel) return; overlay.classList.remove('opacity-100'); overlay.style.pointerEvents = 'none'; panel.style.opacity = '0'; panel.style.transform = 'translateY(-0.5rem) scale(0.98)'; filterCloseTimer = setTimeout(() => overlay.classList.add('hidden'), 180); } function isFilterOpen() { return !document.getElementById('pantry-filter-overlay')?.classList.contains('hidden'); } // Keep old name for refreshPantry compatibility function renderCategoryChips() { updateFilterBadge(); } /* ══════════════════════ 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 rowHtml(id, pantry) { const def = INGREDIENTS[id]; const qty = getPantryTotal(id, pantry); const u = unitLabel(def.pantryUnit); const hasStock = qty > 0; const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; const products = getProductsForIngredient(id); const productCount = products.length; const avatar = def.image ? `` : `
`; const qtyColor = hasStock ? '#6ee7b7' : '#6d6c67'; const qtyText = hasStock ? `${Math.round(qty)} ${esc(u)}` : `0 ${esc(u)}`; let meta = esc(categoryLabel(def.category)); if (productCount > 0) meta += ` · ${productCount} ${productCount === 1 ? 'produkt' : productCount < 5 ? 'produkty' : 'produktów'}`; return ``; } function groupByCategory(ids) { /** @type {Map} */ 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 ? `

Nic na stanie

Wyłącz filtr, aby zobaczyć cały katalog produktów

` : `

Brak wyników — zmień wyszukiwanie lub filtry.

`; return; } const groups = groupByCategory(visible); let html = ''; for (const { cat, ids } of groups) { const icon = CATEGORY_ICONS[cat] || 'fa-jar'; html += `

${esc(categoryLabel(cat))}

${ids.map(id => rowHtml(id, pantry)).join('')}
`; } root.innerHTML = html; root.querySelectorAll('.pv2-chip').forEach(btn => { btn.addEventListener('click', () => openIngredientCard(btn.dataset.id)); }); } /* ══════════════════════ STOCK TOGGLE ══════════════════════ */ /* ══════════════════════ INGREDIENT CARD ══════════════════════ */ function openIngredientCard(ingredientId) { const def = INGREDIENTS[ingredientId]; if (!def) return; editingId = ingredientId; const pantry = loadPantry(); const qty = getPantryTotal(ingredientId, pantry); const u = unitLabel(def.pantryUnit); const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; // Hero image const img = document.getElementById('pv2-card-img'); const fallback = document.getElementById('pv2-card-fallback'); const fallbackIcon = document.getElementById('pv2-card-fallback-icon'); if (def.image) { img.src = def.image; img.alt = def.name; img.classList.remove('hidden'); fallback.classList.add('hidden'); } else { img.classList.add('hidden'); fallback.classList.remove('hidden'); if (fallbackIcon) fallbackIcon.className = `fas ${icon} text-3xl`; } // Header document.getElementById('pv2-card-category').textContent = categoryLabel(def.category); document.getElementById('pv2-card-name').textContent = def.name; const packEl = document.getElementById('pv2-card-pack'); if (def.purchasePack) { packEl.textContent = def.purchasePack.label || `${def.purchasePack.amount} ${u}`; packEl.classList.remove('hidden'); } else { packEl.classList.add('hidden'); } // Nutrition renderCardNutrition(def); // Stock renderCardStock(ingredientId, pantry); // Shopping renderCardShop(ingredientId); // Show const overlay = document.getElementById('pv2-card-overlay'); if (overlay) { overlay.classList.remove('hidden'); overlay.style.pointerEvents = 'auto'; } } function closeIngredientCard() { const overlay = document.getElementById('pv2-card-overlay'); if (overlay) { overlay.classList.add('hidden'); overlay.style.pointerEvents = 'none'; } editingId = null; renderBoard(); } function renderCardNutrition(def) { const wrap = document.getElementById('pv2-card-nutrition'); if (!wrap) return; const n = def.nutritionPer100g; if (!n) { wrap.innerHTML = ''; return; } const label = def.pantryUnit === 'ml' ? 'na 100 ml' : 'na 100 g'; wrap.innerHTML = `

${esc(label)}

${n.kcal}

kcal

${n.protein}g

białko

${n.fat}g

tłuszcz

${n.carbs}g

węgl.

`; } function renderCardStock(ingredientId, pantry) { const wrap = document.getElementById('pv2-card-stock-section'); if (!wrap) return; const def = INGREDIENTS[ingredientId]; if (!def) return; const u = unitLabel(def.pantryUnit); const qty = getPantryTotal(ingredientId, pantry); const products = getProductsForIngredient(ingredientId); const hasProds = products.length > 0; let html = `

Zapas

`; if (hasProds) { const pantryProducts = getPantryProducts(ingredientId, pantry); const generic = getPantryGeneric(ingredientId, pantry); const productQty = (pid) => pantryProducts.find(i => i.productId === pid)?.qty || 0; html += `
`; html += `
Łącznie${Math.round(qty)} ${esc(u)}
`; html += `
`; for (const p of products) { const q = Math.round(productQty(p.id)); html += `
${esc(p.name)} ${q} ${esc(u)}
`; } html += `
Nieokreślony ${Math.round(generic)} ${esc(u)}
`; html += `
`; } else { const step = pantryQtyStep(ingredientId); html += `
${Math.round(qty)} ${esc(u)}
`; } wrap.innerHTML = html; // Bind stock buttons wrap.querySelectorAll('.pv2-stock-btn').forEach(btn => { btn.addEventListener('click', () => { if (!editingId) return; const pid = btn.dataset.pid; const step = Number(btn.dataset.step) || 1; const dir = Number(btn.dataset.dir); const p = loadPantry(); if (pid === '_generic') { const cur = getPantryGeneric(editingId, p); setPantryQty(editingId, Math.max(0, cur + step * dir)); } else { const items = getPantryProducts(editingId, p); const cur = items.find(i => i.productId === pid)?.qty || 0; setPantryProductQty(editingId, pid, Math.max(0, cur + step * dir)); } renderCardStock(editingId, loadPantry()); }); }); } function renderCardShop(ingredientId) { const wrap = document.getElementById('pv2-card-shop-section'); if (!wrap) return; const def = INGREDIENTS[ingredientId]; if (!def) return; const u = unitLabel(def.pantryUnit); const pack = def.purchasePack; const usesPacks = Boolean(pack && pack.amount > 0); const hint = usesPacks ? `1 opak. = ${pack.label || `${pack.amount} ${u}`}` : ''; wrap.innerHTML = ` ${hint ? `

${esc(hint)}

` : ''}`; document.getElementById('pv2-card-add-list')?.addEventListener('click', () => { if (!editingId) return; const d = INGREDIENTS[editingId]; if (!d) return; const uLabel = unitLabel(d.pantryUnit); if (usesPacks && d.purchasePack) { const amt = d.purchasePack.amount; addIngredientToKitchenList(editingId, amt, d.purchasePack.label || `${amt} ${uLabel}`); showAppToast(`Dodano 1 opak. na listę.`); } else { const step = pantryQtyStep(editingId); addIngredientToKitchenList(editingId, step); showAppToast(`Dodano ${step} ${uLabel} na listę.`); } window.refreshShopping?.(); }); } function bindEditSheet() { document.getElementById('pv2-card-close')?.addEventListener('click', closeIngredientCard); document.getElementById('pv2-card-overlay')?.addEventListener('click', (e) => { if (e.target.id === 'pv2-card-overlay') closeIngredientCard(); }); } /* ══════════════════════ PUBLIC API ══════════════════════ */ export function refreshPantry() { renderCategoryChips(); renderBoard(); } export function setupPantry() { updateFilterBadge(); renderBoard(); bindEditSheet(); document.getElementById('pantry-search')?.addEventListener('input', () => renderBoard()); // Filter popup document.getElementById('pantry-filter-btn')?.addEventListener('click', () => { if (isFilterOpen()) closeFilterPopup(); else openFilterPopup(); }); document.getElementById('pantry-filter-overlay')?.addEventListener('click', (e) => { if (e.target.id === 'pantry-filter-overlay') closeFilterPopup(); }); document.getElementById('pantry-filter-clear')?.addEventListener('click', () => { selectedCategories.clear(); showOnlyStock = false; renderFilterCategories(); renderFilterStockCheck(); updateFilterBadge(); renderBoard(); }); document.getElementById('pantry-filter-categories')?.addEventListener('click', (e) => { const btn = e.target.closest('.pv2-filter-cat'); if (!btn) return; const cat = btn.dataset.cat; if (selectedCategories.has(cat)) selectedCategories.delete(cat); else selectedCategories.add(cat); renderFilterCategories(); updateFilterBadge(); renderBoard(); }); document.getElementById('pantry-filter-stock')?.addEventListener('click', () => { showOnlyStock = !showOnlyStock; renderFilterStockCheck(); updateFilterBadge(); renderBoard(); }); window.refreshPantry = refreshPantry; }