Files
homelab/stacks/recipe/js/data/catalog.js
2026-03-24 23:15:56 +01:00

372 lines
12 KiB
JavaScript

/**
* Katalog składników i przepisów — odpowiednik tabel w DB (edycja poza aplikacją).
* pantryUnit: jednostka magazynowa / sumowania na liście zakupów (g, ml, szt.).
* purchasePack: minimalna „sztuka” ze sklepu w tej samej jednostce co pantryUnit (np. 200 g).
* nutritionPer100g — wartości szacunkowe na 100 g (dla płynów: traktuj ml≈g przy wodzie).
*/
export const CATEGORY_LABELS = {
pieczywo: 'Pieczywo',
nabial: 'Nabiał',
mieso_ryby: 'Mięso i ryby',
warzywa: 'Warzywa',
owoce: 'Owoce',
suche: 'Suche i kasze',
przyprawy: 'Przyprawy i zioła',
inne: 'Inne',
};
/**
* @typedef {{ kcal: number, protein: number, fat: number, carbs: number }} NutritionPer100
* @typedef {{ amount: number, label?: string }} PurchasePack
* @typedef {{ id: string, name: string, category: keyof typeof CATEGORY_LABELS, pantryUnit: 'g'|'ml'|'szt', purchasePack?: PurchasePack, nutritionPer100g?: NutritionPer100 }} IngredientDef
*/
/** @type {Record<string, IngredientDef>} */
export const INGREDIENTS = {
maka_pszenna: {
id: 'maka_pszenna',
name: 'Mąka pszenna',
category: 'suche',
pantryUnit: 'g',
nutritionPer100g: { kcal: 364, protein: 10, fat: 1, carbs: 76 },
},
mleko: {
id: 'mleko',
name: 'Mleko',
category: 'nabial',
pantryUnit: 'ml',
purchasePack: { amount: 1000, label: 'butelka 1 l' },
nutritionPer100g: { kcal: 42, protein: 3.4, fat: 1, carbs: 5 },
},
jajko: {
id: 'jajko',
name: 'Jajka',
category: 'nabial',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 143, protein: 13, fat: 9.5, carbs: 1.1 },
},
piers_kurczaka: {
id: 'piers_kurczaka',
name: 'Pierś z kurczaka',
category: 'mieso_ryby',
pantryUnit: 'g',
nutritionPer100g: { kcal: 165, protein: 31, fat: 3.6, carbs: 0 },
},
mix_salat: {
id: 'mix_salat',
name: 'Mix sałat',
category: 'warzywa',
pantryUnit: 'g',
nutritionPer100g: { kcal: 20, protein: 1.5, fat: 0.3, carbs: 3 },
},
pomidor: {
id: 'pomidor',
name: 'Pomidor',
category: 'warzywa',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 18, protein: 0.9, fat: 0.2, carbs: 3.9 },
},
makaron_suchy: {
id: 'makaron_suchy',
name: 'Makaron',
category: 'suche',
pantryUnit: 'g',
nutritionPer100g: { kcal: 371, protein: 13, fat: 1.5, carbs: 74 },
},
pomidory_krojone: {
id: 'pomidory_krojone',
name: 'Pomidory krojone (puszka)',
category: 'warzywa',
pantryUnit: 'g',
nutritionPer100g: { kcal: 20, protein: 1, fat: 0.2, carbs: 4 },
},
bazylia_swieza: {
id: 'bazylia_swieza',
name: 'Bazylia świeża',
category: 'przyprawy',
pantryUnit: 'g',
nutritionPer100g: { kcal: 23, protein: 3.2, fat: 0.6, carbs: 2.7 },
},
jogurt_naturalny: {
id: 'jogurt_naturalny',
name: 'Jogurt naturalny',
category: 'nabial',
pantryUnit: 'g',
nutritionPer100g: { kcal: 61, protein: 3.5, fat: 3.3, carbs: 4.7 },
},
mieszanka_jagod: {
id: 'mieszanka_jagod',
name: 'Mieszanka jagód',
category: 'owoce',
pantryUnit: 'g',
nutritionPer100g: { kcal: 50, protein: 0.7, fat: 0.3, carbs: 12 },
},
miod: {
id: 'miod',
name: 'Miód',
category: 'inne',
pantryUnit: 'g',
nutritionPer100g: { kcal: 304, protein: 0.3, fat: 0, carbs: 82 },
},
chleb_zakwas: {
id: 'chleb_zakwas',
name: 'Chleb na zakwasie',
category: 'pieczywo',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 250, protein: 9, fat: 1.5, carbs: 49 },
},
awokado: {
id: 'awokado',
name: 'Awokado',
category: 'warzywa',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 160, protein: 2, fat: 15, carbs: 9 },
},
cytryna: {
id: 'cytryna',
name: 'Cytryna',
category: 'owoce',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 29, protein: 1.1, fat: 0.3, carbs: 9 },
},
losos_filet: {
id: 'losos_filet',
name: 'Filet z łososia',
category: 'mieso_ryby',
pantryUnit: 'g',
nutritionPer100g: { kcal: 208, protein: 20, fat: 13, carbs: 0 },
},
koper_swiezy: {
id: 'koper_swiezy',
name: 'Koper',
category: 'przyprawy',
pantryUnit: 'g',
nutritionPer100g: { kcal: 43, protein: 3.5, fat: 1.1, carbs: 7 },
},
mieso_wol_mielone: {
id: 'mieso_wol_mielone',
name: 'Mięso mielone wołowe',
category: 'mieso_ryby',
pantryUnit: 'g',
nutritionPer100g: { kcal: 250, protein: 26, fat: 15, carbs: 0 },
},
tortilla_kukurydziana: {
id: 'tortilla_kukurydziana',
name: 'Tortille kukurydziane',
category: 'pieczywo',
pantryUnit: 'szt',
nutritionPer100g: { kcal: 218, protein: 5.7, fat: 2.9, carbs: 44 },
},
salsa_pomidorowa: {
id: 'salsa_pomidorowa',
name: 'Salsa pomidorowa',
category: 'warzywa',
pantryUnit: 'g',
nutritionPer100g: { kcal: 36, protein: 1.5, fat: 0.2, carbs: 8 },
},
platki_owsiane: {
id: 'platki_owsiane',
name: 'Płatki owsiane',
category: 'suche',
pantryUnit: 'g',
nutritionPer100g: { kcal: 389, protein: 17, fat: 7, carbs: 66 },
},
serek_wiejski: {
id: 'serek_wiejski',
name: 'Serek wiejski',
category: 'nabial',
pantryUnit: 'g',
purchasePack: { amount: 200, label: 'opakowanie 200 g' },
nutritionPer100g: { kcal: 97, protein: 11, fat: 5, carbs: 3 },
},
orzechy_wloskie: {
id: 'orzechy_wloskie',
name: 'Orzechy włoskie',
category: 'suche',
pantryUnit: 'g',
nutritionPer100g: { kcal: 654, protein: 15, fat: 65, carbs: 14 },
},
truskawki: {
id: 'truskawki',
name: 'Truskawki',
category: 'owoce',
pantryUnit: 'g',
nutritionPer100g: { kcal: 32, protein: 0.7, fat: 0.3, carbs: 8 },
},
borowki_amerykanskie: {
id: 'borowki_amerykanskie',
name: 'Borówki amerykańskie',
category: 'owoce',
pantryUnit: 'g',
nutritionPer100g: { kcal: 57, protein: 0.7, fat: 0.3, carbs: 14 },
},
};
/** Porcja bazowa = 1; składniki przez ingredientId */
export const RECIPES = {
placki: {
id: 'placki',
title: 'Puszyste placki',
minutes: 15,
thumbLabel: 'Placki',
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
nutritionPerServing: { kcal: 320, protein: 12, fat: 8, carbs: 48 },
ingredients: [
{ ingredientId: 'maka_pszenna', amount: 200, unit: 'g' },
{ ingredientId: 'mleko', amount: 250, unit: 'ml' },
{ ingredientId: 'jajko', amount: 2, unit: 'szt.' },
],
},
salatka: {
id: 'salatka',
title: 'Sałatka z kurczakiem',
minutes: 20,
thumbLabel: 'Sałatka',
allowedSlots: ['obiad'],
nutritionPerServing: { kcal: 250, protein: 35, fat: 9, carbs: 12 },
ingredients: [
{ ingredientId: 'piers_kurczaka', amount: 150, unit: 'g' },
{ ingredientId: 'mix_salat', amount: 100, unit: 'g' },
{ ingredientId: 'pomidor', amount: 1, unit: 'szt.' },
],
},
makaron: {
id: 'makaron',
title: 'Makaron z pomidorami i bazylią',
minutes: 30,
thumbLabel: 'Makaron',
allowedSlots: ['obiad', 'kolacja'],
nutritionPerServing: { kcal: 450, protein: 14, fat: 12, carbs: 72 },
ingredients: [
{ ingredientId: 'makaron_suchy', amount: 120, unit: 'g' },
{ ingredientId: 'pomidory_krojone', amount: 400, unit: 'g' },
{ ingredientId: 'bazylia_swieza', amount: 10, unit: 'g' },
],
},
koktajl: {
id: 'koktajl',
title: 'Koktajl owocowy',
minutes: 5,
thumbLabel: 'Koktajl',
allowedSlots: ['przekaska', 'drugie_sniadanie'],
nutritionPerServing: { kcal: 180, protein: 8, fat: 3, carbs: 32 },
ingredients: [
{ ingredientId: 'jogurt_naturalny', amount: 200, unit: 'g' },
{ ingredientId: 'mieszanka_jagod', amount: 150, unit: 'g' },
{ ingredientId: 'miod', amount: 15, unit: 'g' },
],
},
tost_awokado: {
id: 'tost_awokado',
title: 'Tost z awokado',
minutes: 10,
thumbLabel: 'Tost',
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
nutritionPerServing: { kcal: 220, protein: 6, fat: 14, carbs: 20 },
ingredients: [
{ ingredientId: 'chleb_zakwas', amount: 2, unit: 'kromki' },
{ ingredientId: 'awokado', amount: 1, unit: 'szt.' },
{ ingredientId: 'cytryna', amount: 0.5, unit: 'szt.' },
],
},
losos: {
id: 'losos',
title: 'Grillowany łosoś',
minutes: 25,
thumbLabel: 'Łosoś',
allowedSlots: ['kolacja', 'obiad'],
nutritionPerServing: { kcal: 380, protein: 38, fat: 22, carbs: 4 },
ingredients: [
{ ingredientId: 'losos_filet', amount: 180, unit: 'g' },
{ ingredientId: 'cytryna', amount: 0.5, unit: 'szt.' },
{ ingredientId: 'koper_swiezy', amount: 5, unit: 'g' },
],
},
tacos: {
id: 'tacos',
title: 'Tacos z wołowiną',
minutes: 20,
thumbLabel: 'Tacos',
allowedSlots: ['kolacja', 'obiad'],
nutritionPerServing: { kcal: 410, protein: 28, fat: 18, carbs: 38 },
ingredients: [
{ ingredientId: 'mieso_wol_mielone', amount: 200, unit: 'g' },
{ ingredientId: 'tortilla_kukurydziana', amount: 4, unit: 'szt.' },
{ ingredientId: 'salsa_pomidorowa', amount: 100, unit: 'g' },
],
},
owsianka: {
id: 'owsianka',
title: 'Miska owsianki',
minutes: 10,
thumbLabel: 'Owsianka',
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
nutritionPerServing: { kcal: 210, protein: 8, fat: 6, carbs: 34 },
ingredients: [
{ ingredientId: 'platki_owsiane', amount: 60, unit: 'g' },
{ ingredientId: 'mleko', amount: 200, unit: 'ml' },
{ ingredientId: 'miod', amount: 20, unit: 'g' },
],
},
serek_owoc: {
id: 'serek_owoc',
title: 'Serek wiejski z orzechami i owocami',
minutes: 5,
thumbLabel: 'Serek',
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'przekaska'],
nutritionPerServing: { kcal: 642, protein: 32, fat: 43, carbs: 41 },
ingredients: [
{ ingredientId: 'serek_wiejski', amount: 200, unit: 'g' },
{ ingredientId: 'miod', amount: 10, unit: 'g' },
{ ingredientId: 'orzechy_wloskie', amount: 50, unit: 'g' },
{ ingredientId: 'truskawki', amount: 100, unit: 'g' },
{ ingredientId: 'borowki_amerykanskie', amount: 80, unit: 'g' },
],
},
};
/**
* Krok +/- w spiżarni: całe opakowanie albo domyślny krok (10 g/ml lub 1 szt.).
* @param {string} ingredientId
* @returns {number}
*/
export function pantryQtyStep(ingredientId) {
const d = INGREDIENTS[ingredientId];
if (!d) return 10;
if (d.purchasePack && Number.isFinite(d.purchasePack.amount) && d.purchasePack.amount > 0) {
return d.purchasePack.amount;
}
return d.pantryUnit === 'szt' ? 1 : 10;
}
/**
* @param {IngredientDef} def
* @param {number} stockQty — w pantryUnit
*/
export function nutritionForStock(def, stockQty) {
const n = def.nutritionPer100g;
if (!n || !Number.isFinite(stockQty) || stockQty <= 0) return null;
const f = stockQty / 100;
return {
kcal: Math.round(n.kcal * f),
protein: Math.round(n.protein * f * 10) / 10,
fat: Math.round(n.fat * f * 10) / 10,
carbs: Math.round(n.carbs * f * 10) / 10,
};
}
/**
* Pełne opakowania + reszta (np. 450 g / 200 → 2 + 50 g).
* @param {IngredientDef} def
* @param {number} stockQty
* @returns {{ fullPacks: number, remainder: number } | null}
*/
export function splitStockIntoPacks(def, stockQty) {
const size = def.purchasePack?.amount;
if (!size || !Number.isFinite(size) || size <= 0 || !Number.isFinite(stockQty)) return null;
const fullPacks = Math.floor(stockQty / size);
const remainder = Math.round((stockQty - fullPacks * size) * 10) / 10;
return { fullPacks, remainder };
}