/** * 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} */ 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 }; }