484 lines
18 KiB
JavaScript
484 lines
18 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 },
|
||
},
|
||
orzechy_laskowe: {
|
||
id: 'orzechy_laskowe',
|
||
name: 'Orzechy laskowe',
|
||
category: 'suche',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 628, protein: 15, fat: 61, carbs: 17 },
|
||
},
|
||
orzechy_nerkowca: {
|
||
id: 'orzechy_nerkowca',
|
||
name: 'Orzechy nerkowca',
|
||
category: 'suche',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 553, protein: 18, fat: 44, carbs: 30 },
|
||
},
|
||
migdaly: {
|
||
id: 'migdaly',
|
||
name: 'Migdały',
|
||
category: 'suche',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 579, protein: 21, fat: 50, carbs: 22 },
|
||
},
|
||
orzechy_pekan: {
|
||
id: 'orzechy_pekan',
|
||
name: 'Orzechy pekan',
|
||
category: 'suche',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 691, protein: 9, fat: 72, 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 },
|
||
},
|
||
banany: {
|
||
id: 'banany',
|
||
name: 'Banany',
|
||
category: 'owoce',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 89, protein: 1.1, fat: 0.3, carbs: 23 },
|
||
},
|
||
jagody: {
|
||
id: 'jagody',
|
||
name: 'Jagody',
|
||
category: 'owoce',
|
||
pantryUnit: 'g',
|
||
nutritionPer100g: { kcal: 44, protein: 0.7, fat: 0.4, carbs: 10 },
|
||
},
|
||
};
|
||
|
||
/** Porcja bazowa = 1; składniki przez ingredientId */
|
||
export const RECIPES = {
|
||
placki: {
|
||
id: 'placki',
|
||
title: 'Puszyste placki',
|
||
description: 'Klasyczne placki na śniadanie — puszyste i złociste.',
|
||
minutes: 15,
|
||
thumbLabel: 'Placki',
|
||
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
|
||
tags: ['wegetariańskie', 'słodkie'],
|
||
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.' },
|
||
],
|
||
steps: [
|
||
'Mąkę przesiej do miski, dodaj szczyptę soli.',
|
||
'Wbij jajka, wlej mleko i wymieszaj trzepaczką na gładkie ciasto.',
|
||
'Rozgrzej patelnię z odrobiną masła na średnim ogniu.',
|
||
'Nakładaj ciasto łyżką wazową i smaż placki po ok. 2 min z każdej strony.',
|
||
],
|
||
},
|
||
salatka: {
|
||
id: 'salatka',
|
||
title: 'Sałatka z kurczakiem',
|
||
description: 'Zielone warzywa z grillowanym kurczakiem.',
|
||
minutes: 20,
|
||
thumbLabel: 'Sałatka',
|
||
allowedSlots: ['obiad'],
|
||
tags: ['wysokobiałkowe', 'niskokaloryczne'],
|
||
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.' },
|
||
],
|
||
steps: [
|
||
'Pierś z kurczaka przypraw solą i pieprzem, griluj na patelni ok. 5 min z każdej strony.',
|
||
'Pokrój kurczaka w paski i odłóż do ostygnięcia.',
|
||
'Wymieszaj mix sałat z pokrojonym pomidorem.',
|
||
'Ułóż kurczaka na sałatce, polej ulubionym dressingiem.',
|
||
],
|
||
},
|
||
makaron: {
|
||
id: 'makaron',
|
||
title: 'Makaron z pomidorami i bazylią',
|
||
description: 'Aromatyczny sos pomidorowy z czosnkiem i świeżą bazylią.',
|
||
minutes: 30,
|
||
thumbLabel: 'Makaron',
|
||
allowedSlots: ['obiad', 'kolacja'],
|
||
tags: ['wegetariańskie', 'wegańskie'],
|
||
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' },
|
||
],
|
||
steps: [
|
||
'Ugotuj makaron al dente wg instrukcji na opakowaniu.',
|
||
'Na patelni rozgrzej oliwę, dodaj pomidory krojone i gotuj 10 min.',
|
||
'Dopraw solą, pieprzem i szczyptą cukru.',
|
||
'Wymieszaj makaron z sosem, udekoruj świeżą bazylią.',
|
||
],
|
||
},
|
||
koktajl: {
|
||
id: 'koktajl',
|
||
title: 'Koktajl owocowy',
|
||
description: 'Mix jagód i jogurtu — szybka przekąska lub drugie śniadanie.',
|
||
minutes: 5,
|
||
thumbLabel: 'Koktajl',
|
||
allowedSlots: ['przekaska', 'drugie_sniadanie'],
|
||
tags: ['wegetariańskie', 'szybkie'],
|
||
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' },
|
||
],
|
||
steps: [
|
||
'Wrzuć jogurt, jagody i miód do blendera.',
|
||
'Zmiksuj na gładką masę (~30 sekund).',
|
||
'Przelej do szklanki. Gotowe!',
|
||
],
|
||
},
|
||
tost_awokado: {
|
||
id: 'tost_awokado',
|
||
title: 'Tost z awokado',
|
||
description: 'Chleb na zakwasie z rozgniecionym awokado i cytryną.',
|
||
minutes: 10,
|
||
thumbLabel: 'Tost',
|
||
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
|
||
tags: ['wegetariańskie', 'wegańskie', 'szybkie'],
|
||
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.' },
|
||
],
|
||
steps: [
|
||
'Opiecz kromki chleba w tosterze lub na suchej patelni.',
|
||
'Przekrój awokado, wyjmij pestkę i wyłóż miąższ do miseczki.',
|
||
'Rozgnieć widelcem, dodaj sok z cytryny, sól i pieprz.',
|
||
'Nałóż masę na tosty. Podawaj od razu.',
|
||
],
|
||
},
|
||
losos: {
|
||
id: 'losos',
|
||
title: 'Grillowany łosoś',
|
||
description: 'Świeży łosoś z masłem cytrynowym i koperkiem.',
|
||
minutes: 25,
|
||
thumbLabel: 'Łosoś',
|
||
allowedSlots: ['kolacja', 'obiad'],
|
||
tags: ['wysokobiałkowe'],
|
||
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' },
|
||
],
|
||
steps: [
|
||
'Filety oprósz solą, pieprzem i skrop sokiem z cytryny.',
|
||
'Rozgrzej patelnię grillową na dość mocnym ogniu.',
|
||
'Smaż łososia 4–5 min z każdej strony (skórą do dołu na start).',
|
||
'Podawaj z posiekanym koperkiem i plasterkiem cytryny.',
|
||
],
|
||
},
|
||
tacos: {
|
||
id: 'tacos',
|
||
title: 'Tacos z wołowiną',
|
||
description: 'Pikantna mielona wołowina ze świeżą salsą w tortillach.',
|
||
minutes: 20,
|
||
thumbLabel: 'Tacos',
|
||
allowedSlots: ['kolacja', 'obiad'],
|
||
tags: [],
|
||
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' },
|
||
],
|
||
steps: [
|
||
'Na rozgrzanej patelni podsmaż mielone, rozbijając widelcem, aż się zarumieni.',
|
||
'Dopraw kuminem, papryką, solą i pieprzem.',
|
||
'Podgrzej tortille na suchej patelni po 15 sek. z każdej strony.',
|
||
'Nałóż mięso na tortillę, polej salsą i zawiń.',
|
||
],
|
||
},
|
||
owsianka: {
|
||
id: 'owsianka',
|
||
title: 'Miska owsianki',
|
||
description: 'Ciepła owsianka z miodem — szybki i sycący posiłek.',
|
||
minutes: 10,
|
||
thumbLabel: 'Owsianka',
|
||
allowedSlots: ['sniadanie', 'drugie_sniadanie'],
|
||
tags: ['wegetariańskie', 'szybkie'],
|
||
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' },
|
||
],
|
||
steps: [
|
||
'W garnuszku zagotuj mleko.',
|
||
'Wsyp płatki owsiane, zmniejsz ogień i gotuj 3–4 min, mieszając.',
|
||
'Przełóż do miski, polej miodem. Opcjonalnie dodaj owoce lub orzechy.',
|
||
],
|
||
},
|
||
serek_owoc: {
|
||
id: 'serek_owoc',
|
||
title: 'Serek wiejski z orzechami i owocami',
|
||
description: 'Lekki, pożywny posiłek: serek z orzechami, truskawkami i borówkami.',
|
||
minutes: 5,
|
||
thumbLabel: 'Serek',
|
||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'przekaska'],
|
||
tags: ['wegetariańskie', 'wysokobiałkowe', 'szybkie'],
|
||
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', alternatives: ['orzechy_laskowe', 'orzechy_nerkowca', 'migdaly', 'orzechy_pekan'] },
|
||
{ ingredientId: 'truskawki', amount: 100, unit: 'g', alternatives: ['banany'] },
|
||
{ ingredientId: 'borowki_amerykanskie', amount: 80, unit: 'g', alternatives: ['jagody'] },
|
||
],
|
||
steps: [
|
||
'Przełóż serek wiejski do miseczki.',
|
||
'Dodaj miód i delikatnie wymieszaj.',
|
||
'Orzechy posiekaj na mniejsze kawałki i posyp nimi serek z miodem.',
|
||
'Umyj owoce (ew. pokrój na połówki) i ułóż na wierzchu. Gotowe!',
|
||
],
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 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 };
|
||
}
|