chore: graf db
feat: workout plans (volný, fixní, plovoucí)
This commit is contained in:
1 parent
865c2c3b22
commit
7641e26c9f
43 files changed
+3459
-599
No files matched your search
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user