import { INGREDIENTS, PRODUCTS, CATEGORY_LABELS, getProductsForIngredient, ingredientHasProducts, pantryQtyStep, } from '../data/catalog.js?v=8'; import { addOrMergeShoppingLines, loadPantry, setPantryQty, setPantryProductQty, getPantryTotal, getPantryProducts, } from '../services/pantryShopping.js?v=2'; import { showAppToast } from './toast.js'; 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', }; function esc(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function unitLabel(u) { return u === 'szt' ? 'szt.' : u; } function formatQty(n) { const rounded = Math.round((Number(n) || 0) * 10) / 10; return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1).replace(/\.0$/, ''); } function formatQtyWithUnit(qty, unit) { return `${formatQty(qty)} ${unitLabel(unit)}`; } function productCountLabel(count) { if (count === 1) return '1 produkt'; const mod10 = count % 10; const mod100 = count % 100; if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return `${count} produkty`; return `${count} produktów`; } function nutritionForQty(def, qty, nutrition = def?.nutritionPer100g) { if (!def || !nutrition || !Number.isFinite(qty) || qty <= 0) return null; let grams = qty; if (def.pantryUnit === 'szt' && def.weightPerPiece) grams = qty * def.weightPerPiece; const factor = grams / 100; return { kcal: Math.round(nutrition.kcal * factor), protein: Math.round(nutrition.protein * factor * 10) / 10, fat: Math.round(nutrition.fat * factor * 10) / 10, carbs: Math.round(nutrition.carbs * factor * 10) / 10, }; } function macroLine(n) { if (!n) return ''; return `${n.kcal} kcal · ${formatQty(n.protein)}g B · ${formatQty(n.fat)}g T · ${formatQty(n.carbs)}g W`; } function mediaHtml(image, icon, sizeClass = 'w-9 h-9', radiusClass = 'rounded-lg') { if (image) { return ``; } return `
`; } function compactMetaText(text, tone = 'default') { const color = tone === 'success' ? '#6ee7b7' : tone === 'muted' ? '#9b978f' : '#d7d2c8'; return `${esc(text)}`; } function sortProductsByStock(products, pantryItems) { return [...products].sort((a, b) => { const aq = pantryItems.find((i) => i.productId === a.id)?.qty || 0; const bq = pantryItems.find((i) => i.productId === b.id)?.qty || 0; if (bq !== aq) return bq - aq; return a.name.localeCompare(b.name, 'pl'); }); } export function getIngredientCardHTML({ idBase, overlayClass = 'fixed inset-0 z-[70] hidden opacity-0 transition-opacity duration-200 flex items-center justify-center p-5', overlayStyle = 'pointer-events:none; background:rgba(0,0,0,0.5);', cardClass = 'relative w-full max-w-xs rounded-2xl shadow-2xl overflow-hidden', cardStyle = 'background:#2d2e2b; pointer-events:auto; max-height:85vh; overflow-y:auto; transform:translateY(0.75rem); opacity:0; transition:transform 220ms ease, opacity 220ms ease;', heroHeightClass = 'h-[180px]', } = {}) { if (!idBase) throw new Error('getIngredientCardHTML requires idBase'); return `

