import { INGREDIENTS, CATEGORY_LABELS } from '../data/catalog.js?v=8'; import { KITCHEN_LIST_ID, loadShoppingState, saveShoppingState, addOrMergeShoppingLines, removeItemFromList, clearCheckedInList, loadPantry, savePantry, computeShortfalls, categoryLabel, } from '../services/pantryShopping.js?v=2'; import { aggregateWeekIngredientNeed } from '../services/planIngredients.js?v=2'; import { loadPlans } from '../services/planStore.js?v=2'; import { startOfWeekMonday } from '../services/dateUtils.js'; 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 formatQty(n) { const rounded = Math.round((Number(n) || 0) * 10) / 10; return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1).replace(/\.0$/, ''); } 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 CATEGORY_ORDER = ['pieczywo', 'nabial', 'mieso_ryby', 'warzywa', 'owoce', 'suche', 'przyprawy', 'inne']; /* ══════════════════════ HTML SHELL ══════════════════════ */ export function getShoppingListHTML() { return ` `; } /* ══════════════════════ RENDERING ══════════════════════ */ function getKitchenItems() { const state = loadShoppingState(); const list = state.lists.find((l) => l.id === KITCHEN_LIST_ID && l.type === 'kitchen'); return list ? /** @type {import('../services/pantryShopping.js').KitchenShoppingItem[]} */ (list.items) : []; } function groupItemsByCategory(items) { /** @type {Map} */ const groups = new Map(); for (const item of items) { const cat = item.category || 'inne'; if (!groups.has(cat)) groups.set(cat, []); groups.get(cat).push(item); } return CATEGORY_ORDER .filter((cat) => groups.has(cat)) .map((cat) => ({ cat, items: groups.get(cat) })); } function itemRowHtml(item) { const def = INGREDIENTS[item.ingredientId]; const icon = def ? (CATEGORY_ICONS[def.category] || 'fa-jar') : 'fa-jar'; const image = def?.image; const checked = item.checked; const mediaHtml = image ? `` : `
`; return `
${mediaHtml}
${esc(item.name)}
${esc(formatQty(item.amount))} ${esc(unitLabel(item.unit))}
`; } function renderBoard() { const root = document.getElementById('sl-board'); if (!root) return; const items = getKitchenItems(); const unchecked = items.filter((i) => !i.checked); const checked = items.filter((i) => i.checked); if (items.length === 0) { root.innerHTML = `

Lista jest pusta

Kliknij „Generuj braki z planera" aby dodać składniki na bieżący tydzień.

`; return; } let html = ''; // Unchecked items grouped by category if (unchecked.length > 0) { const groups = groupItemsByCategory(unchecked); html += groups.map(({ cat, items: catItems }) => { const icon = CATEGORY_ICONS[cat] || 'fa-jar'; return `

${esc(categoryLabel(cat))}

${catItems.length}
${catItems.map((item) => itemRowHtml(item)).join('')}
`; }).join(''); } // Checked items at the bottom if (checked.length > 0) { html += `

Kupione

${checked.length}
${checked.map((item) => itemRowHtml(item)).join('')}
`; } root.innerHTML = html; bindRowEvents(root); } /* ══════════════════════ EVENTS ══════════════════════ */ function bindRowEvents(root) { root.querySelectorAll('.sl-row').forEach((row) => { const id = row.dataset.id; row.querySelector('.sl-check')?.addEventListener('click', () => { handleToggle(id); }); row.querySelector('.sl-remove')?.addEventListener('click', () => { removeItemFromList(KITCHEN_LIST_ID, id); renderBoard(); updateBadge(); }); }); } function handleToggle(itemId) { const state = loadShoppingState(); const list = state.lists.find((l) => l.id === KITCHEN_LIST_ID); if (!list) return; const item = list.items.find((i) => i.id === itemId); if (!item) return; const wasChecked = item.checked; item.checked = !wasChecked; saveShoppingState(state); if (!wasChecked) { // Just checked → apply to pantry applyItemToPantry(item); } renderBoard(); updateBadge(); if (typeof window.refreshPantry === 'function') window.refreshPantry(); } function applyItemToPantry(item) { const def = INGREDIENTS[item.ingredientId]; if (!def) return; const pantry = loadPantry(); if (item.productId) { let val = pantry[item.ingredientId]; if (!val || typeof val === 'number') { val = { items: [], _total: 0 }; pantry[item.ingredientId] = val; } const idx = val.items.findIndex((i) => i.productId === item.productId); if (idx >= 0) val.items[idx].qty = Math.round((val.items[idx].qty + item.amount) * 1000) / 1000; else val.items.push({ productId: item.productId, qty: Math.round(item.amount * 1000) / 1000 }); val._total = Math.round(val.items.reduce((s, i) => s + i.qty, 0) * 1000) / 1000; } else { const cur = typeof pantry[item.ingredientId] === 'number' ? pantry[item.ingredientId] : 0; pantry[item.ingredientId] = Math.round((cur + item.amount) * 1000) / 1000; } savePantry(pantry); } function handleGenerate() { const plans = loadPlans(); const weekStart = startOfWeekMonday(new Date()); const needLines = aggregateWeekIngredientNeed(plans, weekStart); const pantry = loadPantry(); const shortfalls = computeShortfalls(needLines, pantry); if (shortfalls.length === 0) { showAppToast('Wszystko masz w spiżarni!'); return; } const lines = shortfalls.map((s) => ({ ingredientId: s.ingredientId, amount: s.shortfall, unit: s.unit, name: s.name, category: s.category, sourceNote: 'Z planera', })); addOrMergeShoppingLines(lines, KITCHEN_LIST_ID); renderBoard(); updateBadge(); showAppToast(`Dodano ${shortfalls.length} pozycji z planera`); } function handleClearChecked() { clearCheckedInList(KITCHEN_LIST_ID); renderBoard(); updateBadge(); } /* ══════════════════════ BADGE ══════════════════════ */ function updateBadge() { const items = getKitchenItems(); const uncheckedCount = items.filter((i) => !i.checked).length; const badge = document.getElementById('nav-shopping-badge'); if (!badge) return; if (uncheckedCount > 0) { badge.textContent = String(uncheckedCount > 99 ? '99+' : uncheckedCount); badge.classList.remove('hidden'); } else { badge.classList.add('hidden'); } } /* ══════════════════════ PUBLIC API ══════════════════════ */ export function refreshShoppingList() { renderBoard(); updateBadge(); } export function setupShoppingList() { renderBoard(); updateBadge(); document.getElementById('sl-generate')?.addEventListener('click', handleGenerate); document.getElementById('sl-clear-checked')?.addEventListener('click', handleClearChecked); window.refreshShoppingList = refreshShoppingList; }