Add ingredients' products
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m20s
Some checks failed
Build and Deploy / build-and-push (push) Failing after 1m20s
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { INGREDIENTS, RECIPES } from '../data/catalog.js?v=2';
|
||||
import { INGREDIENTS, RECIPES, PRODUCTS, getProductsForIngredient } from '../data/catalog.js?v=6';
|
||||
import { MEAL_SLOTS } from '../planner/mealSlots.js';
|
||||
import { addDays } from './dateUtils.js';
|
||||
import { getDayPlan } from './planStore.js';
|
||||
import { getDayPlan } from './planStore.js?v=2';
|
||||
import { getPantryTotal } from './pantryShopping.js?v=2';
|
||||
|
||||
export function dayHasAnyMeal(plans, d) {
|
||||
const p = getDayPlan(plans, d);
|
||||
@@ -17,20 +18,24 @@ function hasCustomizations(entry) {
|
||||
return (entry.excludedIngredients?.length > 0) ||
|
||||
(entry.amountOverrides && Object.keys(entry.amountOverrides).length > 0) ||
|
||||
(entry.addedIngredients?.length > 0) ||
|
||||
(entry.substitutions && Object.keys(entry.substitutions).length > 0);
|
||||
(entry.substitutions && Object.keys(entry.substitutions).length > 0) ||
|
||||
(entry.productSelections && Object.keys(entry.productSelections).length > 0);
|
||||
}
|
||||
|
||||
function nutritionForAmountRaw(ingredientId, amount, unit) {
|
||||
function nutritionForAmountRaw(ingredientId, amount, unit, productId = null) {
|
||||
const def = INGREDIENTS[ingredientId];
|
||||
if (!def?.nutritionPer100g) return null;
|
||||
if (!def) return null;
|
||||
const product = productId ? PRODUCTS[productId] : null;
|
||||
const nutrition = product?.nutritionPer100g || def.nutritionPer100g;
|
||||
if (!nutrition) return null;
|
||||
let g = amount;
|
||||
if ((unit === 'szt.' || unit === 'szt') && def.weightPerPiece) g = amount * def.weightPerPiece;
|
||||
const f = g / 100;
|
||||
return {
|
||||
kcal: def.nutritionPer100g.kcal * f,
|
||||
protein: def.nutritionPer100g.protein * f,
|
||||
fat: def.nutritionPer100g.fat * f,
|
||||
carbs: def.nutritionPer100g.carbs * f,
|
||||
kcal: nutrition.kcal * f,
|
||||
protein: nutrition.protein * f,
|
||||
fat: nutrition.fat * f,
|
||||
carbs: nutrition.carbs * f,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,17 +57,20 @@ export function computeEntryNutrition(entry) {
|
||||
const excluded = new Set(entry.excludedIngredients || []);
|
||||
const overrides = entry.amountOverrides || {};
|
||||
const subs = entry.substitutions || {};
|
||||
const ps = entry.productSelections || {};
|
||||
let kcal = 0, protein = 0, fat = 0, carbs = 0;
|
||||
|
||||
for (const ing of r.ingredients) {
|
||||
if (excluded.has(ing.ingredientId)) continue;
|
||||
const eid = subs[ing.ingredientId] || ing.ingredientId;
|
||||
const base = overrides[ing.ingredientId] ?? ing.amount;
|
||||
const n = nutritionForAmountRaw(eid, base * s, ing.unit);
|
||||
const productId = ps[eid] || null;
|
||||
const n = nutritionForAmountRaw(eid, base * s, ing.unit, productId);
|
||||
if (n) { kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs; }
|
||||
}
|
||||
for (const a of (entry.addedIngredients || [])) {
|
||||
const n = nutritionForAmountRaw(a.ingredientId, a.amount * s, a.unit);
|
||||
const productId = ps[a.ingredientId] || null;
|
||||
const n = nutritionForAmountRaw(a.ingredientId, a.amount * s, a.unit, productId);
|
||||
if (n) { kcal += n.kcal; protein += n.protein; fat += n.fat; carbs += n.carbs; }
|
||||
}
|
||||
|
||||
@@ -231,7 +239,7 @@ export function countDayShortfalls(dayPlan, pantry) {
|
||||
const lines = mergeIngredientLines(flattenDayIngredientLines(dayPlan));
|
||||
let count = 0;
|
||||
for (const line of lines) {
|
||||
if ((Number(pantry[line.ingredientId]) || 0) < line.amount) count++;
|
||||
if (getPantryTotal(line.ingredientId, pantry) < line.amount) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -242,7 +250,11 @@ export function countDayShortfalls(dayPlan, pantry) {
|
||||
* ile jest w spiżarni (po odjęciu zużycia z poprzednich dni) i ile brakuje.
|
||||
*/
|
||||
export function computeFullForecast(plans, pantry, startDate, lookAheadDays = 8) {
|
||||
const running = { ...pantry };
|
||||
// Flatten pantry to simple totals for forecast (running deduction)
|
||||
const running = {};
|
||||
for (const [k, v] of Object.entries(pantry)) {
|
||||
running[k] = typeof v === 'number' ? v : (v && v._total) || 0;
|
||||
}
|
||||
const days = [];
|
||||
|
||||
for (let i = 0; i < lookAheadDays; i++) {
|
||||
@@ -276,3 +288,55 @@ export function computeFullForecast(plans, pantry, startDate, lookAheadDays = 8)
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
const LAST_PRODUCTS_KEY = 'recipe-last-product-selections';
|
||||
|
||||
function loadLastProductSelections() {
|
||||
try {
|
||||
const raw = localStorage.getItem(LAST_PRODUCTS_KEY);
|
||||
return raw ? JSON.parse(raw) : {};
|
||||
} catch { return {}; }
|
||||
}
|
||||
|
||||
/** Save user's product choice so it becomes the default next time. */
|
||||
export function saveLastProductSelection(ingredientId, productId) {
|
||||
const prev = loadLastProductSelections();
|
||||
prev[ingredientId] = productId;
|
||||
try { localStorage.setItem(LAST_PRODUCTS_KEY, JSON.stringify(prev)); } catch {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-select products for a recipe.
|
||||
* Priority: last user choice > pantry stock (highest qty) > first from catalog.
|
||||
* Only selects for ingredients that have products defined.
|
||||
*/
|
||||
export function autoSelectProducts(recipe, pantry) {
|
||||
const selections = {};
|
||||
const lastUsed = loadLastProductSelections();
|
||||
for (const ing of recipe.ingredients) {
|
||||
const products = getProductsForIngredient(ing.ingredientId);
|
||||
if (products.length === 0) continue;
|
||||
|
||||
// 1. Last user choice (if product still exists)
|
||||
const lastPid = lastUsed[ing.ingredientId];
|
||||
if (lastPid && products.some(p => p.id === lastPid)) {
|
||||
selections[ing.ingredientId] = lastPid;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Pantry stock — pick product with most qty
|
||||
const val = pantry[ing.ingredientId];
|
||||
if (val && typeof val === 'object') {
|
||||
const items = (val.items || []).filter(i => i.qty > 0);
|
||||
if (items.length > 0) {
|
||||
items.sort((a, b) => b.qty - a.qty);
|
||||
selections[ing.ingredientId] = items[0].productId;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. First from catalog
|
||||
selections[ing.ingredientId] = products[0].id;
|
||||
}
|
||||
return selections;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user