Add ingredients' products
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m20s

This commit is contained in:
2026-04-07 22:51:30 +02:00
parent ac32e05c31
commit 868862d031
11 changed files with 576 additions and 57 deletions

View File

@@ -1,4 +1,4 @@
import { RECIPES } from '../data/catalog.js?v=2';
import { RECIPES } from '../data/catalog.js?v=6';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
import { applyFilters, getFilterState } from './RecipeList.js';

View File

@@ -1,4 +1,4 @@
import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=2';
import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=6';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
import {
addMonths,
@@ -15,15 +15,15 @@ import {
countDayShortfalls,
dayHasAnyMeal,
sumDayNutrition,
} from '../services/planIngredients.js';
import { addOrMergeShoppingLines, loadPantry } from '../services/pantryShopping.js';
} from '../services/planIngredients.js?v=3';
import { addOrMergeShoppingLines, loadPantry } from '../services/pantryShopping.js?v=2';
import {
dateKey,
getDayPlan,
loadPlans,
newPlanEntryId,
savePlans,
} from '../services/planStore.js';
} from '../services/planStore.js?v=2';
import {
CALENDAR_HANDLE_CLASS,
CALENDAR_MONTHS_SHORT,

View File

@@ -1,9 +1,12 @@
import {
INGREDIENTS,
CATEGORY_LABELS,
PRODUCTS,
pantryQtyStep,
} from '../data/catalog.js?v=2';
import { addIngredientToKitchenList, categoryLabel, loadPantry, setPantryQty } from '../services/pantryShopping.js';
getProductsForIngredient,
ingredientHasProducts,
} from '../data/catalog.js?v=6';
import { addIngredientToKitchenList, categoryLabel, loadPantry, setPantryQty, setPantryProductQty, getPantryTotal, getPantryProducts, getPantryGeneric } from '../services/pantryShopping.js?v=2';
import { showAppToast } from '../ui/toast.js';
/* ── helpers ── */
@@ -97,6 +100,8 @@ export function getPantryHTML() {
</button>
</div>
<div id="pv2-product-breakdown" class="shrink-0"></div>
<div class="border-t border-gray-100 shrink-0"></div>
<div class="shrink-0 space-y-1">
@@ -172,7 +177,7 @@ function getFilteredIds(searchRaw) {
function chipHtml(id, pantry) {
const def = INGREDIENTS[id];
const qty = Number(pantry[id]) || 0;
const qty = getPantryTotal(id, pantry);
const u = unitLabel(def.pantryUnit);
if (qty > 0) {
@@ -209,7 +214,7 @@ function renderBoard() {
const pantry = loadPantry();
const allFiltered = getFilteredIds(q);
const visible = showOnlyStock
? allFiltered.filter(id => (Number(pantry[id]) || 0) > 0)
? allFiltered.filter(id => getPantryTotal(id, pantry) > 0)
: allFiltered;
if (visible.length === 0) {
@@ -271,7 +276,7 @@ function openEditSheet(ingredientId) {
editingId = ingredientId;
const pantry = loadPantry();
const qty = Number(pantry[ingredientId]) || 0;
const qty = getPantryTotal(ingredientId, pantry);
const u = unitLabel(def.pantryUnit);
const step = pantryQtyStep(ingredientId);
const pack = def.purchasePack;
@@ -311,6 +316,16 @@ function openEditSheet(ingredientId) {
}
}
// Hide main +/- when products exist (total is sum of product rows)
const hasProds = ingredientHasProducts(ingredientId);
const mainMinus = document.getElementById('pv2-edit-minus');
const mainPlus = document.getElementById('pv2-edit-plus');
const mainQtyInput = document.getElementById('pv2-edit-qty');
if (mainMinus) mainMinus.classList.toggle('hidden', hasProds);
if (mainPlus) mainPlus.classList.toggle('hidden', hasProds);
if (mainQtyInput) mainQtyInput.readOnly = hasProds;
renderProductBreakdown(ingredientId, pantry);
renderNutritionInSheet(def);
const bg = document.getElementById('pv2-edit-bg');
@@ -331,6 +346,83 @@ function nutritionListRow(label, valueHtml) {
</li>`;
}
function renderProductBreakdown(ingredientId, pantry) {
const wrap = document.getElementById('pv2-product-breakdown');
if (!wrap) return;
const products = getProductsForIngredient(ingredientId);
if (products.length === 0) { wrap.innerHTML = ''; return; }
const def = INGREDIENTS[ingredientId];
const u = unitLabel(def.pantryUnit);
const pantryProducts = getPantryProducts(ingredientId, pantry);
const generic = getPantryGeneric(ingredientId, pantry);
const productQty = (pid) => {
const item = pantryProducts.find(i => i.productId === pid);
return item ? item.qty : 0;
};
const rows = products.map(p => {
const q = Math.round(productQty(p.id));
return `<div class="flex items-center gap-1.5 py-1" data-product-row="${esc(p.id)}">
<span class="flex-1 text-[12px] text-gray-700 truncate" title="${esc(p.name)}">${esc(p.name)}</span>
<button type="button" class="pv2-prod-minus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="${esc(p.id)}" data-step="${p.packSize}">
<i class="fas fa-minus text-[9px]"></i>
</button>
<span class="pv2-prod-qty w-12 text-center text-[13px] font-semibold tabular-nums text-gray-800">${q} ${esc(u)}</span>
<button type="button" class="pv2-prod-plus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="${esc(p.id)}" data-step="${p.packSize}">
<i class="fas fa-plus text-[9px]"></i>
</button>
</div>`;
}).join('');
const genericRow = `<div class="flex items-center gap-1.5 py-1" data-product-row="_generic">
<span class="flex-1 text-[12px] text-gray-500 italic truncate">Nieokreślony</span>
<button type="button" class="pv2-prod-minus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="_generic" data-step="${pantryQtyStep(ingredientId)}">
<i class="fas fa-minus text-[9px]"></i>
</button>
<span class="pv2-prod-qty w-12 text-center text-[13px] font-semibold tabular-nums text-gray-800">${Math.round(generic)} ${esc(u)}</span>
<button type="button" class="pv2-prod-plus w-7 h-7 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 flex items-center justify-center transition-colors active:scale-95 shrink-0" data-pid="_generic" data-step="${pantryQtyStep(ingredientId)}">
<i class="fas fa-plus text-[9px]"></i>
</button>
</div>`;
wrap.innerHTML = `
<div class="space-y-0.5 px-0.5">
<p class="text-[9px] font-semibold uppercase tracking-wide text-gray-400 mb-1">Produkty</p>
${rows}
${genericRow}
</div>`;
// Bind product +/- buttons
wrap.querySelectorAll('.pv2-prod-plus, .pv2-prod-minus').forEach(btn => {
btn.addEventListener('click', () => {
if (!editingId) return;
const pid = btn.dataset.pid;
const step = Number(btn.dataset.step) || 1;
const isPlus = btn.classList.contains('pv2-prod-plus');
const pantry = loadPantry();
if (pid === '_generic') {
const cur = getPantryGeneric(editingId, pantry);
const next = Math.max(0, cur + (isPlus ? step : -step));
setPantryQty(editingId, next);
} else {
const items = getPantryProducts(editingId, pantry);
const cur = items.find(i => i.productId === pid)?.qty || 0;
const next = Math.max(0, cur + (isPlus ? step : -step));
setPantryProductQty(editingId, pid, next);
}
// Re-render breakdown and total
const freshPantry = loadPantry();
renderProductBreakdown(editingId, freshPantry);
const totalQty = getPantryTotal(editingId, freshPantry);
setEditQty(totalQty);
});
});
}
function renderNutritionInSheet(def) {
const wrap = document.getElementById('pv2-edit-nutrition');
if (!wrap) return;

View File

@@ -1,4 +1,4 @@
import { RECIPES, INGREDIENTS } from '../data/catalog.js?v=2';
import { RECIPES, INGREDIENTS } from '../data/catalog.js?v=6';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
function escapeHtml(s) {

View File

@@ -1,4 +1,4 @@
import { RECIPES } from '../data/catalog.js?v=2';
import { RECIPES } from '../data/catalog.js?v=6';
import { MEAL_SLOTS } from '../planner/mealSlots.js';
function escapeHtml(s) {