feat: zlepšení šablon, přidány osobní údaje a spánkový režim

This commit is contained in:
Ondřej Anděl
2026-09-15 14:16:42 +02:00
parent a5711f3e1e
commit 21b01528e9
51 changed files with 3739 additions and 504 deletions
+64 -11
View File
@@ -60,7 +60,8 @@ cd client && yarn build # tsc --noEmit + vite build → client/dist
- **Routes:** `routes/` — `dayRoutes`, `mealRoutes`, `activityRoutes`, `workoutRoutes`,
`statsRoutes`, `calorieRoutes`, `settingsRoutes`, `importRoutes`
- **Domain:** `meals.ts` (jídlo), `activities.ts` (pohyb), `workouts.ts` (šablony
tréninků), `dayOverview.ts` (spojení dne a bilance), `statsService.ts` (agregace),
tréninků), `sleep.ts` (spánek), `profile.ts` (tělesné údaje a výpočet BMR),
`dayOverview.ts` (spojení dne a bilance), `statsService.ts` (agregace),
`luncherImport.ts` (parsování exportů z Luncheru), `settings.ts`, `calories.ts`
- **Auth:** `auth.ts` — JWT + volitelná autentizace z hlavičky reverzní proxy
- **Storage:** `storage/index.ts` factory dle proměnné `STORAGE`; backendy:
@@ -70,9 +71,12 @@ cd client && yarn build # tsc --noEmit + vite build → client/dist
### Client (client/src/)
- **Entry:** `index.tsx` → `AppRoutes.tsx`; `Login.tsx` je přihlašovací obrazovka
- **Pages:** `pages/` — `DayPage` (přehled dne se záložkami Příjem/Výdej),
`TemplatesPage` (šablony tréninků), `ProfilePage` (tělesné údaje a klidový výdej),
`StatsPage` (statistiky + import)
- **Components:** `components/` (Header, CalorieLookup) a `components/modals/`
(MealModal, ActivityModal, WorkoutModal, SettingsModal, ImportModal)
(MealModal, ActivityModal, ApplyWorkoutModal, SettingsModal, ImportModal)
- **Klientské enumy** patří do `enums.ts` jako TypeScript `enum` s mapou popisků,
ne jako inline union u komponenty (`DayTab`, `StatsRange`)
- **Context:** `context/auth.tsx` (JWT), `context/settings.tsx` (světlý/tmavý motiv)
- **Routing:** konstanty adres jsou v `routes.ts`, ne v `AppRoutes.tsx` — hlavička
je potřebuje a kruhový import by je nechal nedefinované
@@ -140,23 +144,72 @@ Den má dvě strany a `DayPage` je dělí do záložek:
- **Výdej** — pohyb (`ActivityEntry`), klíč `activities:<login>:<datum>`
Aktivita se měří v jednotce (`ActivityUnit`: KROKY, MINUTY, KM, OPAKOVANI) a
kalorie se dopočtou jako `quantity / 100 * caloriesPer100Units`. **Sto jednotek,
ne jedna** — u kroků by sazba na jeden krok byla zlomek (~0,04 kcal) a aplikace
všude pracuje s celými čísly. Stejná konvence jako u energie jídla na 100 g.
kalorie se dopočtou jako `getTotalQuantity(quantity, sets) / 100 * caloriesPer100Units`.
**Sto jednotek, ne jedna** — u kroků by sazba na jeden krok byla zlomek (~0,04 kcal)
a aplikace všude pracuje s celými čísly. Stejná konvence jako u energie jídla na 100 g.
`WorkoutTemplate` (`workouts.ts`) je pojmenovaný seznam cviků. Použitím vzniknou
běžné aktivity s vazbou `templateId` — jsou samostatné, takže úprava založené
položky šablonu nemění.
`quantity` je množství **na jednu sérii**, `sets` je počet sérií; celkové množství
je jejich součin. Drží se odděleně schválně: 3 × 40 opakování s pauzami je jiný
trénink než 120 v kuse, i když spálené kalorie vyjdou stejně. Jedna série je
výchozí stav a neukládá se.
Opačný směr k použití šablony je `createWorkoutTemplateFromActivities` —
z aktivit odcvičeného dne (všech, nebo vybraných) vznikne šablona. U jídla to
umí `createMealTemplateFromMeal`, které navíc **přepíše šablonu stejného názvu**;
opakované "uložit jako šablonu" u téhož jídla má dát jednu šablonu, ne několik
stejných. Záznamy ve dni zůstanou v obou případech beze změny.
`WorkoutTemplate` (`workouts.ts`) je pojmenovaný seznam položek. Položka je buď
cvik (`kind: CVIK`), nebo odkaz na jinou šablonu (`kind: SABLONA`), jejíž cviky se
při použití rozbalí. Použitím vzniknou běžné aktivity s vazbou `templateId` — jsou
samostatné, takže úprava založené položky šablonu nemění.
**Zanoření je povolené jen na jednu úroveň** a `checkNesting` to hlídá z obou stran:
šablona nesmí odkazovat na takovou, která sama něco skládá, a zároveň nesmí začít
skládat, pokud ji už někdo používá. Bez druhé kontroly by druhá úroveň vznikla
oklikou přes úpravu. Smazat nelze šablonu, kterou skládá jiná — zůstal by odkaz
do prázdna. Šablony uložené dřív, než skládání přibylo, nemají `kind`; `withKind`
jim ho při načtení doplní na `CVIK`.
`buildEnergyBalance` v `dayOverview.ts` počítá:
`bilance = příjem − (klidový výdej + pohyb)`. Záporná hodnota je deficit.
Bez nastaveného klidového výdeje (`basalCalories` v nastavení) porovnává bilance
jen jídlo proti pohybu — **to není skutečný deficit**, proto se to přes
`hasBasal: false` propisuje do UI, aby to číslo nikoho nemátlo.
Bez nastaveného klidového výdeje porovnává bilance jen jídlo proti pohybu —
**to není skutečný deficit**, proto se to přes `hasBasal: false` propisuje do UI,
aby to číslo nikoho nemátlo.
Klidový výdej se bere z `settings.effectiveBasalCalories`: ručně zadaný
`basalCalories` má přednost před hodnotou spočítanou z tělesných údajů
(`computeBasalCalories` v `profile.ts`, rovnice Mifflin–St Jeor). Výpočet
**záměrně nenásobí koeficientem fyzické aktivity (PAL)**, jak to dělají kalkulačky
TDEE — pohyb se eviduje zvlášť a přičítá se, takže vynásobení by ho započítalo
dvakrát.
**Spánek** (`sleep.ts`, klíč `sleep:<login>:<datum>`) se eviduje jako kontext dne
a do bilance **nevstupuje**. Klidový výdej je hodnota za celých 24 hodin včetně
spánku a žádná ze standardních rovnic délku spánku jako proměnnou nemá — přičítat
nebo odečítat za něj kalorie by bylo vymýšlení čísla. Test to hlídá:
bilance se po zadání spánku nesmí změnit.
`GET /api/day?date=` vrací `DayOverview` se vším naráz (jídlo, pohyb, bilance),
takže `DayPage` si vystačí s jedním voláním.
## Šablony jídel
`MealTemplate` (`mealTemplates.ts`, klíč `mealTemplates:<login>`) je opakovaně
jedené jídlo. Na rozdíl od šablon tréninků se **neskládá z jiných** — je to plochý
záznam. Použitím vznikne běžný `MealEntry` přes `addMeal`, takže gramáž, cenu
i kalorie dopočítá stejná logika jako u ručně zadaného jídla.
Šablona může nést buď `calories` (celá porce), nebo `caloriesPer100g` + `weight`.
Obojí projde `deriveAmounts`, takže stačí vyplnit, co o jídle víte.
Pole `note` slouží i na postup přípravy u domácích jídel. Strukturované suroviny
(recept jako seznam položek s množstvím) mohou přibýt později jako další volitelné
pole, aniž by se muselo měnit cokoli stávajícího.
Kontrakt má vlastní `MealTemplateInput`, kde je `id` volitelné — `MealTemplate`
ho vyžaduje a nešlo by jím posílat novou šablonu.
## Import z Luncheru
`server/src/luncherImport.ts` čte měsíční přehled ze stránky statistik Luncheru