import { INGREDIENTS, CATEGORY_LABELS, PRODUCTS, pantryQtyStep, getProductsForIngredient, ingredientHasProducts, } from '../data/catalog.js?v=8'; import { addIngredientToKitchenList, addOrMergeShoppingLines, categoryLabel, loadPantry, setPantryQty, setPantryProductQty, getPantryTotal, getPantryProducts } 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 ingredientRowHtml(id, pantry) { const def = INGREDIENTS[id]; const products = getProductsForIngredient(id); const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; const u = unitLabel(def.pantryUnit); if (products.length === 0) { // Simple row — no products const qty = getPantryTotal(id, pantry); const qtyColor = qty > 0 ? '#6ee7b7' : '#6d6c67'; const avatar = def.image ? `` : `
`; return ``; } // Group — ingredient header + product rows const totalQty = getPantryTotal(id, pantry); const pantryItems = getPantryProducts(id, pantry); const totalColor = totalQty > 0 ? '#6ee7b7' : '#6d6c67'; let html = `
`; // Group header html += `
`; const avatar = def.image ? `` : `
`; html += avatar; html += `
${esc(def.name)}
`; html += `${totalQty > 0 ? Math.round(totalQty) : 0} ${esc(u)}`; html += `
`; // Product rows for (const p of products) { const pQty = pantryItems.find(i => i.productId === p.id)?.qty || 0; const pQtyColor = pQty > 0 ? '#ddd6ca' : '#6d6c67'; const pAvatar = p.image ? `` : `
`; html += ``; } html += `
`; return html; } 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 => ingredientRowHtml(id, pantry)).join('')}
`; } root.innerHTML = html; root.querySelectorAll('.pv2-chip').forEach(btn => { btn.addEventListener('click', () => openIngredientCard(btn.dataset.id, null)); }); root.querySelectorAll('.pv2-product-row').forEach(btn => { btn.addEventListener('click', () => openIngredientCard(btn.dataset.id, btn.dataset.productId)); }); } /* ══════════════════════ STOCK TOGGLE ══════════════════════ */ /* ══════════════════════ INGREDIENT CARD ══════════════════════ */ let editingProductId = null; function openIngredientCard(ingredientId, productId) { const def = INGREDIENTS[ingredientId]; if (!def) return; editingId = ingredientId; editingProductId = productId || null; const product = editingProductId ? PRODUCTS[editingProductId] : null; const pantry = loadPantry(); const u = unitLabel(def.pantryUnit); const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; // Hero image — product image > ingredient image > fallback const image = product?.image || def.image; const img = document.getElementById('pv2-card-img'); const fallback = document.getElementById('pv2-card-fallback'); const fallbackIcon = document.getElementById('pv2-card-fallback-icon'); if (image) { img.src = image; img.alt = product?.name || 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 — show product info or ingredient info document.getElementById('pv2-card-category').textContent = product?.brand || categoryLabel(def.category); document.getElementById('pv2-card-name').textContent = product?.name || def.name; const packEl = document.getElementById('pv2-card-pack'); const packLabel = product?.packLabel || def.purchasePack?.label; if (packLabel) { packEl.textContent = packLabel; packEl.classList.remove('hidden'); } else { packEl.classList.add('hidden'); } // Nutrition — use product values if available renderCardNutrition(def, product); // Stock renderCardStock(ingredientId, editingProductId, pantry); // Shopping renderCardShop(ingredientId, editingProductId); // 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, product) { const wrap = document.getElementById('pv2-card-nutrition'); if (!wrap) return; const n = product?.nutritionPer100g || 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, productId, pantry) { const wrap = document.getElementById('pv2-card-stock-section'); if (!wrap) return; const def = INGREDIENTS[ingredientId]; if (!def) return; const u = unitLabel(def.pantryUnit); let html = `

Zapas

`; if (productId) { // Product card — show just this product's stock const product = PRODUCTS[productId]; const pantryItems = getPantryProducts(ingredientId, pantry); const qty = pantryItems.find(i => i.productId === productId)?.qty || 0; const step = product?.packSize || pantryQtyStep(ingredientId); html += `
${Math.round(qty)} ${esc(u)}
`; } else { // Generic ingredient — simple +/- const qty = getPantryTotal(ingredientId, pantry); const step = pantryQtyStep(ingredientId); html += `
${Math.round(qty)} ${esc(u)}
`; } wrap.innerHTML = html; 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 = getPantryTotal(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, editingProductId, loadPantry()); }); }); } function renderCardShop(ingredientId, productId) { const wrap = document.getElementById('pv2-card-shop-section'); if (!wrap) return; const def = INGREDIENTS[ingredientId]; if (!def) return; const u = unitLabel(def.pantryUnit); const product = productId ? PRODUCTS[productId] : null; const packSize = product?.packSize || def.purchasePack?.amount; const packLabel = product?.packLabel || def.purchasePack?.label; const usesPacks = Boolean(packSize && packSize > 0); const btnLabel = usesPacks ? `Dodaj na listę (${packLabel || `${packSize} ${u}`})` : 'Dodaj na listę'; wrap.innerHTML = ` `; document.getElementById('pv2-card-add-list')?.addEventListener('click', () => { if (!editingId) return; const d = INGREDIENTS[editingId]; if (!d) return; const uLabel = unitLabel(d.pantryUnit); const amt = usesPacks ? packSize : pantryQtyStep(editingId); const note = usesPacks ? (packLabel || `${packSize} ${uLabel}`) : undefined; // Use addOrMergeShoppingLines to include productId const line = { ingredientId: editingId, amount: amt, unit: uLabel, name: product?.name || d.name, category: d.category, sourceNote: note || 'Ze spiżarni', }; if (productId) line.productId = productId; addOrMergeShoppingLines([line]); showAppToast(`Dodano ${product?.name || d.name} 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; }