chore: graf db

feat: workout plans (volný, fixní, plovoucí)
This commit is contained in:
Ondřej Anděl committed 2026-10-22 12:11:05 +02:00
1 parent 865c2c3b22
commit 7641e26c9f
43 files changed
+3459 -599

No files matched your search

+57 -15
View File
@@ -62,7 +62,8 @@ cd client && yarn build # tsc --noEmit + vite build → client/dist
- **Domain:** `meals.ts` (jídlo a historie jídel), `mealGroups.ts` (seznam jídel —
skupiny podle názvu a zdroje, doplnění a přejmenování), `mealPhotos.ts` (fotky
jídel), `nutrients.ts` (živiny), `mealTemplates.ts` (recepty), `activities.ts` (pohyb), `workouts.ts` (šablony
tréninků), `sleep.ts` (spánek), `weights.ts` (vážení po dnech), `profile.ts`
tréninků a plány), `workoutSchedule.ts` (co plán navrhuje na den, náhrady
vynechaných cviků), `workoutPlans.ts` (záznamy použití šablon), `sleep.ts` (spánek), `weights.ts` (vážení po dnech), `profile.ts`
(tělesné údaje a výpočet BMR), `bodyMetrics.ts` (BMI, podíl tuku, cíle),
`dayOverview.ts` (spojení dne a bilance), `statsService.ts` (agregace),
`luncherImport.ts` (parsování exportů z Luncheru), `settings.ts`, `calories.ts`
@@ -76,11 +77,13 @@ cd client && yarn build # tsc --noEmit + vite build → client/dist
- **Pages:** `pages/` — `DayPage` (přehled dne se záložkami Příjem/Výdej),
`MealGroupsPage` (`/jidla` — seznam jídel podle názvu a zdroje s filtrem zdroje,
hromadným doplněním údajů a přejmenováním),
`WorkoutsPage` (`/treninky` — šablony tréninků), `ProfilePage` (Profil — ukazatele,
`WorkoutsPage` (`/treninky` — plány a šablony tréninků), `ProfilePage` (Profil — ukazatele,
vývoj hmotnosti, tělesné údaje, cíle a klidový výdej),
`StatsPage` (statistiky se záložkami Příjem / Výdej / Útrata a import; panely
`IntakeStatsPanel`, `ActivityStatsPanel`, `SpendingStatsPanel`)
- **Components:** `components/` (Header, CalorieLookup, MealSuggestions, NutrientsFieldset)
- **Components:** `components/` (Header, CalorieLookup, MealSuggestions, NutrientsFieldset,
PlannedWorkoutCard a PendingReplacementsPanel — návrh tréninku a náhrady v přehledu dne,
WorkoutTemplateForm/WorkoutTemplateCard — šablony a plány)
a `components/modals/` (MealModal, ActivityModal, ApplyWorkoutModal, ApplyMealModal —
výběr z historie a receptů, SettingsModal, ImportModal, SpinWheelModal — kolo štěstí)
- **Klientské enumy** patří do `enums.ts` jako TypeScript `enum` s mapou popisků,
@@ -186,7 +189,7 @@ trénink než 120 v kuse, i když spálené kalorie vyjdou stejně. Jedna série
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. Záznamy ve dni
z aktivit odcvičeného dne (všech, nebo vybraných) vznikne volná šablona. Záznamy ve dni
zůstanou beze změny. U jídla nic takového není, opakovaně jedená jídla se nabízejí
z historie (viz Historie jídel a recepty).
@@ -195,17 +198,49 @@ je jen informace o tréninku, kalorie **nemění** — žádný jednoduchý vzor
spotřebu podle zátěže nedává, takže by šlo o vymyšlené číslo. Kdo chce, promítne
ji do sazby na 100 jednotek.
`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í.
`WorkoutTemplate` (`workouts.ts`) má typ (`WorkoutTemplateType`):
**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`.
- **VOLNY** — pojmenovaný seznam položek, do dne se zakládá ručně. Výchozí typ,
nezapisuje se; šablony uložené dřív, než typy přibyly, jsou volné.
- **FIXNI** — plán s dny (`days`) navázanými na den v týdnu (`weekday` 1–7).
- **PLOVOUCI** — plán jako sekvence dnů zakončená dnem volna (volno se jako den
nezapisuje, plyne z typu).
Položka je buď cvik (`kind: CVIK`), nebo odkaz na volnou šablonu (`kind: SABLONA`),
jejíž cviky se při použití rozbalí (`expandExercises`). Použitím vzniknou běžné
aktivity s vazbou `templateId` (u plánu i `planDayId`) — jsou samostatné, takže
úprava založené položky šablonu nemění. Plán má navíc `replaceWithinDays`
(lhůta pro náhradu vynechaných cviků) a `startDate`. Dny plánu mají stálé `id`,
podle kterého se v historii pozná, který den se kdy cvičil, i po úpravě plánu.
**Zanoření volných šablon je povolené jen na jednu úroveň** a `checkReferences`
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ž jiná volná šablona používá. Bez druhé
kontroly by druhá úroveň vznikla oklikou přes úpravu. Den plánu úroveň nepřidává,
smí odkázat na libovolnou volnou šablonu; plán sám do ničeho vnořit nejde a volná
šablona, kterou něco používá, se nemůže stát plánem. Smazat nelze šablonu, kterou
skládá jiná nebo kterou má plán v některém dni — 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`.
**Návrh tréninku** (`getPlannedWorkouts` ve `workoutSchedule.ts`, v přehledu dne
`DayOverview.plannedWorkouts`): fixní plán navrhne den podle dne v týdnu.
Plovoucí prochází záznamy použití od `startDate` jako stavový automat
(`advanceDone`, `advanceGap`): odcvičený den posune sekvenci, den bez tréninku je
řádné volno (když je na řadě), **posunuté volno** (uprostřed rozběhnutého cyklu,
jednou za cyklus — po posledním dni se pak pokračuje rovnou od začátku), jinak
trénink jen odsune. Budoucí dny se počítají, jako by se navržený trénink splnil.
Návrh se potvrdí přes `applyWorkoutTemplate` s `dayId` a `doneIndexes`
(pořadí v rozbaleném seznamu cviků); odškrtnuté cviky se zapíšou do záznamu bez
`entryId`, jako vynechané. Den jde potvrdit i celý jako vynechaný.
**Náhrady** (`getPendingReplacements`, `replaceSkippedExercise`,
`POST /api/workouts/replace`): vynechaný cvik (bez aktivity a bez náhrady) se
nabízí v přehledu dne po dobu `replaceWithinDays` ze záznamu (lhůta, jaká platila
v den vynechání, nejvýš 14). Náhrada založí aktivitu s `replacesDate` a k cviku
v záznamu původního dne zapíše `replacement` (datum a `entryId`). Smazání náhradní
aktivity vazbu zruší, přesun jí změní datum (`updateReplacementLink`). Náhrada ani
den volna plovoucí plán neposouvají — posouvá ho jen záznam dne plánu.
`buildEnergyBalance` v `dayOverview.ts` počítá:
`bilance = příjem − (klidový výdej + pohyb)`. Záporná hodnota je deficit.
@@ -251,8 +286,15 @@ jednotlivé cviky s `entryId` aktivity, která z nich vznikla; podle něj
`analyzeDay` u každého dne pozná cvik **odebraný** (aktivita chybí) a **upravený**
(jiné množství, série nebo násobek) — podle názvu by to nešlo, šablona může mít
stejný cvik dvakrát. Smazání všech cviků šablony ve dni `dropOrphanedPlans`
bere jako vrácené použití a plán zahodí. Aktivity ze šablony bez plánu (cvik
bere jako vrácené použití a plán zahodí — kromě záznamu vynechaného celého dne
plánu a záznamu, jehož cvik už má náhradu. Aktivity ze šablony bez plánu (cvik
přesunutý z jiného dne) se počítají jako splněné přesně dle plánu.
Plán se ve statistikách dělí po dnech (klíč `templateId:planDayId`). Nahrazený
cvik je odchylka **NAHRAZENO** s `replacementDate` a počítá se jako provedený;
náhrada sama je v `ActivityDayStats.replacements` dne, kdy se odcvičila, a její
kalorie jdou do skutečnosti dne plánu, ze kterého pochází. Graf u takového dne
místo „!" ukáže šipku na den náhrady. Dny volna ani necvičené dny plánu se do
statistik nijak nepromítají.
**Statistiky příjmu** (`getIntakeStats`, `GET /api/stats/intake`) jsou protějšek
výdeje: příjem po chodech a zdrojích, živiny a porovnání s výdejem. Výdej