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

+218 -15
View File
@@ -59,6 +59,192 @@ JWT_SECRET=... docker compose up --build
Produkčně běží aplikace nad Redisem, ve vývoji nad JSON souborem
(`server/data/db.json`). Viz `server/.env.template`.
## Datový model
Úložiště je klíč–hodnota (JSON soubor ve vývoji, Redis v produkci). „Tabulka" je
tedy typ klíče a hodnotou je pod ním buď celé pole záznamů, nebo jeden objekt.
Klíče začínají typem a přihlašovacím jménem uživatele, data jednotlivých
uživatelů se nemíchají.
| Klíč | Hodnota |
|---|---|
| `meals:<login>:<datum>` | pole jídel dne (`MealEntry`) |
| `mealTemplates:<login>` | pole receptů (`MealTemplate`) |
| `mealPhotos:<login>` | rejstřík fotek: klíč jídla (název + zdroj) → pole náhledů |
| `mealPhoto:<login>:<id>` | plný obrázek jedné fotky (data URL) |
| `calorieLibrary:<login>` | vlastní knihovna kalorií: znormalizovaný název → záznam |
| `settings:<login>` | nastavení, tělesné údaje a cíle |
| `weights:<login>` | vážení: datum → hmotnost v gramech |
| `sleep:<login>:<datum>` | délka spánku v minutách |
| `activities:<login>:<datum>` | pole aktivit dne (`ActivityEntry`) |
| `workouts:<login>` | pole šablon tréninků — volné šablony i plány |
| `workoutPlans:<login>:<datum>` | pole záznamů o použití šablon a dnů plánu v daném dni |
Výchozí hodnoty se do úložiště nezapisují (jedna série, násobek 1, volný typ
šablony, slaná chuť receptu) a dopočítávaná pole (součty, odhady kalorií,
ukazatele) se počítají při čtení.
```mermaid
erDiagram
MEAL_ENTRY {
string id PK
date date
enum mealType "SNIDANE, OBED, ..."
string name
string source "podnik / obchod"
int price "haléře"
int weight "g"
int pricePer100g "haléře"
int calories "kcal za porci"
int caloriesPer100g
object nutrientsPer100g "g na 100 g, na desetiny"
decimal multiplier
string note
string templateId FK "recept"
string importSource
string importKey "duplicity importu"
datetime createdAt
datetime updatedAt
}
MEAL_TEMPLATE {
string id PK
string name
string source
string category "typ: polévka, omáčka..."
enum flavor "SLANE, SLADKE"
int servings
int cookedWeight "g"
int prepMinutes
int weight "porce, dopočteno"
int calories "porce, dopočteno"
int price "porce, dopočteno"
string note "postup v Markdownu"
array sideDishIds FK "přílohy"
array similarIds FK "podobné"
}
RECIPE_INGREDIENT {
string name
decimal amount "v jiné jednotce než g"
enum unit "KS, ML, L, LZICE..."
int weight "g"
int caloriesPer100g
int calories
int price
}
MEAL_PHOTO_INDEX {
string mealKey PK "název + zdroj"
string id FK
string thumbnail "data URL ~240 px"
datetime createdAt
}
MEAL_PHOTO {
string id PK
string image "data URL ~1600 px"
}
CALORIE_LIBRARY {
string normalizedName PK
string name
int caloriesPer100g
int uses
datetime updatedAt
}
SETTINGS {
array sourceRates "podnik a sazba za 100 g"
int basalCalories "ručně zadaný klidový výdej"
object profile "pohlaví, rok narození, výška, obvody"
object goals "tempo, cílová hmotnost, bílkoviny/kg"
}
WEIGHTS {
date date PK
int weightGrams
}
SLEEP {
date date PK
int minutes
}
ACTIVITY_ENTRY {
string id PK
date date
string name
enum unit "KROKY, MINUTY, SEKUNDY, KM, OPAKOVANI"
int quantity "na sérii"
int sets
int caloriesPer100Units
int calories "za jedno provedení"
int extraWeightGrams
decimal multiplier
string note
string templateId FK "šablona / plán"
string planDayId FK "den plánu"
date replacesDate "nahrazuje vynechaný cvik z tohoto dne"
datetime createdAt
datetime updatedAt
}
WORKOUT_TEMPLATE {
string id PK
string name
enum type "VOLNY (výchozí), FIXNI, PLOVOUCI"
int replaceWithinDays "jen plán"
date startDate "jen plán"
}
WORKOUT_PLAN_DAY {
string id PK
string name
int weekday "1-7, jen fixní plán"
}
WORKOUT_TEMPLATE_ITEM {
enum kind "CVIK, SABLONA"
string name
enum unit
int quantity
int sets
int caloriesPer100Units
int extraWeightGrams
string templateRefId FK "volná šablona"
}
WORKOUT_PLAN {
string id PK
string templateId FK
string templateName "v době použití"
string planDayId FK
string planDayName "v době použití"
int replaceWithinDays "v době použití"
datetime appliedAt
}
WORKOUT_PLAN_ITEM {
string entryId FK "chybí = vynecháno"
string name
enum unit
int quantity
int sets
int caloriesPer100Units
int calories
object replacement "date + entryId náhrady"
}
MEAL_ENTRY }o--o| MEAL_TEMPLATE : "templateId"
MEAL_TEMPLATE ||--|{ RECIPE_INGREDIENT : "ingredients"
MEAL_TEMPLATE }o--o{ MEAL_TEMPLATE : "přílohy, podobné"
MEAL_PHOTO_INDEX ||--|| MEAL_PHOTO : "id"
MEAL_PHOTO_INDEX }o..o{ MEAL_ENTRY : "název + zdroj"
CALORIE_LIBRARY }o..o{ MEAL_ENTRY : "znormalizovaný název"
WORKOUT_TEMPLATE ||--o{ WORKOUT_TEMPLATE_ITEM : "items (volná)"
WORKOUT_TEMPLATE ||--o{ WORKOUT_PLAN_DAY : "days (plán)"
WORKOUT_PLAN_DAY ||--|{ WORKOUT_TEMPLATE_ITEM : "items"
WORKOUT_TEMPLATE_ITEM }o--o| WORKOUT_TEMPLATE : "templateRefId"
ACTIVITY_ENTRY }o--o| WORKOUT_TEMPLATE : "templateId"
ACTIVITY_ENTRY }o--o| WORKOUT_PLAN_DAY : "planDayId"
WORKOUT_PLAN }o--|| WORKOUT_TEMPLATE : "templateId"
WORKOUT_PLAN }o--o| WORKOUT_PLAN_DAY : "planDayId"
WORKOUT_PLAN ||--|{ WORKOUT_PLAN_ITEM : "items"
WORKOUT_PLAN_ITEM |o--o| ACTIVITY_ENTRY : "entryId (odcvičeno)"
WORKOUT_PLAN_ITEM |o--o| ACTIVITY_ENTRY : "replacement (náhrada)"
```
Vazby na šablony a plány jsou volné — smazaná šablona v historii zůstane pod
názvem uloženým v `WORKOUT_PLAN`. Záznam použití (`workoutPlans`) je u plánu zároveň
evidence, který den plánu se kdy odcvičil; podle ní se počítá, co je na řadě.
## Den: příjem a výdej
Přehled dne má dvě záložky:
@@ -75,25 +261,42 @@ včetně spánku a žádná standardní rovnice s délkou spánku nepočítá.
Nahoře je **bilance dne**: `příjem − (klidový výdej + pohyb)`.
## Šablony tréninků
## Tréninky a plány
Opakované tréninky se ukládají jako šablony na stránce **Šablony** — v přehledu dne
pak stačí jedno kliknutí a celý trénink se založí, místo zadávání cviku po cviku.
Do šablony jde zařadit i **jinou šablonu**, třeba rozcvičku, kterou máte v každém
tréninku; při použití se její cviky rozbalí. Zanoření je kvůli jednoduchosti jen
na jednu úroveň a aplikace to hlídá.
Na stránce **Tréninky** jsou tři typy:
- **Plovoucí plán** — sekvence tréninkových dnů (Den 1, Den 2, Den 3…), po které
následuje den volna a cyklus se opakuje. Na kalendář navázaný není: další den
přijde na řadu, až se splní předchozí. Den volna jde v rámci cyklu jednou
**posunout dopředu** — když uprostřed cyklu den necvičíte, bere se to jako volno
a po posledním dni se pokračuje rovnou od začátku (Den 1, Den 2, volno, Den 3,
Den 1…). Další necvičený den trénink jen odsune.
- **Fixní plán** — pevné dny v týdnu (pondělí nohy, středa běh…), ostatní dny jsou volno.
- **Volný trénink** — seznam cviků na kdykoli (rozcvička, protažení). Jde ho vložit
do dne plánu i do jiného volného tréninku; vkládání je jen na jednu úroveň.
Plán se založí jednou a **přehled dne** pak sám navrhne, co je ten den na řadě.
Po odcvičení stačí návrh potvrdit; co se nestihlo, se odškrtne a zapíše jako
vynechané. Nad rámec plánu se ručně přidávají jen „unikátnosti" — chůze, kroky,
jednorázový běh. Jiný den plánu, než který je na řadě, nebo volný trénink jde
založit tlačítkem *Z tréninků*.
U plánu jde nastavit **Nahraditelné během N dní**: vynechaný cvik (odškrtnutý při
potvrzení nebo smazaný ze dne) se pak v následujících N dnech nabízí v přehledu
dne k náhradě. Ve statistikách výdeje se u takového dne místo vykřičníku ukáže
šipka na den náhrady. Dny volna ani necvičené dny se ve statistikách nijak
nezapisují — zobrazuje se jen to, co proběhlo.
Plán i trénink jde kdykoli **vylepšit** — přidat cviky, dny, zvýšit zátěž nebo
počet opakování. Historie zůstává, jak byla: každé použití si pamatuje, co plán
v ten den sliboval.
Založené položky jsou samostatné aktivity, takže je jde v dni doupravit, aniž by
se změnila šablona.
se změnil plán. Volný trénink jde vytvořit i **opačným směrem** — z toho, co
už máte v dni zapsané, tlačítkem *Uložit jako trénink* v záložce Výdej.
Šablonu jde vytvořit i **opačným směrem** — z toho, co už máte v dni zapsané.
U jídla ikonou záložky v řádku, u tréninku tlačítkem *Uložit jako šablonu* v záložce
Výdej (vybere se, které cviky zahrnout). Při zakládání jídla nebo aktivity je
v dialogu i zaškrtávátko *Uložit i jako šablonu*.
Na druhé záložce jsou **šablony jídel** — opakovaně jedené jídlo (název, zdroj,
kalorie, gramáž, cena), které se v přehledu dne založí jedním kliknutím. Ty se
neskládají z jiných. U domácích jídel jde do poznámky zapsat postup přípravy.
Recepty (vařená jídla se surovinami a postupem) jsou rozšířením jídla v seznamu
jídel na stránce **Jídla**.
## Osobní údaje