Redesign shopping list
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m19s

This commit is contained in:
2026-04-17 23:34:53 +02:00
parent a90e8ba9d2
commit 8e48ebdd95
7 changed files with 793 additions and 226 deletions

View File

@@ -1,5 +1,5 @@
import { INGREDIENTS, CATEGORY_LABELS, PRODUCTS, ingredientHasProducts } from '../data/catalog.js?v=9';
import { PANTRY_STORAGE_KEY, PANTRY_STORAGE_KEY_V2, SHOPPING_STORAGE_KEY } from '../storageKeys.js';
import { PANTRY_STORAGE_KEY, PANTRY_STORAGE_KEY_V2, SHOPPING_STORAGE_KEY, SHOPPING_SESSION_KEY } from '../storageKeys.js';
export const KITCHEN_LIST_ID = 'kitchen';
export const MISC_LIST_ID = 'misc';
@@ -535,6 +535,148 @@ export function setPantryProductQty(ingredientId, productId, qty) {
return pantry;
}
/**
* Dodaj delta do spiżarni (używane przy zakupie ze listy).
* @param {string} ingredientId
* @param {string|undefined} productId
* @param {number} amount
*/
export function addAmountToPantry(ingredientId, productId, amount) {
const pantry = loadPantry();
const rounded = Math.round(amount * 1000) / 1000;
if (productId && PRODUCTS[productId]) {
let val = pantry[ingredientId];
if (!val || typeof val === 'number') {
val = { items: [], _total: 0 };
pantry[ingredientId] = val;
}
const idx = val.items.findIndex((i) => i.productId === productId);
if (idx >= 0) val.items[idx].qty = Math.round((val.items[idx].qty + rounded) * 1000) / 1000;
else val.items.push({ productId, qty: rounded });
recalcTotal(val);
} else {
const cur = typeof pantry[ingredientId] === 'number' ? pantry[ingredientId] : 0;
pantry[ingredientId] = Math.round((cur + rounded) * 1000) / 1000;
}
savePantry(pantry);
return pantry;
}
/**
* Odejmij delta ze spiżarni (undo zakupu).
* @param {string} ingredientId
* @param {string|undefined} productId
* @param {number} amount
*/
export function subtractFromPantry(ingredientId, productId, amount) {
const pantry = loadPantry();
if (productId && PRODUCTS[productId]) {
const val = pantry[ingredientId];
if (!val || typeof val === 'number') return pantry;
const idx = val.items.findIndex((i) => i.productId === productId);
if (idx < 0) return pantry;
val.items[idx].qty = Math.max(0, Math.round((val.items[idx].qty - amount) * 1000) / 1000);
if (val.items[idx].qty <= 0) val.items.splice(idx, 1);
recalcTotal(val);
if (val._total <= 0) delete pantry[ingredientId];
} else {
const cur = typeof pantry[ingredientId] === 'number' ? pantry[ingredientId] : 0;
const next = Math.max(0, Math.round((cur - amount) * 1000) / 1000);
if (next <= 0) delete pantry[ingredientId];
else pantry[ingredientId] = next;
}
savePantry(pantry);
return pantry;
}
/* ══════════════════════════════════════════════════════════════════════
* Session zakupowy — selectedDays + log zakupów bieżącej sesji
* ══════════════════════════════════════════════════════════════════════ */
/**
* @typedef {{ id: string, ingredientId: string, productId?: string, name: string, addedAmount: number, unit: string, category: string, timestamp: number }} SessionLogEntry
* @typedef {{ selectedDays: string[], sessionLog: SessionLogEntry[] }} ShoppingSessionState
*/
function defaultShoppingSession() {
return { selectedDays: [], sessionLog: [] };
}
function normalizeShoppingSession(raw) {
if (!raw || typeof raw !== 'object') return defaultShoppingSession();
const selectedDays = Array.isArray(raw.selectedDays)
? raw.selectedDays.filter((d) => typeof d === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(d))
: [];
const sessionLog = Array.isArray(raw.sessionLog)
? raw.sessionLog.filter((e) => e && typeof e.ingredientId === 'string' && Number.isFinite(e.addedAmount))
: [];
return { selectedDays, sessionLog };
}
/** @returns {ShoppingSessionState} */
export function loadShoppingSession() {
try {
const raw = localStorage.getItem(SHOPPING_SESSION_KEY);
if (!raw) return defaultShoppingSession();
return normalizeShoppingSession(JSON.parse(raw));
} catch {
return defaultShoppingSession();
}
}
/** @param {ShoppingSessionState} s */
export function saveShoppingSession(s) {
try {
localStorage.setItem(SHOPPING_SESSION_KEY, JSON.stringify(s));
} catch { /* ignore */ }
}
/** @returns {string[]} */
export function getSelectedDays() {
return loadShoppingSession().selectedDays;
}
/** @param {string[]} days */
export function setSelectedDays(days) {
const s = loadShoppingSession();
s.selectedDays = days;
saveShoppingSession(s);
}
/**
* @param {{ ingredientId: string, productId?: string, name: string, addedAmount: number, unit: string, category: string }} entry
* @returns {string} new entry id
*/
export function addSessionLogEntry(entry) {
const s = loadShoppingSession();
const id = newId('sl');
s.sessionLog.push({
id,
ingredientId: entry.ingredientId,
productId: entry.productId || undefined,
name: entry.name,
addedAmount: Math.round(entry.addedAmount * 100) / 100,
unit: entry.unit,
category: entry.category,
timestamp: Date.now(),
});
saveShoppingSession(s);
return id;
}
/** @param {string} entryId */
export function removeSessionLogEntry(entryId) {
const s = loadShoppingSession();
s.sessionLog = s.sessionLog.filter((e) => e.id !== entryId);
saveShoppingSession(s);
}
export function clearSessionLog() {
const s = loadShoppingSession();
s.sessionLog = [];
saveShoppingSession(s);
}
/** Kupione ze listy kuchennej → spiżarnia (zawsze ta lista, niezależnie od aktywnej zakładki). */
export function applyCheckedKitchenListToPantry() {
const s = loadShoppingState();