Add more example recipes
All checks were successful
Build and Deploy / build-and-push (push) Successful in 27s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 27s
This commit is contained in:
12
js/app.js
12
js/app.js
@@ -1,9 +1,9 @@
|
||||
import { getRecipeListHTML, setupRecipeList } from './views/RecipeList.js';
|
||||
import { getFilterHTML, setupFilter } from './views/Filter.js';
|
||||
import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js';
|
||||
import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js';
|
||||
import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js';
|
||||
import { getShoppingHTML, refreshShopping, setupShopping } from './views/Shopping.js';
|
||||
import { getRecipeListHTML, setupRecipeList } from './views/RecipeList.js?v=2';
|
||||
import { getFilterHTML, setupFilter } from './views/Filter.js?v=2';
|
||||
import { getRecipeDetailHTML, setupRecipeDetail } from './views/RecipeDetailV2.js?v=2';
|
||||
import { getMealPlannerHTML, setupMealPlanner } from './views/MealPlanner.js?v=2';
|
||||
import { getPantryHTML, refreshPantry, setupPantry } from './views/Pantry.js?v=2';
|
||||
import { getShoppingHTML, refreshShopping, setupShopping } from './views/Shopping.js?v=2';
|
||||
|
||||
function getAppToastHTML() {
|
||||
return `
|
||||
|
||||
@@ -24,21 +24,16 @@ export const CATEGORY_LABELS = {
|
||||
|
||||
/** @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 },
|
||||
/* ── Pieczywo ─────────────────────────────────────── */
|
||||
bulka_grahamka: {
|
||||
id: 'bulka_grahamka',
|
||||
name: 'Bułka grahamka',
|
||||
category: 'pieczywo',
|
||||
pantryUnit: 'szt',
|
||||
purchasePack: { amount: 1, label: '1 bułka ~70 g' },
|
||||
nutritionPer100g: { kcal: 260, protein: 9, fat: 3, carbs: 48 },
|
||||
},
|
||||
/* ── Nabiał ───────────────────────────────────────── */
|
||||
jajko: {
|
||||
id: 'jajko',
|
||||
name: 'Jajka',
|
||||
@@ -46,131 +41,21 @@ export const INGREDIENTS = {
|
||||
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',
|
||||
mozzarella: {
|
||||
id: 'mozzarella',
|
||||
name: 'Mozzarella',
|
||||
category: 'nabial',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 61, protein: 3.5, fat: 3.3, carbs: 4.7 },
|
||||
purchasePack: { amount: 125, label: 'kulka 125 g' },
|
||||
nutritionPer100g: { kcal: 280, protein: 22, fat: 20, carbs: 2 },
|
||||
},
|
||||
mieszanka_jagod: {
|
||||
id: 'mieszanka_jagod',
|
||||
name: 'Mieszanka jagód',
|
||||
category: 'owoce',
|
||||
ricotta: {
|
||||
id: 'ricotta',
|
||||
name: 'Ricotta',
|
||||
category: 'nabial',
|
||||
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 },
|
||||
purchasePack: { amount: 250, label: 'opakowanie 250 g' },
|
||||
nutritionPer100g: { kcal: 174, protein: 11, fat: 13, carbs: 3 },
|
||||
},
|
||||
serek_wiejski: {
|
||||
id: 'serek_wiejski',
|
||||
@@ -180,6 +65,127 @@ export const INGREDIENTS = {
|
||||
purchasePack: { amount: 200, label: 'opakowanie 200 g' },
|
||||
nutritionPer100g: { kcal: 97, protein: 11, fat: 5, carbs: 3 },
|
||||
},
|
||||
serek_smietankowy: {
|
||||
id: 'serek_smietankowy',
|
||||
name: 'Serek śmietankowy',
|
||||
category: 'nabial',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 150, label: 'opakowanie 150 g' },
|
||||
nutritionPer100g: { kcal: 230, protein: 6, fat: 21, carbs: 4 },
|
||||
},
|
||||
/* ── Mięso i ryby ─────────────────────────────────── */
|
||||
szynka_parmenska: {
|
||||
id: 'szynka_parmenska',
|
||||
name: 'Szynka parmeńska',
|
||||
category: 'mieso_ryby',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 100, label: 'opakowanie 100 g' },
|
||||
nutritionPer100g: { kcal: 250, protein: 28, fat: 15, carbs: 0 },
|
||||
},
|
||||
szynka_z_kurczaka: {
|
||||
id: 'szynka_z_kurczaka',
|
||||
name: 'Szynka z kurczaka',
|
||||
category: 'mieso_ryby',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 100, label: 'opakowanie 100 g' },
|
||||
nutritionPer100g: { kcal: 105, protein: 19.5, fat: 2, carbs: 1.5 },
|
||||
},
|
||||
losos_wedzony: {
|
||||
id: 'losos_wedzony',
|
||||
name: 'Łosoś wędzony',
|
||||
category: 'mieso_ryby',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 100, label: 'opakowanie 100 g' },
|
||||
nutritionPer100g: { kcal: 150, protein: 20, fat: 7, carbs: 0 },
|
||||
},
|
||||
/* ── Warzywa ──────────────────────────────────────── */
|
||||
pomidor: {
|
||||
id: 'pomidor',
|
||||
name: 'Pomidor',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'szt',
|
||||
nutritionPer100g: { kcal: 18, protein: 0.9, fat: 0.2, carbs: 3.9 },
|
||||
},
|
||||
pomidorki_koktajlowe: {
|
||||
id: 'pomidorki_koktajlowe',
|
||||
name: 'Pomidorki koktajlowe',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 250, label: 'opakowanie 250 g' },
|
||||
nutritionPer100g: { kcal: 18, protein: 0.9, fat: 0.2, carbs: 3.9 },
|
||||
},
|
||||
papryka_czerwona: {
|
||||
id: 'papryka_czerwona',
|
||||
name: 'Papryka czerwona',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'szt',
|
||||
nutritionPer100g: { kcal: 31, protein: 1, fat: 0.3, carbs: 6 },
|
||||
},
|
||||
ogorek: {
|
||||
id: 'ogorek',
|
||||
name: 'Ogórek',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'szt',
|
||||
nutritionPer100g: { kcal: 15, protein: 0.7, fat: 0.1, carbs: 3 },
|
||||
},
|
||||
czosnek: {
|
||||
id: 'czosnek',
|
||||
name: 'Czosnek',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'szt',
|
||||
nutritionPer100g: { kcal: 149, protein: 6.4, fat: 0.5, carbs: 33 },
|
||||
},
|
||||
kielki_rzodkiewki: {
|
||||
id: 'kielki_rzodkiewki',
|
||||
name: 'Kiełki rzodkiewki',
|
||||
category: 'warzywa',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 28, protein: 3, fat: 0.6, carbs: 3.5 },
|
||||
},
|
||||
/* ── Owoce ────────────────────────────────────────── */
|
||||
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 },
|
||||
},
|
||||
/* ── Suche i kasze ────────────────────────────────── */
|
||||
makaron_suchy: {
|
||||
id: 'makaron_suchy',
|
||||
name: 'Makaron',
|
||||
category: 'suche',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 371, protein: 13, fat: 1.5, carbs: 74 },
|
||||
},
|
||||
nasiona_slonecznika: {
|
||||
id: 'nasiona_slonecznika',
|
||||
name: 'Nasiona słonecznika',
|
||||
category: 'suche',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 584, protein: 21, fat: 51, carbs: 20 },
|
||||
},
|
||||
orzechy_wloskie: {
|
||||
id: 'orzechy_wloskie',
|
||||
name: 'Orzechy włoskie',
|
||||
@@ -215,202 +221,192 @@ export const INGREDIENTS = {
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 691, protein: 9, fat: 72, carbs: 14 },
|
||||
},
|
||||
truskawki: {
|
||||
id: 'truskawki',
|
||||
name: 'Truskawki',
|
||||
category: 'owoce',
|
||||
/* ── Przyprawy i zioła ────────────────────────────── */
|
||||
bazylia_swieza: {
|
||||
id: 'bazylia_swieza',
|
||||
name: 'Bazylia świeża',
|
||||
category: 'przyprawy',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 32, protein: 0.7, fat: 0.3, carbs: 8 },
|
||||
nutritionPer100g: { kcal: 23, protein: 3.2, fat: 0.6, carbs: 2.7 },
|
||||
},
|
||||
borowki_amerykanskie: {
|
||||
id: 'borowki_amerykanskie',
|
||||
name: 'Borówki amerykańskie',
|
||||
category: 'owoce',
|
||||
koper_swiezy: {
|
||||
id: 'koper_swiezy',
|
||||
name: 'Koper',
|
||||
category: 'przyprawy',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 57, protein: 0.7, fat: 0.3, carbs: 14 },
|
||||
nutritionPer100g: { kcal: 43, protein: 3.5, fat: 1.1, carbs: 7 },
|
||||
},
|
||||
banany: {
|
||||
id: 'banany',
|
||||
name: 'Banany',
|
||||
category: 'owoce',
|
||||
szczypiorek: {
|
||||
id: 'szczypiorek',
|
||||
name: 'Szczypiorek',
|
||||
category: 'przyprawy',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 89, protein: 1.1, fat: 0.3, carbs: 23 },
|
||||
nutritionPer100g: { kcal: 30, protein: 3.3, fat: 0.7, carbs: 1.8 },
|
||||
},
|
||||
jagody: {
|
||||
id: 'jagody',
|
||||
name: 'Jagody',
|
||||
category: 'owoce',
|
||||
tymianek: {
|
||||
id: 'tymianek',
|
||||
name: 'Tymianek suszony',
|
||||
category: 'przyprawy',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 44, protein: 0.7, fat: 0.4, carbs: 10 },
|
||||
nutritionPer100g: { kcal: 276, protein: 9, fat: 7, carbs: 45 },
|
||||
},
|
||||
chrzan: {
|
||||
id: 'chrzan',
|
||||
name: 'Chrzan tarty',
|
||||
category: 'przyprawy',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 44, protein: 1, fat: 0.5, carbs: 8 },
|
||||
},
|
||||
/* ── Inne ─────────────────────────────────────────── */
|
||||
miod: {
|
||||
id: 'miod',
|
||||
name: 'Miód',
|
||||
category: 'inne',
|
||||
pantryUnit: 'g',
|
||||
nutritionPer100g: { kcal: 304, protein: 0.3, fat: 0, carbs: 82 },
|
||||
},
|
||||
oliwa: {
|
||||
id: 'oliwa',
|
||||
name: 'Oliwa z oliwek',
|
||||
category: 'inne',
|
||||
pantryUnit: 'ml',
|
||||
nutritionPer100g: { kcal: 884, protein: 0, fat: 100, carbs: 0 },
|
||||
},
|
||||
hummus: {
|
||||
id: 'hummus',
|
||||
name: 'Hummus',
|
||||
category: 'inne',
|
||||
pantryUnit: 'g',
|
||||
purchasePack: { amount: 200, label: 'opakowanie 200 g' },
|
||||
nutritionPer100g: { kcal: 166, protein: 8, fat: 10, carbs: 14 },
|
||||
},
|
||||
};
|
||||
|
||||
/** 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.',
|
||||
kanapka_parmenska: {
|
||||
id: 'kanapka_parmenska',
|
||||
title: 'Kanapka z szynką parmeńską i mozzarellą',
|
||||
description: 'Bułka grahamka z szynką parmeńską, mozzarellą i pomidorkami — włoskie smaki na szybko.',
|
||||
minutes: 5,
|
||||
thumbLabel: 'Koktajl',
|
||||
allowedSlots: ['przekaska', 'drugie_sniadanie'],
|
||||
tags: ['wegetariańskie', 'szybkie'],
|
||||
nutritionPerServing: { kcal: 180, protein: 8, fat: 3, carbs: 32 },
|
||||
thumbLabel: 'Parmeńska',
|
||||
image: 'images/recipes/kanapka_parmenska.jpg',
|
||||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'kolacja'],
|
||||
tags: ['szybkie'],
|
||||
nutritionPerServing: { kcal: 606, protein: 47, fat: 29, carbs: 39 },
|
||||
ingredients: [
|
||||
{ ingredientId: 'jogurt_naturalny', amount: 200, unit: 'g' },
|
||||
{ ingredientId: 'mieszanka_jagod', amount: 150, unit: 'g' },
|
||||
{ ingredientId: 'miod', amount: 15, unit: 'g' },
|
||||
{ ingredientId: 'bulka_grahamka', amount: 1, unit: 'szt.' },
|
||||
{ ingredientId: 'szynka_parmenska', amount: 95, unit: 'g' },
|
||||
{ ingredientId: 'mozzarella', amount: 60, unit: 'g' },
|
||||
{ ingredientId: 'pomidorki_koktajlowe', amount: 100, unit: 'g' },
|
||||
],
|
||||
steps: [
|
||||
'Wrzuć jogurt, jagody i miód do blendera.',
|
||||
'Zmiksuj na gładką masę (~30 sekund).',
|
||||
'Przelej do szklanki. Gotowe!',
|
||||
'Bułkę grahamkę przekrój na pół.',
|
||||
'Na bułce ułóż plastry szynki parmeńskiej, na nią pokrojoną mozzarellę.',
|
||||
'Podawaj z pomidorkami koktajlowymi.',
|
||||
],
|
||||
},
|
||||
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.',
|
||||
makaron_ricotta: {
|
||||
id: 'makaron_ricotta',
|
||||
title: 'Makaron z ricottą i pomidorami',
|
||||
description: 'Makaron z sosem z pieczonych pomidorków koktajlowych, ricottą i słonecznikiem.',
|
||||
minutes: 20,
|
||||
thumbLabel: 'Tacos',
|
||||
allowedSlots: ['kolacja', 'obiad'],
|
||||
tags: [],
|
||||
nutritionPerServing: { kcal: 410, protein: 28, fat: 18, carbs: 38 },
|
||||
thumbLabel: 'Ricotta',
|
||||
image: 'images/recipes/makaron_ricotta.jpg',
|
||||
allowedSlots: ['obiad', 'kolacja'],
|
||||
tags: ['wegetariańskie'],
|
||||
nutritionPerServing: { kcal: 608, protein: 24, fat: 24, carbs: 75 },
|
||||
ingredients: [
|
||||
{ ingredientId: 'mieso_wol_mielone', amount: 200, unit: 'g' },
|
||||
{ ingredientId: 'tortilla_kukurydziana', amount: 4, unit: 'szt.' },
|
||||
{ ingredientId: 'salsa_pomidorowa', amount: 100, unit: 'g' },
|
||||
{ ingredientId: 'makaron_suchy', amount: 80, unit: 'g' },
|
||||
{ ingredientId: 'pomidorki_koktajlowe', amount: 200, unit: 'g' },
|
||||
{ ingredientId: 'czosnek', amount: 6, unit: 'g' },
|
||||
{ ingredientId: 'tymianek', amount: 1, unit: 'g' },
|
||||
{ ingredientId: 'oliwa', amount: 5, unit: 'ml' },
|
||||
{ ingredientId: 'ricotta', amount: 75, unit: 'g' },
|
||||
{ ingredientId: 'bazylia_swieza', amount: 3, unit: 'g' },
|
||||
{ ingredientId: 'nasiona_slonecznika', amount: 15, 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ń.',
|
||||
'Makaron ugotuj wg przepisu na opakowaniu. Po ugotowaniu pozostaw 1–2 łyżki wody.',
|
||||
'Na patelni rozgrzej oliwę, dodaj przeciśnięty czosnek, przekrojone na pół pomidorki i tymianek — podsmażaj 2–3 minuty.',
|
||||
'Na patelnię z sosem dodaj ugotowany makaron. Wymieszaj, przypraw solą i pieprzem.',
|
||||
'Ricottę wymieszaj z 1–2 łyżkami wody z gotowania makaronu. Nałóż na makaron.',
|
||||
'Posyp bazylią świeżą i nasionami słonecznika.',
|
||||
],
|
||||
},
|
||||
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 },
|
||||
jajecznica: {
|
||||
id: 'jajecznica',
|
||||
title: 'Jajecznica z pieczywem',
|
||||
description: 'Klasyczna jajecznica z 4 jajek z bułką grahamką i szczypiorkiem.',
|
||||
minutes: 5,
|
||||
thumbLabel: 'Jajecznica',
|
||||
image: 'images/recipes/jajecznica.png',
|
||||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'kolacja'],
|
||||
tags: ['szybkie', 'wysokobiałkowe'],
|
||||
nutritionPerServing: { kcal: 548, protein: 36, fat: 28, carbs: 36 },
|
||||
ingredients: [
|
||||
{ ingredientId: 'platki_owsiane', amount: 60, unit: 'g' },
|
||||
{ ingredientId: 'mleko', amount: 200, unit: 'ml' },
|
||||
{ ingredientId: 'miod', amount: 20, unit: 'g' },
|
||||
{ ingredientId: 'jajko', amount: 4, unit: 'szt.' },
|
||||
{ ingredientId: 'oliwa', amount: 5, unit: 'ml' },
|
||||
{ ingredientId: 'bulka_grahamka', amount: 1, unit: 'szt.' },
|
||||
{ ingredientId: 'szczypiorek', amount: 5, 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.',
|
||||
'Na patelni rozgrzej oliwę na małej mocy palnika.',
|
||||
'Wbij jajka bezpośrednio na patelnię. Smaż bez przykrycia, delikatnie mieszając łopatką, aż żółtka i białka stopniowo się połączą.',
|
||||
'Dopraw solą i pieprzem. Kontynuuj smażenie, aż jajka się zetną, ale pozostaną lekko kremowe — ok. 3–4 minuty.',
|
||||
'Posyp posiekanym szczypiorkiem i podawaj z bułką grahamką.',
|
||||
],
|
||||
},
|
||||
kanapka_hummus: {
|
||||
id: 'kanapka_hummus',
|
||||
title: 'Kanapka z hummusem, wędliną i warzywami',
|
||||
description: 'Bułka grahamka z hummusem, szynką z kurczaka i świeżymi warzywami.',
|
||||
minutes: 5,
|
||||
thumbLabel: 'Hummus',
|
||||
image: 'images/recipes/kanapka_hummus.png',
|
||||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'kolacja'],
|
||||
tags: ['szybkie', 'wysokobiałkowe'],
|
||||
nutritionPerServing: { kcal: 609, protein: 46, fat: 19, carbs: 66 },
|
||||
ingredients: [
|
||||
{ ingredientId: 'bulka_grahamka', amount: 1, unit: 'szt.' },
|
||||
{ ingredientId: 'szynka_z_kurczaka', amount: 130, unit: 'g' },
|
||||
{ ingredientId: 'pomidor', amount: 80, unit: 'g' },
|
||||
{ ingredientId: 'papryka_czerwona', amount: 85, unit: 'g' },
|
||||
{ ingredientId: 'ogorek', amount: 75, unit: 'g' },
|
||||
{ ingredientId: 'szczypiorek', amount: 20, unit: 'g' },
|
||||
{ ingredientId: 'hummus', amount: 140, unit: 'g' },
|
||||
],
|
||||
steps: [
|
||||
'Pomidora i ogórka pokrój w plastry. Paprykę pokrój w paski. Szczypiorek posiekaj.',
|
||||
'Bułkę grahamkę przekrój i posmaruj hummusem.',
|
||||
'Na bułce ułóż szynkę z kurczaka, pomidora, paprykę i ogórka.',
|
||||
'Posyp szczypiorkiem i podawaj.',
|
||||
],
|
||||
},
|
||||
kanapka_losos: {
|
||||
id: 'kanapka_losos',
|
||||
title: 'Kanapka z wędzonym łososiem',
|
||||
description: 'Bułka grahamka z łososiem wędzonym, pastą chrzanowo-serową i kiełkami.',
|
||||
minutes: 5,
|
||||
thumbLabel: 'Łosoś',
|
||||
image: 'images/recipes/kanapka_losos.jpg',
|
||||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'kolacja'],
|
||||
tags: ['szybkie'],
|
||||
nutritionPerServing: { kcal: 443, protein: 30, fat: 18, carbs: 39 },
|
||||
ingredients: [
|
||||
{ ingredientId: 'bulka_grahamka', amount: 1, unit: 'szt.' },
|
||||
{ ingredientId: 'losos_wedzony', amount: 100, unit: 'g' },
|
||||
{ ingredientId: 'serek_smietankowy', amount: 40, unit: 'g' },
|
||||
{ ingredientId: 'chrzan', amount: 10, unit: 'g' },
|
||||
{ ingredientId: 'koper_swiezy', amount: 5, unit: 'g' },
|
||||
{ ingredientId: 'kielki_rzodkiewki', amount: 5, unit: 'g' },
|
||||
{ ingredientId: 'ogorek', amount: 75, unit: 'g' },
|
||||
],
|
||||
steps: [
|
||||
'Bułkę grahamkę przekrój i podsmaż na patelni na średnim ogniu przez 2–3 minuty.',
|
||||
'Chrzan dokładnie wymieszaj z serkiem śmietankowym.',
|
||||
'Koperek drobno posiekaj. Ogórka pokrój na mniejsze kawałki.',
|
||||
'Na bułce rozsmaruj pastę chrzanowo-serową. Ułóż łososia, koperek, ogórka i kiełki.',
|
||||
],
|
||||
},
|
||||
serek_owoc: {
|
||||
@@ -419,6 +415,7 @@ export const RECIPES = {
|
||||
description: 'Lekki, pożywny posiłek: serek z orzechami, truskawkami i borówkami.',
|
||||
minutes: 5,
|
||||
thumbLabel: 'Serek',
|
||||
image: 'images/recipes/serek_owoc.jpg',
|
||||
allowedSlots: ['sniadanie', 'drugie_sniadanie', 'przekaska'],
|
||||
tags: ['wegetariańskie', 'wysokobiałkowe', 'szybkie'],
|
||||
nutritionPerServing: { kcal: 642, protein: 32, fat: 43, carbs: 41 },
|
||||
|
||||
@@ -523,8 +523,10 @@ function renderDayContent(state) {
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-2 shadow-sm" data-slot-id="${slot.id}" data-entry-id="${eid}">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0 cursor-pointer planner-open-recipe" data-recipe-id="${escapeHtml(recipe.id)}">
|
||||
<div class="w-8 h-8 rounded-lg bg-[#d4d4d4] flex items-center justify-center shrink-0">
|
||||
<span class="text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>
|
||||
<div class="w-8 h-8 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
|
||||
${recipe.image
|
||||
? `<img src="${escapeHtml(recipe.image)}" alt="" class="w-full h-full object-cover">`
|
||||
: `<span class="w-full h-full flex items-center justify-center text-white text-[8px] font-medium">${escapeHtml(recipe.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-[13px] font-bold text-gray-900 truncate underline decoration-1 underline-offset-2">${escapeHtml(recipe.title)}</p>
|
||||
@@ -629,8 +631,10 @@ function getRecentRecipeIds(plans, limit = 5) {
|
||||
function recipeCardHtml(r) {
|
||||
return `
|
||||
<button type="button" class="planner-pick-recipe w-full flex gap-2.5 p-2.5 rounded-xl border border-gray-200 bg-gray-50/80 hover:border-gray-900 hover:bg-white text-left transition-all" data-recipe-id="${r.id}">
|
||||
<div class="w-11 h-11 rounded-lg bg-[#d4d4d4] flex items-center justify-center shrink-0">
|
||||
<span class="text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>
|
||||
<div class="w-11 h-11 rounded-lg bg-[#d4d4d4] overflow-hidden shrink-0">
|
||||
${r.image
|
||||
? `<img src="${escapeHtml(r.image)}" alt="" class="w-full h-full object-cover">`
|
||||
: `<span class="w-full h-full flex items-center justify-center text-white text-[9px] font-medium">${escapeHtml(r.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 py-0.5">
|
||||
<p class="text-[13px] font-bold text-gray-900 line-clamp-2">${escapeHtml(r.title)}</p>
|
||||
@@ -872,9 +876,9 @@ function seedDemoIfEmpty(plans) {
|
||||
return {
|
||||
...plans,
|
||||
[todayKey]: {
|
||||
sniadanie: [{ id: newPlanEntryId(), recipeId: 'owsianka', servings: 1 }],
|
||||
obiad: [{ id: newPlanEntryId(), recipeId: 'salatka', servings: 1 }],
|
||||
kolacja: [{ id: newPlanEntryId(), recipeId: 'makaron', servings: 1 }],
|
||||
sniadanie: [{ id: newPlanEntryId(), recipeId: 'jajecznica', servings: 1 }],
|
||||
obiad: [{ id: newPlanEntryId(), recipeId: 'makaron_ricotta', servings: 1 }],
|
||||
kolacja: [{ id: newPlanEntryId(), recipeId: 'kanapka_losos', servings: 1 }],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,8 +67,9 @@ export function getRecipeDetailHTML() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rd-hero" class="h-[220px] shrink-0 w-full bg-[#d4d4d4] flex items-center justify-center relative">
|
||||
<span id="rd-hero-label" class="text-white font-medium text-[15px]"></span>
|
||||
<div id="rd-hero" class="h-[220px] shrink-0 w-full bg-[#d4d4d4] relative overflow-hidden">
|
||||
<img id="rd-hero-img" src="" alt="" class="w-full h-full object-cover hidden">
|
||||
<span id="rd-hero-label" class="absolute inset-0 flex items-center justify-center text-white font-medium text-[15px]"></span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-t-3xl -mt-6 relative z-30 pt-6 flex flex-col flex-1 overflow-hidden">
|
||||
@@ -128,7 +129,18 @@ function populateDetail(recipeId) {
|
||||
currentSubstitutions = {};
|
||||
expandedAlternatives.clear();
|
||||
|
||||
document.getElementById('rd-hero-label').textContent = `Zdjęcie: ${recipe.title}`;
|
||||
const heroImg = document.getElementById('rd-hero-img');
|
||||
const heroLabel = document.getElementById('rd-hero-label');
|
||||
if (recipe.image) {
|
||||
heroImg.src = recipe.image;
|
||||
heroImg.alt = recipe.title;
|
||||
heroImg.classList.remove('hidden');
|
||||
heroLabel.textContent = '';
|
||||
} else {
|
||||
heroImg.classList.add('hidden');
|
||||
heroImg.src = '';
|
||||
heroLabel.textContent = `Zdjęcie: ${recipe.title}`;
|
||||
}
|
||||
document.getElementById('rd-title').textContent = recipe.title;
|
||||
document.getElementById('rd-time').textContent = `${recipe.minutes} min`;
|
||||
updateKcalDisplay();
|
||||
@@ -190,10 +202,11 @@ function renderNutritionLine(nutrition) {
|
||||
|
||||
/* ── ingredients tab with inline nutrition + summary ───── */
|
||||
|
||||
function computeIngredientNutritionTotals(recipe) {
|
||||
function computeEffectiveNutritionTotals(recipe) {
|
||||
let kcal = 0, protein = 0, fat = 0, carbs = 0;
|
||||
for (const ing of recipe.ingredients) {
|
||||
const n = nutritionForAmount(ing.ingredientId, ing.amount * currentServings);
|
||||
const effectiveId = getEffectiveIngredientId(ing.ingredientId);
|
||||
const n = nutritionForAmount(effectiveId, ing.amount * currentServings);
|
||||
if (n) {
|
||||
kcal += n.kcal;
|
||||
protein += n.protein;
|
||||
@@ -210,14 +223,8 @@ function computeIngredientNutritionTotals(recipe) {
|
||||
}
|
||||
|
||||
function renderNutritionSummary(recipe) {
|
||||
const n = recipe.nutritionPerServing;
|
||||
const s = currentServings;
|
||||
const total = {
|
||||
kcal: Math.round(n.kcal * s * 10) / 10,
|
||||
protein: Math.round(n.protein * s * 10) / 10,
|
||||
fat: Math.round(n.fat * s * 10) / 10,
|
||||
carbs: Math.round(n.carbs * s * 10) / 10,
|
||||
};
|
||||
const total = computeEffectiveNutritionTotals(recipe);
|
||||
|
||||
return `
|
||||
<div class="mb-4 pt-1 pb-3 border-b border-gray-100">
|
||||
@@ -255,7 +262,7 @@ function renderIngredientCard(name, amount, unit, nutrition, extra) {
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="${extra?.cls || 'bg-white'} rounded-xl p-2.5 border ${extra?.border || 'border-gray-200'} flex items-center gap-2.5">
|
||||
<div class="${extra?.cls || 'bg-white'} rounded-xl p-2.5 border ${extra?.border || 'border-gray-200'} flex items-center gap-2.5" ${extra?.dataAttrs || ''}>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
${extra?.prefix || ''}
|
||||
@@ -277,35 +284,52 @@ function renderIngredients(recipe) {
|
||||
if (!container) return;
|
||||
|
||||
const rows = recipe.ingredients.map((ing) => {
|
||||
const def = INGREDIENTS[ing.ingredientId];
|
||||
const name = def?.name || ing.ingredientId;
|
||||
const scaledAmount = ing.amount * currentServings;
|
||||
const nutrition = nutritionForAmount(ing.ingredientId, scaledAmount);
|
||||
|
||||
const origId = ing.ingredientId;
|
||||
const hasAlts = ing.alternatives && ing.alternatives.length > 0;
|
||||
const isExpanded = expandedAlternatives.has(ing.ingredientId);
|
||||
const effectiveId = hasAlts ? getEffectiveIngredientId(origId) : origId;
|
||||
const effectiveDef = INGREDIENTS[effectiveId];
|
||||
const effectiveName = effectiveDef?.name || effectiveId;
|
||||
const scaledAmount = ing.amount * currentServings;
|
||||
const nutrition = nutritionForAmount(effectiveId, scaledAmount);
|
||||
const isSwapped = effectiveId !== origId;
|
||||
const isExpanded = expandedAlternatives.has(origId);
|
||||
|
||||
const toggleBtn = hasAlts
|
||||
? `<button type="button" class="rd-alt-toggle shrink-0 w-5 h-5 rounded-full ${isExpanded ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-original-id="${escapeHtml(ing.ingredientId)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
||||
? `<button type="button" class="rd-alt-toggle shrink-0 w-5 h-5 rounded-full ${isExpanded ? 'bg-amber-50 text-amber-500' : isSwapped ? 'bg-amber-50 text-amber-500' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'} flex items-center justify-center transition-colors" data-original-id="${escapeHtml(origId)}"><i class="fas fa-shuffle text-[8px]"></i></button>`
|
||||
: '';
|
||||
|
||||
const cardHtml = renderIngredientCard(name, scaledAmount, ing.unit, nutrition, { suffix: toggleBtn });
|
||||
const cardBorder = isSwapped ? 'border-amber-200' : 'border-gray-200';
|
||||
const cardCls = isSwapped ? 'bg-amber-50/30' : 'bg-white';
|
||||
const cardHtml = renderIngredientCard(effectiveName, scaledAmount, ing.unit, nutrition, {
|
||||
suffix: toggleBtn,
|
||||
border: cardBorder,
|
||||
cls: cardCls,
|
||||
});
|
||||
|
||||
let altListHtml = '';
|
||||
if (hasAlts) {
|
||||
const altCards = ing.alternatives.map((altId) => {
|
||||
const altDef = INGREDIENTS[altId];
|
||||
const altName = altDef?.name || altId;
|
||||
if (hasAlts && isExpanded) {
|
||||
const allOptions = [origId, ...ing.alternatives];
|
||||
const optionCards = allOptions.map((altId) => {
|
||||
const def = INGREDIENTS[altId];
|
||||
const altName = def?.name || altId;
|
||||
const isSelected = effectiveId === altId;
|
||||
const isOriginal = altId === origId;
|
||||
const altNutrition = nutritionForAmount(altId, scaledAmount);
|
||||
|
||||
const radioDot = `<div class="w-3.5 h-3.5 rounded-full border-2 shrink-0 ${isSelected ? 'border-gray-900' : 'border-gray-300'} flex items-center justify-center">${isSelected ? '<div class="w-1.5 h-1.5 rounded-full bg-gray-900"></div>' : ''}</div>`;
|
||||
const defaultTag = isOriginal ? `<span class="text-[9px] px-1.5 py-0.5 rounded ${isSelected ? 'bg-gray-200 text-gray-600' : 'bg-gray-100 text-gray-400'} font-medium ml-1">Domyślny</span>` : '';
|
||||
|
||||
return renderIngredientCard(altName, scaledAmount, ing.unit, altNutrition, {
|
||||
cls: 'bg-gray-50',
|
||||
border: 'border-gray-100',
|
||||
cls: isSelected ? 'bg-gray-50' : 'bg-white hover:bg-gray-50 cursor-pointer',
|
||||
border: isSelected ? 'border-gray-900 ring-1 ring-gray-900' : 'border-gray-100',
|
||||
prefix: radioDot,
|
||||
badge: defaultTag,
|
||||
dataAttrs: `data-original-id="${escapeHtml(origId)}" data-alt-id="${escapeHtml(altId)}"`,
|
||||
});
|
||||
});
|
||||
altListHtml = `
|
||||
<div class="rd-alt-list ${isExpanded ? '' : 'hidden'} mt-1.5 space-y-1.5" data-original-id="${escapeHtml(ing.ingredientId)}">
|
||||
<p class="text-[10px] text-gray-400 font-medium mb-1 pl-1">Można zamienić na:</p>
|
||||
${altCards.join('')}
|
||||
<div class="mt-1.5 space-y-1.5 rd-alt-options" data-original-id="${escapeHtml(origId)}">
|
||||
${optionCards.join('')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -324,12 +348,23 @@ function renderIngredients(recipe) {
|
||||
} else {
|
||||
expandedAlternatives.add(origId);
|
||||
}
|
||||
const list = container.querySelector(`.rd-alt-list[data-original-id="${origId}"]`);
|
||||
if (list) list.classList.toggle('hidden');
|
||||
btn.classList.toggle('bg-gray-100');
|
||||
btn.classList.toggle('text-gray-400');
|
||||
btn.classList.toggle('bg-amber-50');
|
||||
btn.classList.toggle('text-amber-500');
|
||||
renderIngredients(recipe);
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelectorAll('.rd-alt-options').forEach((group) => {
|
||||
group.querySelectorAll('[data-alt-id]').forEach((card) => {
|
||||
card.addEventListener('click', () => {
|
||||
const originalId = card.dataset.originalId;
|
||||
const altId = card.dataset.altId;
|
||||
if (altId === originalId) {
|
||||
delete currentSubstitutions[originalId];
|
||||
} else {
|
||||
currentSubstitutions[originalId] = altId;
|
||||
}
|
||||
expandedAlternatives.delete(originalId);
|
||||
renderIngredients(recipe);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -625,7 +660,6 @@ export function setupRecipeDetail() {
|
||||
if (!recipe) return;
|
||||
|
||||
document.getElementById('rd-planner-recipe-name').textContent = recipe.title;
|
||||
currentSubstitutions = {};
|
||||
expandedVariantGroups.clear();
|
||||
|
||||
const today = startOfDay(new Date());
|
||||
|
||||
@@ -55,8 +55,10 @@ function renderRecipeCard(recipe) {
|
||||
const labels = slotLabelsFor(recipe);
|
||||
return `
|
||||
<div onclick="openRecipeDetail('${escapeHtml(recipe.id)}')" class="border border-gray-200 rounded-xl overflow-hidden shadow-sm flex flex-col bg-white cursor-pointer hover:shadow-md transition-shadow">
|
||||
<div class="h-32 bg-[#d4d4d4] relative flex items-center justify-center">
|
||||
<span class="text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>
|
||||
<div class="h-32 bg-[#d4d4d4] relative overflow-hidden">
|
||||
${recipe.image
|
||||
? `<img src="${escapeHtml(recipe.image)}" alt="${escapeHtml(recipe.title)}" class="w-full h-full object-cover">`
|
||||
: `<span class="absolute inset-0 flex items-center justify-center text-white font-medium text-xs">${escapeHtml(recipe.thumbLabel)}</span>`}
|
||||
</div>
|
||||
<div class="p-3 flex flex-col flex-1">
|
||||
<h3 class="text-sm font-medium underline decoration-1 underline-offset-2 text-black mb-1 line-clamp-1">${escapeHtml(recipe.title)}</h3>
|
||||
|
||||
Reference in New Issue
Block a user