diff --git a/VIEWS_AND_SCENARIOS.md b/VIEWS_AND_SCENARIOS.md index d49b44e..2c48bcd 100644 --- a/VIEWS_AND_SCENARIOS.md +++ b/VIEWS_AND_SCENARIOS.md @@ -1,6 +1,6 @@ # Widoki i scenariusze — Aplikacja Kuchenna -> **Cel dokumentu:** Opis wszystkich widoków aplikacji z obecnym stanem, scenariusze użytkownika i znane problemy. Odniesienie dla dalszego rozwoju. +> **Cel dokumentu:** Opis widoków, przepływów i scenariuszy użytkownika. Odniesienie dla dalszego rozwoju. > **Kontekst projektu:** To jest **prototyp / mockup** — celem jest wypracowanie UX, logiki widoków i przepływów użytkownika. Finalna aplikacja będzie pisana w innym języku z backendem. Wartość tego prototypu to przede wszystkim: struktura widoków, scenariusze, model danych i decyzje UX — nie kod sam w sobie. @@ -11,14 +11,15 @@ | Cecha | Opis | |-------|------| | Liczba osób | 1 (gotuje dla siebie) | -| Planowanie | Elastyczne, 1–7 dni do przodu | +| Planowanie | Mieszane — część z wyprzedzeniem (np. niedziela na tydzień), część ad hoc na bieżąco | | Posiłki | 5 slotów (śniadanie, drugie śniadanie, obiad, przekąska, kolacja) | -| Powtarzalność | Duża (zwłaszcza śniadania) — kopiowanie dnia kluczowe | -| Styl gotowania | Hybrydowy: trochę meal-prep, trochę na bieżąco | -| Zakupy | Mieszane: duże zakupy + uzupełnienia w tygodniu | -| Cel dietetyczny | Utrzymanie wagi, śledzenie per posiłek | -| Pomijanie posiłków | Jawne "Pomijam" (jedzenie na mieście itp.) | -| Przepisy | Katalog wbudowany (9 przepisów, 24 składniki), bez edytora | +| Powtarzalność | Duża (zwłaszcza śniadania), ale chce stopniowo urozmaicać | +| Styl gotowania | Hybrydowy: głównie na bieżąco, ale chce zacząć gotować na zapas (np. zupy na kilka dni) | +| Jedzenie poza domem | 2–3× w tygodniu (restauracja, kantyna, zamówienie) — stąd jawne "Pomijam" | +| Zakupy | Duże zakupy raz w tygodniu + drobne uzupełnienia | +| Cel dietetyczny | Świadomość makro — chce widzieć wartości, ale nie liczy co do grama | +| Bóle | Brak inspiracji ("co ugotować?"), marnowanie jedzenia, brak czasu w tygodniu, chaotyczne zakupy | +| Przepisy | Katalog wbudowany (6 przepisów, 34 składniki), bez edytora | --- @@ -45,7 +46,8 @@ js/ views/ RecipeList.js ← lista przepisów Filter.js ← overlay filtrów - RecipeDetail.js ← detal przepisu (slide-in overlay) + RecipeDetailV2.js ← detal przepisu (aktywna wersja) + RecipeDetail.js ← detal przepisu (oryginał, nieużywany — 3-zakładkowy) MealPlanner.js ← planer posiłków + kalendarz Pantry.js ← spiżarnia Shopping.js ← listy zakupów @@ -68,128 +70,42 @@ js/ ### 3.1 Przepisy (`RecipeList`) -**Lokalizacja:** `js/views/RecipeList.js` +Siatka 2-kolumnowa kart przepisów z wyszukiwarką i filtrami. Kliknięcie karty otwiera detal. Filtry (overlay `Filter.js`): pory posiłku, tagi dietetyczne, suwak czasu. -Siatka 2-kolumnowa kart przepisów generowana dynamicznie z `RECIPES`. Każda karta zawiera miniaturę (placeholder), tytuł, opis, czas przygotowania, kalorie, chipy slotów. Kliknięcie otwiera detal. +### 3.2 Szczegóły przepisu (`RecipeDetailV2`) -**Elementy:** -- Wyszukiwarka (real-time, po tytule i tagach) -- Przycisk filtrów (otwiera overlay) -- Siatka kart -- Empty state gdy brak wyników +Slide-in overlay z detalami przepisu. Dwie zakładki: **Składniki** i **Kroki**. -**Stan:** Kompletny. +**Zakładka Składniki:** +- Podsumowanie wartości odżywczych na górze (4-kolumnowa siatka: kalorie, białko, węgle, tłuszcze) — przeliczane dynamicznie z uwzględnieniem wybranych zamienników +- Składniki jako karty z wartościami odżywczymi per składnik (makro + kcal po prawej, obok gramów) +- **Wymienne składniki** — kliknięcie ikony shuffle rozwija listę opcji (oryginał + alternatywy) z radio-przyciskami; wybranie zamiennika: karta zmienia się na wybraną opcję (amber obramowanie), podsumowanie kaloryczne przelicza się na żywo +- Selektor porcji (±, zakres 1–12) przelicza składniki i wartości ---- +**Bottom sheet "Zaplanuj":** +1. Kalendarz (tydzień/miesiąc, nawigacja ←/→/Dziś) +2. Pora posiłku — chipy filtrowane do `allowedSlots` przepisu +3. Wymienne składniki — **wstępnie ustawione z wyborów na liście składników**, z możliwością dalszej zmiany +4. Przycisk "Dodaj" → zapis do `planStore` (z opcjonalnym obiektem `substitutions`) -### 3.2 Filtry (`Filter`) - -**Lokalizacja:** `js/views/Filter.js` - -Full-screen overlay (z-50) z chipami pór posiłku, tagami dietetycznymi i suwakiem czasu. Filtr zamknięty przyciskiem ← odrzuca niezapisane zmiany; "Pokaż X wyników" aplikuje i zamyka. - -**Elementy:** -- Chipy: pory posiłku (z `MEAL_SLOTS`) -- Chipy: tagi dietetyczne (zbierane dynamicznie z `RECIPES`) -- Suwak: maksymalny czas przygotowania (5–120 min) -- Przycisk "Wyczyść" + "Pokaż X wyników" - -**Stan:** Kompletny. - ---- - -### 3.3 Szczegóły przepisu (`RecipeDetail`) - -**Lokalizacja:** `js/views/RecipeDetail.js` - -Slide-in overlay z detalami przepisu. Trzy zakładki: Składniki, Kroki, Wartości. - -**Elementy:** -- Hero (placeholder) + strzałka powrotu + przycisk "Zaplanuj" -- Tytuł, tagi (sloty + tagi przepisu), czas, kcal -- Selektor porcji (± , zakres 1–12) z przeliczaniem składników i wartości -- Zakładka Składniki: lista read-only (bez checkboxów, bez badge'ów spiżarni). Składniki z wymiennymi wariantami mają ikonę shuffle — kliknięcie rozwija karty z alternatywami i ich wartościami odżywczymi (informacyjnie, bez możliwości wyboru) -- Zakładka Kroki: numerowane kroki -- Zakładka Wartości: kcal/białko/tłuszcze/węglowodany × porcje -- Bottom sheet "Zaplanuj": - 1. Kalendarz (tydzień/miesiąc, nawigacja ←/→/Dziś, toggle rozwinięcia) — styl ujednolicony z `MealPlanner` - 2. Pora posiłku — chipy filtrowane do `allowedSlots` przepisu - 3. Wymienne składniki (opcjonalne, widoczne tylko gdy przepis ma `alternatives`) — kompaktowe karty per składnik wyświetlające aktualny wybór z wartościami odżywczymi; kliknięcie rozwija listę opcji z radio-przyciskami; po wyborze karta się zwija; zmieniony składnik ma amber tło - 4. Przycisk "Dodaj" → zapis do `planStore` (z opcjonalnym obiektem `substitutions`) +> **Uwaga:** Istnieje starsza wersja (`RecipeDetail.js`) z 3 zakładkami (Składniki, Kroki, Wartości) i read-only alternatywami. Aktualnie nieużywana — import w `app.js` wskazuje na `RecipeDetailV2.js`. **Model danych — wymienne składniki:** -- W `RECIPES`, składnik może mieć pole `alternatives: ['id1', 'id2', ...]` — tablica ID alternatywnych składników -- Przy dodawaniu do planera, wybrane zamienniki zapisywane są jako `substitutions: { originalId: chosenAltId }` w `planStore` +- W `RECIPES`, składnik może mieć pole `alternatives: ['id1', 'id2', ...]` +- Wybrane zamienniki zapisywane jako `substitutions: { originalId: chosenAltId }` w `planStore` - Przykład: serek wiejski ma 3 wymienne składniki — orzechy (5 opcji), truskawki (banany), borówki (jagody) -**Stan:** Kompletny. +### 3.3 Planer posiłków (`MealPlanner`) ---- +Kalendarz (tydzień/miesiąc) + plan dnia z 5 slotami posiłków. Karty przepisów z porcjami (±), kcal, usuwaniem. "Pomijam" przy pustym slocie. Podsumowanie kaloryczne dnia. "Składniki na ten dzień" z porównaniem do spiżarni i prognozą tygodniową. Kopiowanie planu dnia. Picker przepisów do dodawania. Demo-dane przy pustym localStorage. -### 3.4 Planer posiłków (`MealPlanner`) +### 3.4 Spiżarnia (`Pantry`) -**Lokalizacja:** `js/views/MealPlanner.js` +Chipy składników pogrupowane po kategorii z kolorami wg stanu. Wyszukiwarka, filtry kategorii, toggle "Tylko na stanie". Bottom sheet edycji z ± i inputem, wartościami odżywczymi, "Dodaj na listę zakupów". -Kalendarz (tydzień/miesiąc) + plan dnia z 5 slotami posiłków. +### 3.5 Zakupy (`Shopping`) -**Elementy kalendarza:** -- Widok tygodnia ↔ miesiąca (swipe góra/dół na `#calendar-swipe-zone`) -- Nawigacja: ←/→ + "Dziś" -- Kropki pod dniem = zaplanowane posiłki - -**Elementy planu dnia:** -- Nagłówek dnia + przycisk "Kopiuj dzień" -- Karta podsumowania kalorycznego (kcal + makro, rozwijalne szczegóły) -- "Składniki na ten dzień" (badge z liczbą braków vs "OK") -- Sloty posiłków (5 slotów z `MEAL_SLOTS`): - - Kcal per slot w nagłówku - - Karty przepisów z porcjami (±), kcal, czasem, przyciskiem usuwania - - Kliknięcie nazwy przepisu → `RecipeDetail` - - "Dodaj przepis" / "Dodaj kolejny" - - "Pomijam" przy pustym slocie → slot przygaszony z "Cofnij" - -**Bottom sheety:** -1. **Picker przepisów**: wyszukiwarka + sekcja "Ostatnio używane" + lista filtrowana do `allowedSlots` -2. **Składniki i spiżarnia**: porównanie potrzeb vs zapasy + prognoza tygodniowa (`computeFullForecast`) + "Dodaj braki" (dzień / tydzień) -3. **Kopiuj plan dnia**: lista 13 dni (3 wstecz, 10 do przodu) → kopiuje cały dzień (w tym statusy "Pominięto") - -**Demo:** `seedDemoIfEmpty` wypełnia dzisiejszy dzień danymi demo gdy localStorage jest pusty. - -**Stan:** Kompletny. - ---- - -### 3.5 Spiżarnia (`Pantry`) - -**Lokalizacja:** `js/views/Pantry.js` - -Przeglądanie i edycja stanów magazynowych składników. - -**Elementy:** -- Wyszukiwarka -- Chipy filtrów kategorii (multi-select) -- Toggle "Tylko na stanie" -- Siatka chipów składników pogrupowana po kategorii (kolor wg stanu) -- Bottom sheet edycji (`#pv2-edit-sheet`): ± z krokiem (`pantryQtyStep`), input numeryczny, opcjonalne wartości odżywcze (per 100g), "Dodaj na listę zakupów" (z `purchasePack`) - -**Stan:** Kompletny. - ---- - -### 3.6 Zakupy (`Shopping`) - -**Lokalizacja:** `js/views/Shopping.js` - -Zarządzanie listami zakupów — jedna stała lista kuchenna + dowolna liczba list freeform. - -**Elementy:** -- Selektor aktywnej listy (dropdown) -- Przycisk "Nowa lista" (freeform) + usuwanie list (nie dotyczy kuchennej) -- **Lista kuchenna** (`KITCHEN_LIST_ID`): pogrupowana po kategorii, checkbox, edycja ilości (klik → prompt), usuwanie pozycji -- **Pasek akcji** (widoczny gdy są zaznaczone pozycje): "Kupione → spiżarnia" (z potwierdzeniem i podglądem) + "Wyczyść kupione" -- **Lista freeform**: pozycje tekstowe z opcjonalną notatką, checkbox - -**Stan:** Kompletny. +Lista kuchenna (pogrupowana po kategorii, checkboxy, edycja ilości) + listy freeform. Pasek akcji dla zaznaczonych: "Kupione → spiżarnia" (z podglądem) i "Wyczyść kupione". --- @@ -197,7 +113,8 @@ Zarządzanie listami zakupów — jedna stała lista kuchenna + dowolna liczba l ``` Przepisy ──[klik kartę]──→ Szczegóły przepisu - └──[Zaplanuj]──→ Bottom sheet (kalendarz + pora + opcjonalnie wymiana składników) → Planer + ├──[zamiana składnika]──→ podsumowanie przelicza się na żywo + └──[Zaplanuj]──→ Bottom sheet (kalendarz + pora + zamienniki z detalu) → Planer Planer ──[klik przepis w slocie]──→ Szczegóły przepisu ──[Składniki na ten dzień]──→ Sheet: porównanie z spiżarnią + prognoza @@ -214,162 +131,63 @@ Zakupy ──[Kupione → spiżarnia]──→ Spiżarnia (stany zaktualizowane) ## 5. Scenariusze użytkownika -### Scenariusz 1: Przeglądanie przepisów +### Niedzielne planowanie tygodnia -**Cel:** Użytkownik otwiera apkę, chce zobaczyć co jest dostępne. +Wieczorem w niedzielę siada z apką i układa plan na najbliższe 4–5 dni. Kopiuje sprawdzone dni, dodaje nowe przepisy tam gdzie chce urozmaicenie. Przy kilku posiłkach z góry wie, że będzie jeść poza domem — oznacza je jako pominięte. Sprawdza podsumowanie składników na zaplanowane dni, generuje braki na listę zakupów. Rano idzie do sklepu z gotową listą. -1. Otwiera aplikację → widzi zakładkę **Przepisy** z siatką 9 kart -2. Przewija listę, czyta opisy i kalorie na kartach -3. Klika kartę "Serek wiejski z orzechami i owocami" -4. Widzi detal: składniki, kroki, wartości odżywcze -5. Przy orzechach, truskawkach i borówkach widzi ikonę shuffle — klika ją przy orzechach -6. Rozwija się lista alternatyw (laskowe, nerkowca, migdały, pekan) z wartościami odżywczymi — informacyjnie -7. Zmienia liczbę porcji z 1 na 2 → składniki i kcal się przeliczają -8. Przełącza zakładkę na "Kroki" → widzi numerowane kroki -9. Przełącza na "Wartości" → widzi makroskładniki ×2 -10. Wraca strzałką ← do listy +**Co mu to daje:** Nie musi codziennie myśleć "co ugotować". Kupuje celowo — mniej marnowania. -**Uwagi:** -- Brak zdjęć (szare placeholdery) — OK dla prototypu -- Po powrocie z detalu filtr/szukajka się utrzymują +### Poranek — rutynowe śniadanie z wariacją ---- +Od tygodni je to samo śniadanie. Tym razem otwiera detal przepisu i klika shuffle przy jednym ze składników. Porównuje wartości odżywcze zamienników, wybiera coś innego. Podsumowanie kaloryczne przelicza się od razu — widzi, że różnica jest niewielka. Dodaje do planu z zamiennikiem. -### Scenariusz 2: Szukanie przepisu na kolację +**Co mu to daje:** Urozmaicenie bez wysiłku. Widzi wpływ zamiany na makro zanim się zdecyduje. -**Cel:** Użytkownik szuka czegoś konkretnego. +### Środa wieczór — "nie chce mi się gotować" -1. Wpisuje "łosoś" → lista filtruje się do 1 karty -2. Kasuje tekst → wracają wszystkie -3. Klika ikonę filtrów → otwiera się overlay -4. Zaznacza "Kolacja" → aktualizuje się licznik wyników -5. Dodatkowo zaznacza tag "Wysokobiałkowe" -6. Ustawia suwak na max 25 min -7. Klika "Pokaż X wyników" → wraca do przefiltrowanej listy -8. Wybiera przepis i otwiera detal +Wraca zmęczony z pracy. Otwiera apkę — miał zaplanowany obiad, ale nie ma energii. Oznacza posiłek jako pominięty (zamówi albo zje na mieście). Albo: przegląda przepisy filtrując po czasie do 15 min, szybko coś wybiera i dodaje na dziś. ---- +**Co mu to daje:** Brak poczucia winy — pominięcie jest jawne, plan się dostosowuje. Albo szybka alternatywa bez przeglądania całego katalogu. -### Scenariusz 3: Planowanie posiłków na tydzień +### Gotowanie na zapas -**Cel:** Użytkownik układa menu na kilka dni. +W weekend wybiera przepis na obiad i ustawia 3 porcje. Dodaje ten sam przepis na poniedziałek, wtorek i środę. Gotuje raz — ma obiady na trzy dni odhaczone. Składniki na liście zakupów są policzone na pełną ilość. -1. Przechodzi na zakładkę **Planer** -2. Widzi dzisiejszy dzień (demo-dane lub wcześniej zaplanowane) -3. Klika na przepis w slocie → otwiera się detal -4. Wraca ← do planera -5. Klika następny dzień w kalendarzu -6. Widzi puste sloty, klika "Dodaj przepis" przy Śniadaniu -7. Picker: wpisuje fragment nazwy, widzi "Ostatnio używane" -8. Wybiera przepis → pojawia się w slocie z kcal w nagłówku -9. Przy obiedzie klika "Pomijam" (je na mieście) → slot przygaszony -10. Klika "Składniki na ten dzień" → widzi braki vs spiżarnia + prognoza -11. Klika "Dodaj braki na dziś do listy" → toast potwierdzenia -12. Następnego dnia: "Kopiuj dzień" → wybiera dzień docelowy → gotowe +**Co mu to daje:** Oszczędność czasu w tygodniu. Jedno gotowanie zamiast trzech. ---- +### W sklepie z listą -### Scenariusz 4: Zarządzanie spiżarnią +Stoi w sklepie, otwiera listę zakupów. Produkty pogrupowane po kategoriach — idzie przez alejki i odznacza kolejne pozycje. Wraca do domu, klika "Kupione → spiżarnia" — stany magazynowe aktualizują się jednym ruchem. -**Cel:** Użytkownik sprawdza co ma w domu. +**Co mu to daje:** Nie zapomina co kupić. Spiżarnia jest aktualna bez ręcznego wpisywania. -1. Przechodzi na zakładkę **Spiżarnia** -2. Widzi chipy składników pogrupowane po kategorii -3. Włącza "Tylko na stanie" → widzi co ma -4. Klika "Płatki owsiane" → bottom sheet -5. Ustawia 500g (przyciskami ± lub inputem) -6. Zamyka → chip zmienił się na zielony z "500 g" -7. Chce dodać mleko na listę → klika "Dodaj na listę" w sheecie +### Sprawdzenie przed zakupami ---- +Przed wyjściem do sklepu sprawdza spiżarnię — co jeszcze ma. Przechodzi do planera, ogląda najbliższe dni i klika "Dodaj braki do listy". Lista zakupów zawiera tylko to, czego naprawdę potrzebuje. -### Scenariusz 5: Zakupy w sklepie - -**Cel:** Użytkownik jest w sklepie, odznacza kupione. - -1. Otwiera zakładkę **Zakupy** -2. Widzi listę kuchenną pogrupowaną po kategorii -3. Bierze mleko z półki → klika checkbox → przekreślenie -4. Widzi `sourceNote` z informacją skąd pozycja pochodzi -5. Ilość się nie zgadza — klika na ilość → prompt → poprawia -6. Kupuje dalsze pozycje - ---- - -### Scenariusz 6: Po zakupach — przeniesienie do spiżarni - -**Cel:** Użytkownik wrócił ze sklepu, aktualizuje spiżarnię. - -1. Otwiera **Zakupy** → widzi zaznaczone pozycje -2. Pojawia się pasek: "Kupione → spiżarnia" i "Wyczyść kupione" -3. Klika "Kupione → spiżarnia" → potwierdzenie z podglądem pozycji -4. Potwierdza → toast "Przeniesiono X pozycji" -5. Kupione znikają z listy -6. Przechodzi do **Spiżarni** → stany zaktualizowane -7. Wraca do **Planera** → braki zmniejszone - ---- - -### Scenariusz 7: Gotowanie z przepisu - -**Cel:** Użytkownik gotuje, sprawdza przepis krok po kroku. - -1. Otwiera **Planer** → dzisiejszy dzień -2. Klika przepis w slocie Kolacja -3. Otwiera się detal → przełącza na "Kroki" -4. Czyta krok po kroku -5. Sprawdza ilość składnika → przełącza na "Składniki" -6. Wraca na "Kroki" - ---- - -### Scenariusz 8: Dodanie przepisu do planera z widoku detalu - -**Cel:** Użytkownik znalazł przepis i chce go zaplanować. - -1. Przegląda **Przepisy** → otwiera detal "Serek wiejski z orzechami i owocami" -2. Klika "Zaplanuj" (górny przycisk) -3. Otwiera się bottom sheet z kalendarzem (domyślnie widok tygodnia, można rozwinąć do miesiąca) -4. Wybiera dzień w kalendarzu -5. Wybiera porę posiłku (np. "Śniadanie") -6. Widzi sekcję "Wymienne składniki" — 3 kompaktowe karty (orzechy, truskawki, borówki) z aktualnym wyborem -7. Klika kartę "Orzechy włoskie" → rozwija się lista opcji (włoskie, laskowe, nerkowca, migdały, pekan) z wartościami odżywczymi i radio-przyciskami -8. Wybiera "Migdały" → karta się zwija, wybrany składnik zmienia się na "Migdały" z amber tłem -9. Klika "Dodaj" → toast potwierdzenia, sheet się zamyka, przepis dodany do planera z informacją o zamianie (substitutions) - ---- - -### Scenariusz 9: "Co mogę ugotować?" - -**Cel:** Użytkownik ma coś w spiżarni, chce wiedzieć co da się z tego zrobić. - -1. Otwiera **Spiżarnię** → widzi co ma -2. Chciałby kliknąć "Co mogę ugotować?" → **takiej funkcji jeszcze nie ma** -3. Musi ręcznie sprawdzać przepisy - -**Propozycja:** Filtr "Mam składniki" w widoku Przepisy. +**Co mu to daje:** Nie kupuje podwójnie. Nie marnuje jedzenia, które zalega w szafce. --- ## 6. Znane problemy i propozycje ulepszeń -### Do poprawy (TODO) +### Do poprawy -| # | Problem | Dotyczy scenariusza | -|---|---------|---------------------| -| 1 | Brak wskaźnika aktywnych filtrów na ikonce (badge/kropka) | 2 | -| 2 | Po dodaniu braków do listy zakupów brak ochrony przed duplikacją (brak info "już dodano") | 3 | -| 3 | Kupione pozycje mieszają się z niekupionymi w tej samej grupie (brak separacji) | 5 | -| 4 | Brak podsumowania na liście zakupów ("Do kupienia: X, kupione: Y") | 5 | -| 5 | Brak undo przy "Kupione → spiżarnia" | 6 | -| 6 | Scroll spiżarni resetuje się po edycji (re-render) | 4 | -| 7 | "Kopiuj dzień" kopiuje też status "Pominięto" — może nie zawsze pożądane | 3 | +| # | Problem | +|---|---------| +| 1 | Brak wskaźnika aktywnych filtrów na ikonce (badge/kropka) | +| 2 | Po dodaniu braków do listy zakupów brak ochrony przed duplikacją | +| 3 | Kupione pozycje mieszają się z niekupionymi w tej samej grupie | +| 4 | Brak podsumowania na liście zakupów ("Do kupienia: X, kupione: Y") | +| 5 | Brak undo przy "Kupione → spiżarnia" | +| 6 | Scroll spiżarni resetuje się po edycji (re-render) | +| 7 | "Kopiuj dzień" kopiuje też status "Pominięto" — może nie zawsze pożądane | ### Propozycje nowych funkcji | # | Funkcja | Opis | |---|---------|------| -| P1 | Tryb "krok po kroku" przy gotowaniu | Full-screen, jeden krok, swipe next + checkbox | +| P1 | Tryb "krok po kroku" przy gotowaniu | Full-screen, jeden krok, swipe next + checkbox | | P2 | Wake lock | Zapobiega wygaszeniu ekranu podczas gotowania | | P3 | Filtr "Mam składniki" | W widoku Przepisy — pokaż co da się ugotować z aktualnej spiżarni | | P4 | Większe elementy na liście zakupów | Ułatwienie obsługi w sklepie na telefonie | diff --git a/images/recipes/jajecznica.png b/images/recipes/jajecznica.png new file mode 100644 index 0000000..3ba44fa Binary files /dev/null and b/images/recipes/jajecznica.png differ diff --git a/images/recipes/kanapka_hummus.png b/images/recipes/kanapka_hummus.png new file mode 100644 index 0000000..5dabed3 Binary files /dev/null and b/images/recipes/kanapka_hummus.png differ diff --git a/images/recipes/kanapka_losos.jpg b/images/recipes/kanapka_losos.jpg new file mode 100644 index 0000000..b8176c8 Binary files /dev/null and b/images/recipes/kanapka_losos.jpg differ diff --git a/images/recipes/kanapka_parmenska.jpg b/images/recipes/kanapka_parmenska.jpg new file mode 100644 index 0000000..0acbb5c Binary files /dev/null and b/images/recipes/kanapka_parmenska.jpg differ diff --git a/images/recipes/makaron_ricotta.jpg b/images/recipes/makaron_ricotta.jpg new file mode 100644 index 0000000..13e9b5d Binary files /dev/null and b/images/recipes/makaron_ricotta.jpg differ diff --git a/images/recipes/serek_owoc.jpg b/images/recipes/serek_owoc.jpg new file mode 100644 index 0000000..36adf27 Binary files /dev/null and b/images/recipes/serek_owoc.jpg differ diff --git a/index.html b/index.html index bb41cf7..1961772 100644 --- a/index.html +++ b/index.html @@ -47,6 +47,6 @@ navigator.serviceWorker.register('./sw.js', { scope: './' }).catch(() => {}); } - + \ No newline at end of file diff --git a/js/app.js b/js/app.js index 20a5122..71e2cca 100644 --- a/js/app.js +++ b/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 ` diff --git a/js/data/catalog.js b/js/data/catalog.js index d6f8ab8..50d4f5e 100644 --- a/js/data/catalog.js +++ b/js/data/catalog.js @@ -24,21 +24,16 @@ export const CATEGORY_LABELS = { /** @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 }, + /* ── 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 }, diff --git a/js/views/MealPlanner.js b/js/views/MealPlanner.js index 48a5b97..db188e3 100644 --- a/js/views/MealPlanner.js +++ b/js/views/MealPlanner.js @@ -523,8 +523,10 @@ function renderDayContent(state) {
-
- ${escapeHtml(recipe.thumbLabel)} +
+ ${recipe.image + ? `` + : `${escapeHtml(recipe.thumbLabel)}`}

${escapeHtml(recipe.title)}

@@ -629,8 +631,10 @@ function getRecentRecipeIds(plans, limit = 5) { function recipeCardHtml(r) { return ` ` + ? `` : ''; - 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 = `
${isSelected ? '
' : ''}
`; + const defaultTag = isOriginal ? `Domyślny` : ''; + 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 = ` -
-

Można zamienić na:

- ${altCards.join('')} +
+ ${optionCards.join('')}
`; } @@ -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()); diff --git a/js/views/RecipeList.js b/js/views/RecipeList.js index fa00a66..c9c4de5 100644 --- a/js/views/RecipeList.js +++ b/js/views/RecipeList.js @@ -55,8 +55,10 @@ function renderRecipeCard(recipe) { const labels = slotLabelsFor(recipe); return `
-
- ${escapeHtml(recipe.thumbLabel)} +
+ ${recipe.image + ? `${escapeHtml(recipe.title)}` + : `${escapeHtml(recipe.thumbLabel)}`}

${escapeHtml(recipe.title)}