Compare commits
3 Commits
2b237d3d71
...
2468b31462
| Author | SHA1 | Date | |
|---|---|---|---|
| 2468b31462 | |||
| be3c355b37 | |||
| ee144847bd |
@ -46,3 +46,57 @@ na zobrazeni app/ingredients sprav aby formular pre pridanie novej suroviny sa z
|
|||||||
|
|
||||||
----- 2026-02-14 08:08:33 -----------------------------------------------------
|
----- 2026-02-14 08:08:33 -----------------------------------------------------
|
||||||
dopln jemnejší UX flow (toasty, confirm modaly pri delete, loading/error states per card)
|
dopln jemnejší UX flow (toasty, confirm modaly pri delete, loading/error states per card)
|
||||||
|
|
||||||
|
----- 2026-02-14 12:44:33 -----------------------------------------------------
|
||||||
|
v zozname surovin app/ingredients sa zobrazuju len bielkoviny, sacharidy a tuky, pridaj tam aj vlakninu,
|
||||||
|
|
||||||
|
zaroven to chcem farebne rozlisit, urob to ako farebny stitok, ze bielkoviny budu modre, sacharidy oranzove, tuky cervene (mierne tlmena) a vlaknina tmavsia zelena
|
||||||
|
|
||||||
|
podobne farebne to rozlis aj na denndom prehlade app/today v sucte dna a tiez tam pridaj vlakninu
|
||||||
|
|
||||||
|
v zozname jedalnickov app/meals je pri kazdom len pocet kalorii, pridaj tam tiez sumar makrozivin (bielkoviny, sacharidy, tuky a vlaknina)
|
||||||
|
|
||||||
|
GPT upravil takto:
|
||||||
|
Rozšír UI aplikácie Nutrio nasledovne:
|
||||||
|
|
||||||
|
1) Ingredients list (route: /app/ingredients)
|
||||||
|
- Aktuálne sa zobrazujú len bielkoviny, sacharidy a tuky.
|
||||||
|
- Pridaj zobrazenie vlákniny (fiber_g_100).
|
||||||
|
- Makrá zobraz ako malé farebné štítky (badge), nie ako obyčajný text.
|
||||||
|
|
||||||
|
Farby štítkov:
|
||||||
|
- Bielkoviny (protein) → modrá #3B82F6
|
||||||
|
- Sacharidy (carbs) → oranžová #F59E0B
|
||||||
|
- Tuky (fat) → tlmená červená #EF4444
|
||||||
|
- Vláknina (fiber) → tmavšia zelená #10B981
|
||||||
|
|
||||||
|
Štýl badge:
|
||||||
|
- malé zaoblené pill tvary
|
||||||
|
- jemné svetlé pozadie (napr. 10–15% opacity farby)
|
||||||
|
- text farba plná farba
|
||||||
|
- formát: B 12g / S 24g / T 5g / V 6g
|
||||||
|
|
||||||
|
2) Today page (route: /app/today)
|
||||||
|
- V dennom súčte pridaj vlákninu.
|
||||||
|
- Makrá zobraz rovnakým farebným štýlom ako v ingredients.
|
||||||
|
- Kalórie nech ostanú neutrálne (tmavá šedá), vizuálne dominantné veľkosťou písma, nie farbou.
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
- hore veľké číslo kcal
|
||||||
|
- pod tým horizontálne makrá ako farebné štítky
|
||||||
|
|
||||||
|
3) Meals list (route: /app/meals)
|
||||||
|
- Aktuálne sa zobrazuje len počet kalórií.
|
||||||
|
- Pridaj aj súhrn makier (protein, carbs, fat, fiber).
|
||||||
|
- Zobraz ich rovnakým badge systémom pre konzistentnosť.
|
||||||
|
- Kcal nech je oddelené (napr. nad makrami alebo výraznejšie písmo).
|
||||||
|
|
||||||
|
4) Konzistentnosť:
|
||||||
|
- Použi jeden spoločný komponent napr. <MacroBadge />
|
||||||
|
- Nepoužívaj sýte plné farebné pozadia.
|
||||||
|
- Použi minimalistický SaaS štýl.
|
||||||
|
- Farby makier musia byť identické naprieč celou aplikáciou.
|
||||||
|
- Nepridávaj nové knižnice.
|
||||||
|
|
||||||
|
Výsledok:
|
||||||
|
Čistý, konzistentný, moderný vzhľad bez prehnaných farieb.
|
||||||
|
|||||||
@ -1,4 +1,92 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Grotesk:wght@500;700&display=swap');
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/dm-sans-latin-ext-400-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/dm-sans-latin-400-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/dm-sans-latin-ext-500-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/dm-sans-latin-500-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/dm-sans-latin-ext-700-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DM Sans';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/dm-sans-latin-700-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Space Grotesk';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/space-grotesk-latin-ext-500-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Space Grotesk';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/space-grotesk-latin-500-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Space Grotesk';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/space-grotesk-latin-ext-700-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Space Grotesk';
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/space-grotesk-latin-700-normal.woff2') format('woff2');
|
||||||
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
@ -13,6 +101,10 @@
|
|||||||
--color-success-bg: #e7f4d8;
|
--color-success-bg: #e7f4d8;
|
||||||
--color-error-bg: #f6dde0;
|
--color-error-bg: #f6dde0;
|
||||||
--color-error: #7d2430;
|
--color-error: #7d2430;
|
||||||
|
--macro-protein: #3B82F6;
|
||||||
|
--macro-carbs: #F59E0B;
|
||||||
|
--macro-fat: #EF4444;
|
||||||
|
--macro-fiber: #10B981;
|
||||||
--radius-md: 0.875rem;
|
--radius-md: 0.875rem;
|
||||||
--radius-lg: 1.25rem;
|
--radius-lg: 1.25rem;
|
||||||
--space-xs: 0.5rem;
|
--space-xs: 0.5rem;
|
||||||
@ -27,6 +119,8 @@
|
|||||||
--fs-xl: 1.875rem;
|
--fs-xl: 1.875rem;
|
||||||
--shadow-soft: 0 20px 40px -30px var(--color-shadow);
|
--shadow-soft: 0 20px 40px -30px var(--color-shadow);
|
||||||
--transition-fast: 160ms ease;
|
--transition-fast: 160ms ease;
|
||||||
|
--font-body: 'DM Sans', 'Segoe UI', Tahoma, sans-serif;
|
||||||
|
--font-display: 'Space Grotesk', 'Segoe UI Semibold', 'Trebuchet MS', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme='dark'] {
|
:root[data-theme='dark'] {
|
||||||
@ -55,7 +149,7 @@ body,
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'DM Sans', 'Segoe UI', sans-serif;
|
font-family: var(--font-body);
|
||||||
font-size: var(--fs-md);
|
font-size: var(--fs-md);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
background:
|
background:
|
||||||
@ -105,7 +199,7 @@ select {
|
|||||||
|
|
||||||
.auth-brand h1 {
|
.auth-brand h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
|
font-family: var(--font-display);
|
||||||
font-size: clamp(2rem, 5vw, 2.75rem);
|
font-size: clamp(2rem, 5vw, 2.75rem);
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
@ -176,7 +270,7 @@ select {
|
|||||||
|
|
||||||
.auth-card h2 {
|
.auth-card h2 {
|
||||||
margin: var(--space-sm) 0 0;
|
margin: var(--space-sm) 0 0;
|
||||||
font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
|
font-family: var(--font-display);
|
||||||
font-size: var(--fs-xl);
|
font-size: var(--fs-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +439,7 @@ select {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
|
font-family: var(--font-display);
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +491,7 @@ select {
|
|||||||
.app-topbar h1 {
|
.app-topbar h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
|
font-family: var(--font-display);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-topbar__user {
|
.app-topbar__user {
|
||||||
@ -535,6 +629,20 @@ select {
|
|||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-row__meta--nutrition {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 0.42rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row__kcal {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: color-mix(in srgb, var(--color-text) 88%, var(--color-muted));
|
||||||
|
}
|
||||||
|
|
||||||
.list-row__actions {
|
.list-row__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
@ -556,20 +664,107 @@ select {
|
|||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.totals-grid {
|
.day-meal-card__summary--nutrition {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
flex-direction: column;
|
||||||
gap: var(--space-sm);
|
gap: 0.45rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.totals-grid span {
|
.day-meal-card__kcal {
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
color: color-mix(in srgb, var(--color-text) 88%, var(--color-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals-card__kcal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals-card__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals-card__kcal-label {
|
||||||
display: block;
|
display: block;
|
||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.totals-grid strong {
|
.totals-card__kcal-value {
|
||||||
font-size: 1.1rem;
|
font-size: clamp(2rem, 5vw, 2.75rem);
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
color: color-mix(in srgb, var(--color-text) 90%, var(--color-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.42rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge-group--with-top-gap {
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge-group--compact {
|
||||||
|
gap: 0.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge-group--right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge-group--nowrap {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.24rem 0.56rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge__value {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge--protein {
|
||||||
|
color: var(--macro-protein);
|
||||||
|
border-color: color-mix(in srgb, var(--macro-protein) 30%, var(--color-border));
|
||||||
|
background: color-mix(in srgb, var(--macro-protein) 13%, var(--color-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge--carbs {
|
||||||
|
color: var(--macro-carbs);
|
||||||
|
border-color: color-mix(in srgb, var(--macro-carbs) 30%, var(--color-border));
|
||||||
|
background: color-mix(in srgb, var(--macro-carbs) 13%, var(--color-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge--fat {
|
||||||
|
color: var(--macro-fat);
|
||||||
|
border-color: color-mix(in srgb, var(--macro-fat) 30%, var(--color-border));
|
||||||
|
background: color-mix(in srgb, var(--macro-fat) 13%, var(--color-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
.macro-badge--fiber {
|
||||||
|
color: var(--macro-fiber);
|
||||||
|
border-color: color-mix(in srgb, var(--macro-fiber) 30%, var(--color-border));
|
||||||
|
background: color-mix(in srgb, var(--macro-fiber) 13%, var(--color-surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
.meal-items-editor h3,
|
.meal-items-editor h3,
|
||||||
@ -747,10 +942,6 @@ select {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.totals-grid {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-two {
|
.grid-two {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
frontend/src/assets/fonts/dm-sans-latin-400-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-400-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/dm-sans-latin-500-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-500-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/dm-sans-latin-700-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-700-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-400-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-400-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-500-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-500-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-700-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/dm-sans-latin-ext-700-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/space-grotesk-latin-500-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/space-grotesk-latin-500-normal.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/space-grotesk-latin-700-normal.woff2
Normal file
BIN
frontend/src/assets/fonts/space-grotesk-latin-700-normal.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
frontend/src/components/common/MacroBadge.vue
Normal file
29
frontend/src/components/common/MacroBadge.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
export type MacroType = 'protein' | 'carbs' | 'fat' | 'fiber'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
macro: MacroType
|
||||||
|
label: string
|
||||||
|
grams: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formattedGrams = computed(() => {
|
||||||
|
if (!Number.isFinite(props.grams)) {
|
||||||
|
return '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.grams.toLocaleString(undefined, {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span :class="['macro-badge', `macro-badge--${macro}`]">
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
<strong class="macro-badge__value">{{ formattedGrams }}g</strong>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import MacroBadge from '@/components/common/MacroBadge.vue'
|
||||||
import type { Meal, MealType } from '@/types/domain'
|
import type { Meal, MealType } from '@/types/domain'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -40,12 +41,15 @@ const selectedValue = computed({
|
|||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="day-meal-card__summary" v-if="meal?.totals">
|
<div class="day-meal-card__summary day-meal-card__summary--nutrition" v-if="meal?.totals">
|
||||||
{{ meal.totals.kcal }} {{ t('common.kcalUnit') }}
|
<span class="day-meal-card__kcal">{{ meal.totals.kcal }} {{ t('common.kcalUnit') }}</span>
|
||||||
· {{ t('nutrition.short.protein') }} {{ meal.totals.protein_g }} g
|
<div class="macro-badge-group macro-badge-group--compact">
|
||||||
· {{ t('nutrition.short.carbs') }} {{ meal.totals.carbs_g }} g
|
<MacroBadge macro="protein" :label="t('nutrition.short.protein')" :grams="meal.totals.protein_g" />
|
||||||
· {{ t('nutrition.short.fat') }} {{ meal.totals.fat_g }} g
|
<MacroBadge macro="carbs" :label="t('nutrition.short.carbs')" :grams="meal.totals.carbs_g" />
|
||||||
</p>
|
<MacroBadge macro="fat" :label="t('nutrition.short.fat')" :grams="meal.totals.fat_g" />
|
||||||
|
<MacroBadge macro="fiber" :label="t('nutrition.short.fiber')" :grams="meal.totals.fiber_g" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p class="day-meal-card__summary" v-else>
|
<p class="day-meal-card__summary" v-else>
|
||||||
{{ t('today.noItems') }}
|
{{ t('today.noItems') }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import MacroBadge from '@/components/common/MacroBadge.vue'
|
||||||
import type { NutritionTotals } from '@/types/domain'
|
import type { NutritionTotals } from '@/types/domain'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -12,22 +13,16 @@ const { t } = useI18n()
|
|||||||
<template>
|
<template>
|
||||||
<section class="card totals-card">
|
<section class="card totals-card">
|
||||||
<h3>{{ t('today.dayTotals') }}</h3>
|
<h3>{{ t('today.dayTotals') }}</h3>
|
||||||
<div class="totals-grid">
|
<div class="totals-card__row">
|
||||||
<div>
|
<div class="totals-card__kcal">
|
||||||
<span>{{ t('common.kcalUnit') }}</span>
|
<span class="totals-card__kcal-label">{{ t('common.kcalUnit') }}</span>
|
||||||
<strong>{{ totals.kcal }}</strong>
|
<strong class="totals-card__kcal-value">{{ totals.kcal }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="macro-badge-group macro-badge-group--right macro-badge-group--compact macro-badge-group--nowrap">
|
||||||
<span>{{ t('nutrition.labels.protein') }}</span>
|
<MacroBadge macro="protein" :label="t('nutrition.short.protein')" :grams="totals.protein_g" />
|
||||||
<strong>{{ totals.protein_g }} g</strong>
|
<MacroBadge macro="carbs" :label="t('nutrition.short.carbs')" :grams="totals.carbs_g" />
|
||||||
</div>
|
<MacroBadge macro="fat" :label="t('nutrition.short.fat')" :grams="totals.fat_g" />
|
||||||
<div>
|
<MacroBadge macro="fiber" :label="t('nutrition.short.fiber')" :grams="totals.fiber_g" />
|
||||||
<span>{{ t('nutrition.labels.carbs') }}</span>
|
|
||||||
<strong>{{ totals.carbs_g }} g</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>{{ t('nutrition.labels.fat') }}</span>
|
|
||||||
<strong>{{ totals.fat_g }} g</strong>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -110,7 +110,8 @@
|
|||||||
"short": {
|
"short": {
|
||||||
"protein": "B",
|
"protein": "B",
|
||||||
"carbs": "S",
|
"carbs": "S",
|
||||||
"fat": "T"
|
"fat": "T",
|
||||||
|
"fiber": "V"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"protein": "Bílkoviny",
|
"protein": "Bílkoviny",
|
||||||
|
|||||||
@ -110,7 +110,8 @@
|
|||||||
"short": {
|
"short": {
|
||||||
"protein": "E",
|
"protein": "E",
|
||||||
"carbs": "K",
|
"carbs": "K",
|
||||||
"fat": "F"
|
"fat": "F",
|
||||||
|
"fiber": "Ba"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"protein": "Eiweiß",
|
"protein": "Eiweiß",
|
||||||
|
|||||||
@ -110,7 +110,8 @@
|
|||||||
"short": {
|
"short": {
|
||||||
"protein": "P",
|
"protein": "P",
|
||||||
"carbs": "C",
|
"carbs": "C",
|
||||||
"fat": "F"
|
"fat": "F",
|
||||||
|
"fiber": "Fi"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"protein": "Protein",
|
"protein": "Protein",
|
||||||
|
|||||||
@ -110,7 +110,8 @@
|
|||||||
"short": {
|
"short": {
|
||||||
"protein": "P",
|
"protein": "P",
|
||||||
"carbs": "C",
|
"carbs": "C",
|
||||||
"fat": "G"
|
"fat": "G",
|
||||||
|
"fiber": "Fi"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"protein": "Proteínas",
|
"protein": "Proteínas",
|
||||||
|
|||||||
@ -110,7 +110,8 @@
|
|||||||
"short": {
|
"short": {
|
||||||
"protein": "B",
|
"protein": "B",
|
||||||
"carbs": "S",
|
"carbs": "S",
|
||||||
"fat": "T"
|
"fat": "T",
|
||||||
|
"fiber": "V"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"protein": "Bielkoviny",
|
"protein": "Bielkoviny",
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import IngredientForm from '@/components/ingredients/IngredientForm.vue'
|
import IngredientForm from '@/components/ingredients/IngredientForm.vue'
|
||||||
|
import MacroBadge from '@/components/common/MacroBadge.vue'
|
||||||
import { useIngredientsStore } from '@/stores/ingredients'
|
import { useIngredientsStore } from '@/stores/ingredients'
|
||||||
import { useUIStore } from '@/stores/ui'
|
import { useUIStore } from '@/stores/ui'
|
||||||
import type { Ingredient } from '@/types/domain'
|
import type { Ingredient } from '@/types/domain'
|
||||||
@ -132,11 +133,28 @@ const removeIngredient = async (ingredientId: number) => {
|
|||||||
<div class="list-row" v-for="ingredient in ingredientsStore.sortedItems" :key="ingredient.ingredient_id">
|
<div class="list-row" v-for="ingredient in ingredientsStore.sortedItems" :key="ingredient.ingredient_id">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ ingredient.name }}</strong>
|
<strong>{{ ingredient.name }}</strong>
|
||||||
<p>
|
<div class="macro-badge-group macro-badge-group--with-top-gap">
|
||||||
{{ t('nutrition.short.protein') }} {{ ingredient.protein_g_100 }}
|
<MacroBadge
|
||||||
· {{ t('nutrition.short.carbs') }} {{ ingredient.carbs_g_100 }}
|
macro="protein"
|
||||||
· {{ t('nutrition.short.fat') }} {{ ingredient.fat_g_100 }}
|
:label="t('nutrition.short.protein')"
|
||||||
</p>
|
:grams="ingredient.protein_g_100"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="carbs"
|
||||||
|
:label="t('nutrition.short.carbs')"
|
||||||
|
:grams="ingredient.carbs_g_100"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="fat"
|
||||||
|
:label="t('nutrition.short.fat')"
|
||||||
|
:grams="ingredient.fat_g_100"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="fiber"
|
||||||
|
:label="t('nutrition.short.fiber')"
|
||||||
|
:grams="ingredient.fiber_g_100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-row__actions">
|
<div class="list-row__actions">
|
||||||
<button class="btn" type="button" @click="startEdit(ingredient.ingredient_id)">{{ t('common.edit') }}</button>
|
<button class="btn" type="button" @click="startEdit(ingredient.ingredient_id)">{{ t('common.edit') }}</button>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { computed, onMounted, ref } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import MealTypeFilter from '@/components/meals/MealTypeFilter.vue'
|
import MealTypeFilter from '@/components/meals/MealTypeFilter.vue'
|
||||||
|
import MacroBadge from '@/components/common/MacroBadge.vue'
|
||||||
import { useMealsStore } from '@/stores/meals'
|
import { useMealsStore } from '@/stores/meals'
|
||||||
import { useUIStore } from '@/stores/ui'
|
import { useUIStore } from '@/stores/ui'
|
||||||
import type { MealType } from '@/types/domain'
|
import type { MealType } from '@/types/domain'
|
||||||
@ -96,7 +97,31 @@ const createMeal = async () => {
|
|||||||
<strong>{{ meal.name }}</strong>
|
<strong>{{ meal.name }}</strong>
|
||||||
<p>{{ t(`mealTypes.${meal.meal_type}`) }}</p>
|
<p>{{ t(`mealTypes.${meal.meal_type}`) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-row__meta">{{ meal.totals?.kcal ?? 0 }} {{ t('common.kcalUnit') }}</div>
|
<div class="list-row__meta list-row__meta--nutrition">
|
||||||
|
<div class="list-row__kcal">{{ meal.totals?.kcal ?? 0 }} {{ t('common.kcalUnit') }}</div>
|
||||||
|
<div class="macro-badge-group macro-badge-group--right macro-badge-group--compact">
|
||||||
|
<MacroBadge
|
||||||
|
macro="protein"
|
||||||
|
:label="t('nutrition.short.protein')"
|
||||||
|
:grams="meal.totals?.protein_g ?? 0"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="carbs"
|
||||||
|
:label="t('nutrition.short.carbs')"
|
||||||
|
:grams="meal.totals?.carbs_g ?? 0"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="fat"
|
||||||
|
:label="t('nutrition.short.fat')"
|
||||||
|
:grams="meal.totals?.fat_g ?? 0"
|
||||||
|
/>
|
||||||
|
<MacroBadge
|
||||||
|
macro="fiber"
|
||||||
|
:label="t('nutrition.short.fiber')"
|
||||||
|
:grams="meal.totals?.fiber_g ?? 0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="empty-state">{{ t('meals.empty') }}</p>
|
<p v-else class="empty-state">{{ t('meals.empty') }}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user