`; } export function createIngredientCardController({ idBase, defaultSourceNote = 'Ze spiżarni' } = {}) { if (!idBase) throw new Error('createIngredientCardController requires idBase'); const state = { ingredientId: null, productId: null, selectedProductId: null, sourceNote: defaultSourceNote, onProductChange: null, onAfterChange: null, closeTimer: null, }; let bound = false; const el = (suffix = '') => document.getElementById(suffix ? `${idBase}-${suffix}` : idBase); function renderHeader(def, product, pantry) { const hasProducts = ingredientHasProducts(def.id); const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; const image = product?.image || def.image; const img = el('img'); const fallback = el('fallback'); const fallbackIcon = el('fallback-icon'); if (img && fallback) { 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`; } } const totalQty = getPantryTotal(def.id, pantry); const categoryEl = el('category'); const nameEl = el('name'); const subtitleEl = el('subtitle'); const backBtn = el('back'); if (categoryEl) categoryEl.textContent = product?.brand || CATEGORY_LABELS[def.category] || def.category; if (nameEl) nameEl.textContent = product?.name || def.name; if (subtitleEl) { let subtitle = ''; if (product) { subtitle = [def.name, product.packLabel].filter(Boolean).join(' • '); } else if (hasProducts) { subtitle = `${productCountLabel(getProductsForIngredient(def.id).length)} • ${formatQtyWithUnit(totalQty, def.pantryUnit)} na stanie`; } else if (def.purchasePack?.label) { subtitle = def.purchasePack.label; } if (subtitle) { subtitleEl.textContent = subtitle; subtitleEl.classList.remove('hidden'); } else { subtitleEl.classList.add('hidden'); } } if (backBtn) { backBtn.classList.toggle('hidden', !(hasProducts && state.productId)); } } function renderNutrition(def, product) { const wrap = el('nutrition'); if (!wrap) return; const nutrition = product?.nutritionPer100g || def.nutritionPer100g; if (!nutrition) { wrap.innerHTML = ''; return; } const hasProducts = ingredientHasProducts(def.id); const unitScope = def.pantryUnit === 'ml' ? 'na 100 ml' : 'na 100 g'; const hint = product ? 'dokładne dla produktu' : hasProducts ? 'orientacyjnie dla składnika' : 'bazowe wartości'; wrap.innerHTML = `

Wartości odżywcze

${esc(unitScope)} • ${esc(hint)}

${nutrition.kcal}

kcal

${formatQty(nutrition.protein)}g

białko

${formatQty(nutrition.fat)}g

tłuszcz

${formatQty(nutrition.carbs)}g

węgl.

`; } function renderStock() { const wrap = el('stock'); if (!wrap || !state.ingredientId) return; const def = INGREDIENTS[state.ingredientId]; if (!def) return; const pantry = loadPantry(); const hasProducts = ingredientHasProducts(state.ingredientId); const product = state.productId ? PRODUCTS[state.productId] : null; const totalQty = getPantryTotal(state.ingredientId, pantry); const unit = unitLabel(def.pantryUnit); if (hasProducts && !product) { const stockedCount = getPantryProducts(state.ingredientId, pantry).filter((i) => i.qty > 0).length; const summaryNutrition = nutritionForQty(def, totalQty); wrap.innerHTML = `

Zapas

${esc(formatQty(totalQty))} ${esc(unit)}

${stockedCount} z ${getProductsForIngredient(state.ingredientId).length} produktów ma stan

Wybierz produkt niżej, aby zmienić stan
${summaryNutrition ? `

${esc(macroLine(summaryNutrition))}

` : ''}
`; return; } const qty = product ? (getPantryProducts(state.ingredientId, pantry).find((i) => i.productId === state.productId)?.qty || 0) : totalQty; const step = product ? (product.packSize || pantryQtyStep(state.ingredientId)) : pantryQtyStep(state.ingredientId); const stockNutrition = nutritionForQty(def, qty, product?.nutritionPer100g || def.nutritionPer100g); wrap.innerHTML = `

Zapas

${esc(formatQty(qty))} ${esc(unit)}

Krok: ${esc(formatQty(step))} ${esc(unit)}

${stockNutrition ? `

${esc(macroLine(stockNutrition))} na stanie

` : ''}`; wrap.querySelectorAll('.ingredient-card-stock-btn').forEach((btn) => { btn.addEventListener('click', () => { const pid = btn.dataset.pid; const stepVal = Number(btn.dataset.step) || 1; const dir = Number(btn.dataset.dir) || 1; const pantryState = loadPantry(); if (pid === '_generic') { const current = getPantryTotal(state.ingredientId, pantryState); setPantryQty(state.ingredientId, Math.max(0, current + stepVal * dir)); } else { const items = getPantryProducts(state.ingredientId, pantryState); const current = items.find((i) => i.productId === pid)?.qty || 0; setPantryProductQty(state.ingredientId, pid, Math.max(0, current + stepVal * dir)); } render(); state.onAfterChange?.(); }); }); } function productRowHtml(ingredientId, productId, pantry, selectedProductId) { const def = INGREDIENTS[ingredientId]; const product = PRODUCTS[productId]; const icon = CATEGORY_ICONS[def.category] || 'fa-jar'; const qty = getPantryProducts(ingredientId, pantry).find((i) => i.productId === productId)?.qty || 0; const isSelected = selectedProductId === productId; return ``; } function renderProducts() { const wrap = el('products'); if (!wrap || !state.ingredientId) return; if (!ingredientHasProducts(state.ingredientId)) { wrap.innerHTML = ''; return; } const pantry = loadPantry(); const products = sortProductsByStock(getProductsForIngredient(state.ingredientId), getPantryProducts(state.ingredientId, pantry)); const selectedProductId = state.selectedProductId || state.productId; const subtitle = state.productId ? 'Wróć lub wybierz inny wariant.' : 'Wybierz wariant, aby zobaczyć szczegóły.'; wrap.innerHTML = `

Produkty

${esc(subtitle)}

${products.map((product) => productRowHtml(state.ingredientId, product.id, pantry, selectedProductId)).join('')}
`; wrap.querySelectorAll('.ingredient-card-product-row').forEach((btn) => { btn.addEventListener('click', () => { const nextProductId = btn.dataset.productId || null; state.productId = nextProductId; state.selectedProductId = nextProductId; if (nextProductId) state.onProductChange?.(nextProductId); render(); state.onAfterChange?.(); }); }); } function renderShop() { const wrap = el('shop'); if (!wrap || !state.ingredientId) return; const def = INGREDIENTS[state.ingredientId]; if (!def) return; const hasProducts = ingredientHasProducts(state.ingredientId); const product = state.productId ? PRODUCTS[state.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 || `${formatQty(packSize)} ${unitLabel(def.pantryUnit)}`})` : 'Dodaj na listę'; const helperText = hasProducts && !product ? 'Doda składnik bez wskazanej marki. Jeśli chcesz konkretny produkt, wybierz go wyżej.' : product ? 'Pozycja trafi na listę zakupów z dokładnym produktem.' : `Szybki skrót do listy zakupów ${defaultSourceNote === 'Ze spiżarni' ? 'ze spiżarni' : 'z planera'}.`; wrap.innerHTML = `

Lista zakupów

${esc(helperText)}

`; wrap.querySelector('.ingredient-card-add-list')?.addEventListener('click', () => { const amount = usesPacks ? packSize : pantryQtyStep(state.ingredientId); const note = usesPacks ? (packLabel || `${formatQty(packSize)} ${unitLabel(def.pantryUnit)}`) : undefined; const line = { ingredientId: state.ingredientId, amount, unit: unitLabel(def.pantryUnit), name: product?.name || def.name, category: def.category, sourceNote: note || state.sourceNote || defaultSourceNote, }; if (state.productId) line.productId = state.productId; addOrMergeShoppingLines([line]); showAppToast(`Dodano ${product?.name || def.name} na listę.`); window.refreshShopping?.(); }); } function render() { if (!state.ingredientId) return; const def = INGREDIENTS[state.ingredientId]; if (!def) return; const product = state.productId ? PRODUCTS[state.productId] : null; const pantry = loadPantry(); renderHeader(def, product, pantry); renderNutrition(def, product); renderStock(); renderProducts(); renderShop(); } function open({ ingredientId, productId = null, selectedProductId = productId, sourceNote = defaultSourceNote, onProductChange = null, onAfterChange = null, } = {}) { const def = ingredientId ? INGREDIENTS[ingredientId] : null; const overlay = el('overlay'); const card = el(); if (!def || !overlay || !card) return; state.ingredientId = ingredientId; state.productId = productId && PRODUCTS[productId] ? productId : null; state.selectedProductId = selectedProductId && PRODUCTS[selectedProductId] ? selectedProductId : state.productId; state.sourceNote = sourceNote; state.onProductChange = onProductChange; state.onAfterChange = onAfterChange; render(); clearTimeout(state.closeTimer); overlay.classList.remove('hidden'); overlay.style.pointerEvents = 'auto'; requestAnimationFrame(() => { overlay.classList.add('opacity-100'); card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }); } function close() { const overlay = el('overlay'); const card = el(); if (overlay && card) { overlay.classList.remove('opacity-100'); overlay.style.pointerEvents = 'none'; card.style.opacity = '0'; card.style.transform = 'translateY(1.5rem)'; state.closeTimer = setTimeout(() => overlay.classList.add('hidden'), 220); } state.ingredientId = null; state.productId = null; state.selectedProductId = null; state.onProductChange = null; state.onAfterChange = null; state.sourceNote = defaultSourceNote; } function bind() { if (bound) return; bound = true; el('close')?.addEventListener('click', close); el('back')?.addEventListener('click', () => { state.productId = null; render(); }); el('overlay')?.addEventListener('click', (event) => { if (event.target.id === `${idBase}-overlay`) close(); }); } function refresh() { if (state.ingredientId) render(); } return { bind, open, close, refresh, isOpen: () => Boolean(state.ingredientId), }; }