added transalations for UI texts
This commit is contained in:
33
AGENTS.md
33
AGENTS.md
@ -15,27 +15,39 @@ It describes what the project is, what is already implemented, and what still ne
|
||||
- `backend/`
|
||||
- `frontend/`
|
||||
|
||||
## Current State (as of 2026-02-13)
|
||||
## Current State (as of 2026-02-14)
|
||||
|
||||
- `README.md` already contains product specification in Slovak and English.
|
||||
- Backend DB migrations exist in `backend/src/Maintenance.php` up to version `7`.
|
||||
- Backend API methods are implemented in `backend/src/API.php`.
|
||||
- Frontend auth page is now implemented:
|
||||
- Frontend auth page is implemented:
|
||||
- `frontend/src/App.vue` renders router view.
|
||||
- `frontend/src/router/index.ts` maps `/` to `frontend/src/views/AuthView.vue`.
|
||||
- `frontend/src/router/index.ts` maps `/` to `frontend/src/views/AuthView.vue` for guests.
|
||||
- `AuthView` serves as login + registration entry (single form, email + password).
|
||||
- Successful login stores `token` and `user_email` in `localStorage`.
|
||||
- Login now uses `frontend/src/stores/auth.ts` (`Pinia`) and redirects to authenticated app routes.
|
||||
- `frontend/src/views/AuthView.vue` formatting now uses tab-based indentation.
|
||||
- Login response parsing in `AuthView` now reads user fields from `response.data.user`.
|
||||
- Frontend i18n is wired:
|
||||
- Frontend authenticated area is implemented:
|
||||
- `frontend/src/views/AppLayout.vue` is shell layout.
|
||||
- Desktop navigation: `frontend/src/components/navigation/AppSidebar.vue`.
|
||||
- Mobile navigation: `frontend/src/components/navigation/AppBottomTabs.vue`.
|
||||
- Top bar/title: `frontend/src/components/navigation/AppTopbar.vue`.
|
||||
- Pages: `TodayView`, `MealsView`, `MealDetailView`, `IngredientsView`, `StatsView`, `SettingsView`.
|
||||
- Route guard is active for `/app/*` (requires token), guest-only for `/`.
|
||||
- Frontend i18n is wired and used across new UI:
|
||||
- setup in `frontend/src/i18n/index.ts`
|
||||
- locale files in `frontend/src/locales/{sk,cs,en,es,de}.ts`
|
||||
- locale files in `frontend/src/locales/{sk,cs,en,es,de}.json`
|
||||
- language switcher updates locale dynamically.
|
||||
- new app UI keys are added (`nav`, `pageTitles`, `mealTypes`, `common`, `nutrition`, `today`, `meals`, `ingredients`, `stats`, `settings`).
|
||||
- Frontend theme system is implemented:
|
||||
- light/dark mode toggle in auth page
|
||||
- design tokens in `frontend/src/assets/css/style.css` (`:root` variables).
|
||||
- App logo is served from `frontend/public/Nutrio.png` (copied from `doc/Nutrio.png`).
|
||||
- Font Awesome is installed and registered globally in `frontend/src/main.ts`.
|
||||
- Pinia is installed and configured in `frontend/src/main.ts` via `frontend/src/stores/index.ts`.
|
||||
- Frontend domain/store structure exists:
|
||||
- `frontend/src/types/domain.ts`
|
||||
- `frontend/src/stores/{auth,ingredients,meals,diary}.ts`
|
||||
- `frontend/src/utils/{nutrition,api,date}.ts`
|
||||
- `frontend/src/BackendAPI.ts` is generated via `backend/scripts/buildTypeScript.php` and should not be edited manually.
|
||||
- `backend/data.json` contains sample meal data (not currently wired into DB/API flow).
|
||||
|
||||
@ -143,7 +155,7 @@ All actions are invoked through `backend/public/API.php` with `?action=<method_n
|
||||
- APIlite response handling detail:
|
||||
- raw API response is wrapped as `{ status, data }`
|
||||
- generated `BackendAPI.ts` currently resolves `response.data` in `callPromise` for non-`__HELP__` actions
|
||||
- frontend parsing must match the actual returned runtime shape
|
||||
- frontend stores use `frontend/src/utils/api.ts` (`unwrapApiData`) to normalize both envelope and unwrapped runtime shapes
|
||||
- `frontend/src/BackendAPI.ts` is generated output; regenerate when backend API changes, do not patch manually.
|
||||
- In vue-i18n locale strings, `@` must be escaped as `{'@'}` to avoid "Invalid linked format" errors.
|
||||
|
||||
@ -169,8 +181,9 @@ Frontend:
|
||||
## Product Behavior Target (what to build next)
|
||||
|
||||
- Harden auth (token transport/header strategy, token revoke strategy, brute-force/rate-limits).
|
||||
- Build remaining frontend screens for ingredients, meals, meal item editor, diary day, diary range.
|
||||
- Connect frontend to implemented backend actions.
|
||||
- Polish authenticated frontend UX (validation messages, delete confirmations, optimistic updates, better loading/error states).
|
||||
- Add diary range screen/workflow on frontend (backend endpoint already exists).
|
||||
- Add i18n coverage for any future UI additions and keep keys stable.
|
||||
- Add API tests for validation, ownership checks, and totals calculation consistency.
|
||||
- Add pagination/filter strategy where list endpoints grow.
|
||||
|
||||
|
||||
@ -37,3 +37,9 @@ Preferencie:
|
||||
- UI môže byť čisté bez knižnice, alebo minimalisticky (napr. jednoduché CSS – rozhodni a drž konzistentne a pokracuj v pouzivani suboru frontend/src/assets/css/style.css).
|
||||
- Použi slovenské názvy v UI (Raňajky, Obed, Večera), ale kľúče v kóde nech sú anglické (breakfast/lunch/dinner).
|
||||
Výstup: konkrétny návrh + ukážky kódu, nie všeobecné rady.
|
||||
|
||||
----- 2026-02-14 07:16:25 -----------------------------------------------------
|
||||
dopln kluce pre UI texty pre preklad, pre kluce pouzivaj anglictinu, dopln potom aj vsetky preklady vo frontend/src/locales/*.json
|
||||
|
||||
----- 2026-02-14 07:33:30 -----------------------------------------------------
|
||||
doplniť jemnejší UX flow (toasty, confirm modaly pri delete, loading/error states per card)
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch } from 'vue'
|
||||
import { computed, reactive, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { Ingredient } from '@/types/domain'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -20,6 +21,12 @@ const emit = defineEmits<{
|
||||
(event: 'cancel'): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const formTitle = computed(() => {
|
||||
return props.initial ? t('ingredients.editTitle') : t('ingredients.newTitle')
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
protein_g_100: 0,
|
||||
@ -57,40 +64,40 @@ const onSubmit = () => {
|
||||
|
||||
<template>
|
||||
<form class="card ingredient-form" @submit.prevent="onSubmit">
|
||||
<h3>{{ props.initial ? 'Upraviť surovinu' : 'Nová surovina' }}</h3>
|
||||
<h3>{{ formTitle }}</h3>
|
||||
<div class="grid-two">
|
||||
<label>
|
||||
<span>Názov</span>
|
||||
<span>{{ t('ingredients.name') }}</span>
|
||||
<input v-model="form.name" class="input-text" type="text" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Protein / 100 g</span>
|
||||
<span>{{ t('ingredients.protein100') }}</span>
|
||||
<input v-model.number="form.protein_g_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Carbs / 100 g</span>
|
||||
<span>{{ t('ingredients.carbs100') }}</span>
|
||||
<input v-model.number="form.carbs_g_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Sugar / 100 g</span>
|
||||
<span>{{ t('ingredients.sugar100') }}</span>
|
||||
<input v-model.number="form.sugar_g_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Fat / 100 g</span>
|
||||
<span>{{ t('ingredients.fat100') }}</span>
|
||||
<input v-model.number="form.fat_g_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Fiber / 100 g</span>
|
||||
<span>{{ t('ingredients.fiber100') }}</span>
|
||||
<input v-model.number="form.fiber_g_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>Kcal / 100 g (0 = auto)</span>
|
||||
<span>{{ t('ingredients.kcal100Auto') }}</span>
|
||||
<input v-model.number="form.kcal_100" class="input-number" type="number" min="0" step="0.01" required />
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" type="submit">{{ props.submitLabel ?? 'Uložiť' }}</button>
|
||||
<button class="btn" type="button" @click="emit('cancel')">Zrušiť</button>
|
||||
<button class="btn btn-primary" type="submit">{{ props.submitLabel ?? t('ingredients.submitDefault') }}</button>
|
||||
<button class="btn" type="button" @click="emit('cancel')">{{ t('common.cancel') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { Ingredient, MealItem } from '@/types/domain'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -13,6 +14,8 @@ const emit = defineEmits<{
|
||||
(event: 'remove-item', mealItemId: number): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const addIngredientId = ref<number | null>(null)
|
||||
const addGrams = ref<number>(100)
|
||||
|
||||
@ -48,25 +51,25 @@ const addItem = () => {
|
||||
|
||||
<template>
|
||||
<section class="card meal-items-editor">
|
||||
<h3>Položky jedálnička</h3>
|
||||
<h3>{{ t('meals.itemsTitle') }}</h3>
|
||||
|
||||
<div class="meal-items-editor__add-row">
|
||||
<select v-model.number="addIngredientId" class="input-select">
|
||||
<option :value="null">Vyber surovinu</option>
|
||||
<option :value="null">{{ t('meals.selectIngredient') }}</option>
|
||||
<option v-for="ingredient in props.ingredients" :key="ingredient.ingredient_id" :value="ingredient.ingredient_id">
|
||||
{{ ingredient.name }}
|
||||
</option>
|
||||
</select>
|
||||
<input v-model.number="addGrams" type="number" min="1" class="input-number" />
|
||||
<button class="btn btn-primary" type="button" @click="addItem">Pridať</button>
|
||||
<button class="btn btn-primary" type="button" @click="addItem">{{ t('meals.addItem') }}</button>
|
||||
</div>
|
||||
|
||||
<table class="table" v-if="props.items.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Surovina</th>
|
||||
<th>Gramáž</th>
|
||||
<th>Akcia</th>
|
||||
<th>{{ t('ingredients.name') }}</th>
|
||||
<th>{{ t('meals.grams') }}</th>
|
||||
<th>{{ t('common.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -97,12 +100,12 @@ const addItem = () => {
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" type="button" @click="emit('remove-item', item.meal_item_id)">
|
||||
Zmazať
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p v-else class="empty-state">Tento jedálniček zatiaľ nemá položky.</p>
|
||||
<p v-else class="empty-state">{{ t('meals.noItems') }}</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { MealType } from '@/types/domain'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -8,20 +9,22 @@ const props = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: MealType | ''): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="filter-control">
|
||||
<span>Typ jedla</span>
|
||||
<span>{{ t('meals.mealTypeFilter') }}</span>
|
||||
<select
|
||||
class="input-select"
|
||||
:value="props.modelValue"
|
||||
@change="emit('update:modelValue', ($event.target as HTMLSelectElement).value as MealType | '')"
|
||||
>
|
||||
<option value="">Všetky</option>
|
||||
<option value="breakfast">Raňajky</option>
|
||||
<option value="lunch">Obed</option>
|
||||
<option value="dinner">Večera</option>
|
||||
<option value="">{{ t('common.all') }}</option>
|
||||
<option value="breakfast">{{ t('mealTypes.breakfast') }}</option>
|
||||
<option value="lunch">{{ t('mealTypes.lunch') }}</option>
|
||||
<option value="dinner">{{ t('mealTypes.dinner') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
const tabs = [
|
||||
{ to: { name: 'today' }, label: 'Dnes' },
|
||||
{ to: { name: 'meals' }, label: 'Jedlá' },
|
||||
{ to: { name: 'ingredients' }, label: 'Suroviny' },
|
||||
{ to: { name: 'stats' }, label: 'Stats' },
|
||||
{ to: { name: 'settings' }, label: 'Viac' },
|
||||
]
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const tabs = computed(() => [
|
||||
{ to: { name: 'today' }, label: t('nav.today') },
|
||||
{ to: { name: 'meals' }, label: t('nav.meals') },
|
||||
{ to: { name: 'ingredients' }, label: t('nav.ingredients') },
|
||||
{ to: { name: 'stats' }, label: t('nav.stats') },
|
||||
{ to: { name: 'settings' }, label: t('nav.more') },
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,18 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const navItems = [
|
||||
{ to: { name: 'today' }, label: 'Dnes' },
|
||||
{ to: { name: 'meals' }, label: 'Jedálničky' },
|
||||
{ to: { name: 'ingredients' }, label: 'Suroviny' },
|
||||
{ to: { name: 'stats' }, label: 'Štatistiky' },
|
||||
{ to: { name: 'settings' }, label: 'Nastavenia' },
|
||||
]
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ to: { name: 'today' }, label: t('nav.today') },
|
||||
{ to: { name: 'meals' }, label: t('nav.meals') },
|
||||
{ to: { name: 'ingredients' }, label: t('nav.ingredients') },
|
||||
{ to: { name: 'stats' }, label: t('nav.stats') },
|
||||
{ to: { name: 'settings' }, label: t('nav.settings') },
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="app-sidebar">
|
||||
<div class="app-sidebar__brand">
|
||||
<img src="/Nutrio.png" alt="Nutrio" class="app-sidebar__logo" />
|
||||
<strong>Nutrio</strong>
|
||||
<img src="/Nutrio.png" :alt="t('app.name')" class="app-sidebar__logo" />
|
||||
<strong>{{ t('app.name') }}</strong>
|
||||
</div>
|
||||
<nav class="app-sidebar__nav">
|
||||
<RouterLink
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const title = computed(() => {
|
||||
switch (route.name) {
|
||||
case 'today':
|
||||
return 'Denný prehľad'
|
||||
return t('pageTitles.today')
|
||||
case 'meals':
|
||||
return 'Jedálničky'
|
||||
return t('pageTitles.meals')
|
||||
case 'meal-detail':
|
||||
return 'Detail jedálnička'
|
||||
return t('pageTitles.mealDetail')
|
||||
case 'ingredients':
|
||||
return 'Suroviny'
|
||||
return t('pageTitles.ingredients')
|
||||
case 'stats':
|
||||
return 'Štatistiky'
|
||||
return t('pageTitles.stats')
|
||||
case 'settings':
|
||||
return 'Nastavenia'
|
||||
return t('pageTitles.settings')
|
||||
default:
|
||||
return 'Nutrio'
|
||||
return t('pageTitles.default')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { Meal, MealType } from '@/types/domain'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -14,6 +15,8 @@ const emit = defineEmits<{
|
||||
(event: 'select-meal', mealType: MealType, mealId: number | null): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const selectedValue = computed({
|
||||
get: () => (props.selectedMealId === null ? '' : String(props.selectedMealId)),
|
||||
set: (value: string) => {
|
||||
@ -32,16 +35,19 @@ const selectedValue = computed({
|
||||
<h3>{{ label }}</h3>
|
||||
</div>
|
||||
<select v-model="selectedValue" class="input-select">
|
||||
<option value="">Bez jedálnička</option>
|
||||
<option value="">{{ t('today.noMealPlan') }}</option>
|
||||
<option v-for="option in mealOptions" :key="option.value" :value="String(option.value)">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
<p class="day-meal-card__summary" v-if="meal?.totals">
|
||||
{{ meal.totals.kcal }} kcal · B {{ meal.totals.protein_g }} g · S {{ meal.totals.carbs_g }} g · T {{ meal.totals.fat_g }} g
|
||||
{{ meal.totals.kcal }} {{ t('common.kcalUnit') }}
|
||||
· {{ t('nutrition.short.protein') }} {{ meal.totals.protein_g }} g
|
||||
· {{ t('nutrition.short.carbs') }} {{ meal.totals.carbs_g }} g
|
||||
· {{ t('nutrition.short.fat') }} {{ meal.totals.fat_g }} g
|
||||
</p>
|
||||
<p class="day-meal-card__summary" v-else>
|
||||
Zatiaľ bez položiek.
|
||||
{{ t('today.noItems') }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,29 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { NutritionTotals } from '@/types/domain'
|
||||
|
||||
defineProps<{
|
||||
totals: NutritionTotals
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="card totals-card">
|
||||
<h3>Súčty dňa</h3>
|
||||
<h3>{{ t('today.dayTotals') }}</h3>
|
||||
<div class="totals-grid">
|
||||
<div>
|
||||
<span>Kcal</span>
|
||||
<span>{{ t('common.kcalUnit') }}</span>
|
||||
<strong>{{ totals.kcal }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Bielkoviny</span>
|
||||
<span>{{ t('nutrition.labels.protein') }}</span>
|
||||
<strong>{{ totals.protein_g }} g</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Sacharidy</span>
|
||||
<span>{{ t('nutrition.labels.carbs') }}</span>
|
||||
<strong>{{ totals.carbs_g }} g</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Tuky</span>
|
||||
<span>{{ t('nutrition.labels.fat') }}</span>
|
||||
<strong>{{ totals.fat_g }} g</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card" aria-hidden="true">
|
||||
<p>MealPickerModal placeholder</p>
|
||||
<p>{{ t('today.mealPickerPlaceholder') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -37,5 +37,102 @@
|
||||
"en": "Angličtina",
|
||||
"es": "Španělština",
|
||||
"de": "Němčina"
|
||||
},
|
||||
"nav": {
|
||||
"today": "Dnes",
|
||||
"meals": "Jídelníčky",
|
||||
"ingredients": "Suroviny",
|
||||
"stats": "Statistiky",
|
||||
"settings": "Nastavení",
|
||||
"more": "Více"
|
||||
},
|
||||
"pageTitles": {
|
||||
"today": "Denní přehled",
|
||||
"meals": "Jídelníčky",
|
||||
"mealDetail": "Detail jídelníčku",
|
||||
"ingredients": "Suroviny",
|
||||
"stats": "Statistiky",
|
||||
"settings": "Nastavení",
|
||||
"default": "Nutrio"
|
||||
},
|
||||
"mealTypes": {
|
||||
"breakfast": "Snídaně",
|
||||
"lunch": "Oběd",
|
||||
"dinner": "Večeře"
|
||||
},
|
||||
"common": {
|
||||
"date": "Datum",
|
||||
"all": "Všechny",
|
||||
"none": "Žádné",
|
||||
"save": "Uložit",
|
||||
"saving": "Ukládám...",
|
||||
"cancel": "Zrušit",
|
||||
"create": "Vytvořit",
|
||||
"edit": "Upravit",
|
||||
"delete": "Smazat",
|
||||
"actions": "Akce",
|
||||
"loading": "Načítám...",
|
||||
"kcalUnit": "kcal"
|
||||
},
|
||||
"nutrition": {
|
||||
"short": {
|
||||
"protein": "B",
|
||||
"carbs": "S",
|
||||
"fat": "T"
|
||||
},
|
||||
"labels": {
|
||||
"protein": "Bílkoviny",
|
||||
"carbs": "Sacharidy",
|
||||
"fat": "Tuky"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"loadingDay": "Načítám den...",
|
||||
"noMealPlan": "Bez jídelníčku",
|
||||
"noItems": "Zatím bez položek.",
|
||||
"dayTotals": "Součty dne",
|
||||
"mealPickerPlaceholder": "Výběr jídelníčku bude doplněn."
|
||||
},
|
||||
"meals": {
|
||||
"mealTypeFilter": "Typ jídla",
|
||||
"namePlaceholder": "Název jídelníčku",
|
||||
"createButton": "Vytvořit",
|
||||
"libraryTitle": "Knihovna jídelníčků",
|
||||
"empty": "Zatím nemáš žádné jídelníčky.",
|
||||
"loadingMeal": "Načítám jídelníček...",
|
||||
"nameLabel": "Název",
|
||||
"mealTypeLabel": "Typ jídla",
|
||||
"saveChanges": "Uložit změny",
|
||||
"deleteMeal": "Smazat jídelníček",
|
||||
"notFound": "Jídelníček neexistuje.",
|
||||
"itemsTitle": "Položky jídelníčku",
|
||||
"selectIngredient": "Vyber surovinu",
|
||||
"addItem": "Přidat",
|
||||
"noItems": "Tento jídelníček zatím nemá položky.",
|
||||
"grams": "Gramáž"
|
||||
},
|
||||
"ingredients": {
|
||||
"newTitle": "Nová surovina",
|
||||
"editTitle": "Upravit surovinu",
|
||||
"databaseTitle": "Databáze surovin",
|
||||
"newButton": "Nová surovina",
|
||||
"empty": "Zatím nemáš uložené suroviny.",
|
||||
"name": "Název",
|
||||
"protein100": "Bílkoviny / 100 g",
|
||||
"carbs100": "Sacharidy / 100 g",
|
||||
"sugar100": "Cukr / 100 g",
|
||||
"fat100": "Tuky / 100 g",
|
||||
"fiber100": "Vláknina / 100 g",
|
||||
"kcal100Auto": "Kcal / 100 g (0 = auto)",
|
||||
"submitDefault": "Uložit"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Statistiky",
|
||||
"placeholder": "Základní přehled bude doplněn v další iteraci."
|
||||
},
|
||||
"settings": {
|
||||
"accountTitle": "Nastavení účtu",
|
||||
"loggedInAs": "Přihlášený uživatel",
|
||||
"logout": "Odhlásit se"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,102 @@
|
||||
"en": "Englisch",
|
||||
"es": "Spanisch",
|
||||
"de": "Deutsch"
|
||||
},
|
||||
"nav": {
|
||||
"today": "Heute",
|
||||
"meals": "Mahlzeiten",
|
||||
"ingredients": "Zutaten",
|
||||
"stats": "Statistik",
|
||||
"settings": "Einstellungen",
|
||||
"more": "Mehr"
|
||||
},
|
||||
"pageTitles": {
|
||||
"today": "Tagesübersicht",
|
||||
"meals": "Mahlzeiten",
|
||||
"mealDetail": "Mahlzeit-Detail",
|
||||
"ingredients": "Zutaten",
|
||||
"stats": "Statistik",
|
||||
"settings": "Einstellungen",
|
||||
"default": "Nutrio"
|
||||
},
|
||||
"mealTypes": {
|
||||
"breakfast": "Frühstück",
|
||||
"lunch": "Mittagessen",
|
||||
"dinner": "Abendessen"
|
||||
},
|
||||
"common": {
|
||||
"date": "Datum",
|
||||
"all": "Alle",
|
||||
"none": "Keine",
|
||||
"save": "Speichern",
|
||||
"saving": "Speichert...",
|
||||
"cancel": "Abbrechen",
|
||||
"create": "Erstellen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"actions": "Aktion",
|
||||
"loading": "Lädt...",
|
||||
"kcalUnit": "kcal"
|
||||
},
|
||||
"nutrition": {
|
||||
"short": {
|
||||
"protein": "E",
|
||||
"carbs": "K",
|
||||
"fat": "F"
|
||||
},
|
||||
"labels": {
|
||||
"protein": "Eiweiß",
|
||||
"carbs": "Kohlenhydrate",
|
||||
"fat": "Fett"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"loadingDay": "Tag wird geladen...",
|
||||
"noMealPlan": "Kein Mahlzeitenplan",
|
||||
"noItems": "Noch keine Einträge.",
|
||||
"dayTotals": "Tagessummen",
|
||||
"mealPickerPlaceholder": "Mahlzeiten-Auswahl wird später ergänzt."
|
||||
},
|
||||
"meals": {
|
||||
"mealTypeFilter": "Mahlzeittyp",
|
||||
"namePlaceholder": "Name des Mahlzeitenplans",
|
||||
"createButton": "Erstellen",
|
||||
"libraryTitle": "Mahlzeiten-Bibliothek",
|
||||
"empty": "Du hast noch keine Mahlzeitenpläne.",
|
||||
"loadingMeal": "Mahlzeitenplan wird geladen...",
|
||||
"nameLabel": "Name",
|
||||
"mealTypeLabel": "Mahlzeittyp",
|
||||
"saveChanges": "Änderungen speichern",
|
||||
"deleteMeal": "Mahlzeitenplan löschen",
|
||||
"notFound": "Mahlzeitenplan existiert nicht.",
|
||||
"itemsTitle": "Mahlzeiten-Elemente",
|
||||
"selectIngredient": "Zutat auswählen",
|
||||
"addItem": "Hinzufügen",
|
||||
"noItems": "Dieser Mahlzeitenplan hat noch keine Einträge.",
|
||||
"grams": "Gramm"
|
||||
},
|
||||
"ingredients": {
|
||||
"newTitle": "Neue Zutat",
|
||||
"editTitle": "Zutat bearbeiten",
|
||||
"databaseTitle": "Zutaten-Datenbank",
|
||||
"newButton": "Neue Zutat",
|
||||
"empty": "Du hast noch keine gespeicherten Zutaten.",
|
||||
"name": "Name",
|
||||
"protein100": "Eiweiß / 100 g",
|
||||
"carbs100": "Kohlenhydrate / 100 g",
|
||||
"sugar100": "Zucker / 100 g",
|
||||
"fat100": "Fett / 100 g",
|
||||
"fiber100": "Ballaststoffe / 100 g",
|
||||
"kcal100Auto": "Kcal / 100 g (0 = auto)",
|
||||
"submitDefault": "Speichern"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Statistik",
|
||||
"placeholder": "Grundübersicht wird in der nächsten Iteration ergänzt."
|
||||
},
|
||||
"settings": {
|
||||
"accountTitle": "Kontoeinstellungen",
|
||||
"loggedInAs": "Angemeldet als",
|
||||
"logout": "Abmelden"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,102 @@
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
"de": "German"
|
||||
},
|
||||
"nav": {
|
||||
"today": "Today",
|
||||
"meals": "Meals",
|
||||
"ingredients": "Ingredients",
|
||||
"stats": "Stats",
|
||||
"settings": "Settings",
|
||||
"more": "More"
|
||||
},
|
||||
"pageTitles": {
|
||||
"today": "Daily Overview",
|
||||
"meals": "Meals",
|
||||
"mealDetail": "Meal Detail",
|
||||
"ingredients": "Ingredients",
|
||||
"stats": "Stats",
|
||||
"settings": "Settings",
|
||||
"default": "Nutrio"
|
||||
},
|
||||
"mealTypes": {
|
||||
"breakfast": "Breakfast",
|
||||
"lunch": "Lunch",
|
||||
"dinner": "Dinner"
|
||||
},
|
||||
"common": {
|
||||
"date": "Date",
|
||||
"all": "All",
|
||||
"none": "None",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"actions": "Action",
|
||||
"loading": "Loading...",
|
||||
"kcalUnit": "kcal"
|
||||
},
|
||||
"nutrition": {
|
||||
"short": {
|
||||
"protein": "P",
|
||||
"carbs": "C",
|
||||
"fat": "F"
|
||||
},
|
||||
"labels": {
|
||||
"protein": "Protein",
|
||||
"carbs": "Carbs",
|
||||
"fat": "Fat"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"loadingDay": "Loading day...",
|
||||
"noMealPlan": "No meal plan",
|
||||
"noItems": "No items yet.",
|
||||
"dayTotals": "Day totals",
|
||||
"mealPickerPlaceholder": "Meal picker will be added later."
|
||||
},
|
||||
"meals": {
|
||||
"mealTypeFilter": "Meal type",
|
||||
"namePlaceholder": "Meal plan name",
|
||||
"createButton": "Create",
|
||||
"libraryTitle": "Meal library",
|
||||
"empty": "You do not have any meal plans yet.",
|
||||
"loadingMeal": "Loading meal plan...",
|
||||
"nameLabel": "Name",
|
||||
"mealTypeLabel": "Meal type",
|
||||
"saveChanges": "Save changes",
|
||||
"deleteMeal": "Delete meal plan",
|
||||
"notFound": "Meal plan does not exist.",
|
||||
"itemsTitle": "Meal items",
|
||||
"selectIngredient": "Select ingredient",
|
||||
"addItem": "Add",
|
||||
"noItems": "This meal plan has no items yet.",
|
||||
"grams": "Grams"
|
||||
},
|
||||
"ingredients": {
|
||||
"newTitle": "New ingredient",
|
||||
"editTitle": "Edit ingredient",
|
||||
"databaseTitle": "Ingredient database",
|
||||
"newButton": "New ingredient",
|
||||
"empty": "You do not have any saved ingredients yet.",
|
||||
"name": "Name",
|
||||
"protein100": "Protein / 100 g",
|
||||
"carbs100": "Carbs / 100 g",
|
||||
"sugar100": "Sugar / 100 g",
|
||||
"fat100": "Fat / 100 g",
|
||||
"fiber100": "Fiber / 100 g",
|
||||
"kcal100Auto": "Kcal / 100 g (0 = auto)",
|
||||
"submitDefault": "Save"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Stats",
|
||||
"placeholder": "Basic overview will be added in the next iteration."
|
||||
},
|
||||
"settings": {
|
||||
"accountTitle": "Account settings",
|
||||
"loggedInAs": "Logged in as",
|
||||
"logout": "Log out"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,102 @@
|
||||
"en": "Inglés",
|
||||
"es": "Español",
|
||||
"de": "Alemán"
|
||||
},
|
||||
"nav": {
|
||||
"today": "Hoy",
|
||||
"meals": "Menús",
|
||||
"ingredients": "Ingredientes",
|
||||
"stats": "Estadísticas",
|
||||
"settings": "Ajustes",
|
||||
"more": "Más"
|
||||
},
|
||||
"pageTitles": {
|
||||
"today": "Resumen diario",
|
||||
"meals": "Menús",
|
||||
"mealDetail": "Detalle del menú",
|
||||
"ingredients": "Ingredientes",
|
||||
"stats": "Estadísticas",
|
||||
"settings": "Ajustes",
|
||||
"default": "Nutrio"
|
||||
},
|
||||
"mealTypes": {
|
||||
"breakfast": "Desayuno",
|
||||
"lunch": "Comida",
|
||||
"dinner": "Cena"
|
||||
},
|
||||
"common": {
|
||||
"date": "Fecha",
|
||||
"all": "Todos",
|
||||
"none": "Ninguno",
|
||||
"save": "Guardar",
|
||||
"saving": "Guardando...",
|
||||
"cancel": "Cancelar",
|
||||
"create": "Crear",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"actions": "Acción",
|
||||
"loading": "Cargando...",
|
||||
"kcalUnit": "kcal"
|
||||
},
|
||||
"nutrition": {
|
||||
"short": {
|
||||
"protein": "P",
|
||||
"carbs": "C",
|
||||
"fat": "G"
|
||||
},
|
||||
"labels": {
|
||||
"protein": "Proteínas",
|
||||
"carbs": "Carbohidratos",
|
||||
"fat": "Grasas"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"loadingDay": "Cargando día...",
|
||||
"noMealPlan": "Sin menú",
|
||||
"noItems": "Todavía sin elementos.",
|
||||
"dayTotals": "Totales del día",
|
||||
"mealPickerPlaceholder": "El selector de menú se añadirá más tarde."
|
||||
},
|
||||
"meals": {
|
||||
"mealTypeFilter": "Tipo de comida",
|
||||
"namePlaceholder": "Nombre del menú",
|
||||
"createButton": "Crear",
|
||||
"libraryTitle": "Biblioteca de menús",
|
||||
"empty": "Aún no tienes menús.",
|
||||
"loadingMeal": "Cargando menú...",
|
||||
"nameLabel": "Nombre",
|
||||
"mealTypeLabel": "Tipo de comida",
|
||||
"saveChanges": "Guardar cambios",
|
||||
"deleteMeal": "Eliminar menú",
|
||||
"notFound": "El menú no existe.",
|
||||
"itemsTitle": "Elementos del menú",
|
||||
"selectIngredient": "Selecciona ingrediente",
|
||||
"addItem": "Añadir",
|
||||
"noItems": "Este menú aún no tiene elementos.",
|
||||
"grams": "Gramos"
|
||||
},
|
||||
"ingredients": {
|
||||
"newTitle": "Nuevo ingrediente",
|
||||
"editTitle": "Editar ingrediente",
|
||||
"databaseTitle": "Base de datos de ingredientes",
|
||||
"newButton": "Nuevo ingrediente",
|
||||
"empty": "Aún no tienes ingredientes guardados.",
|
||||
"name": "Nombre",
|
||||
"protein100": "Proteína / 100 g",
|
||||
"carbs100": "Carbohidratos / 100 g",
|
||||
"sugar100": "Azúcar / 100 g",
|
||||
"fat100": "Grasas / 100 g",
|
||||
"fiber100": "Fibra / 100 g",
|
||||
"kcal100Auto": "Kcal / 100 g (0 = auto)",
|
||||
"submitDefault": "Guardar"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Estadísticas",
|
||||
"placeholder": "El resumen básico se añadirá en la próxima iteración."
|
||||
},
|
||||
"settings": {
|
||||
"accountTitle": "Ajustes de cuenta",
|
||||
"loggedInAs": "Sesión iniciada como",
|
||||
"logout": "Cerrar sesión"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,5 +37,102 @@
|
||||
"en": "Angličtina",
|
||||
"es": "Španielčina",
|
||||
"de": "Nemčina"
|
||||
},
|
||||
"nav": {
|
||||
"today": "Dnes",
|
||||
"meals": "Jedálničky",
|
||||
"ingredients": "Suroviny",
|
||||
"stats": "Štatistiky",
|
||||
"settings": "Nastavenia",
|
||||
"more": "Viac"
|
||||
},
|
||||
"pageTitles": {
|
||||
"today": "Denný prehľad",
|
||||
"meals": "Jedálničky",
|
||||
"mealDetail": "Detail jedálnička",
|
||||
"ingredients": "Suroviny",
|
||||
"stats": "Štatistiky",
|
||||
"settings": "Nastavenia",
|
||||
"default": "Nutrio"
|
||||
},
|
||||
"mealTypes": {
|
||||
"breakfast": "Raňajky",
|
||||
"lunch": "Obed",
|
||||
"dinner": "Večera"
|
||||
},
|
||||
"common": {
|
||||
"date": "Dátum",
|
||||
"all": "Všetky",
|
||||
"none": "Žiadne",
|
||||
"save": "Uložiť",
|
||||
"saving": "Ukladám...",
|
||||
"cancel": "Zrušiť",
|
||||
"create": "Vytvoriť",
|
||||
"edit": "Upraviť",
|
||||
"delete": "Zmazať",
|
||||
"actions": "Akcia",
|
||||
"loading": "Načítavam...",
|
||||
"kcalUnit": "kcal"
|
||||
},
|
||||
"nutrition": {
|
||||
"short": {
|
||||
"protein": "B",
|
||||
"carbs": "S",
|
||||
"fat": "T"
|
||||
},
|
||||
"labels": {
|
||||
"protein": "Bielkoviny",
|
||||
"carbs": "Sacharidy",
|
||||
"fat": "Tuky"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"loadingDay": "Načítavam deň...",
|
||||
"noMealPlan": "Bez jedálnička",
|
||||
"noItems": "Zatiaľ bez položiek.",
|
||||
"dayTotals": "Súčty dňa",
|
||||
"mealPickerPlaceholder": "Výber jedálnička bude doplnený."
|
||||
},
|
||||
"meals": {
|
||||
"mealTypeFilter": "Typ jedla",
|
||||
"namePlaceholder": "Názov jedálnička",
|
||||
"createButton": "Vytvoriť",
|
||||
"libraryTitle": "Knižnica jedálničkov",
|
||||
"empty": "Zatiaľ nemáš žiadne jedálničky.",
|
||||
"loadingMeal": "Načítavam jedálniček...",
|
||||
"nameLabel": "Názov",
|
||||
"mealTypeLabel": "Typ jedla",
|
||||
"saveChanges": "Uložiť zmeny",
|
||||
"deleteMeal": "Zmazať jedálniček",
|
||||
"notFound": "Jedálniček neexistuje.",
|
||||
"itemsTitle": "Položky jedálnička",
|
||||
"selectIngredient": "Vyber surovinu",
|
||||
"addItem": "Pridať",
|
||||
"noItems": "Tento jedálniček zatiaľ nemá položky.",
|
||||
"grams": "Gramáž"
|
||||
},
|
||||
"ingredients": {
|
||||
"newTitle": "Nová surovina",
|
||||
"editTitle": "Upraviť surovinu",
|
||||
"databaseTitle": "Databáza surovín",
|
||||
"newButton": "Nová surovina",
|
||||
"empty": "Zatiaľ nemáš uložené suroviny.",
|
||||
"name": "Názov",
|
||||
"protein100": "Bielkoviny / 100 g",
|
||||
"carbs100": "Sacharidy / 100 g",
|
||||
"sugar100": "Cukor / 100 g",
|
||||
"fat100": "Tuky / 100 g",
|
||||
"fiber100": "Vláknina / 100 g",
|
||||
"kcal100Auto": "Kcal / 100 g (0 = auto)",
|
||||
"submitDefault": "Uložiť"
|
||||
},
|
||||
"stats": {
|
||||
"title": "Štatistiky",
|
||||
"placeholder": "Základný prehľad bude doplnený v ďalšej iterácii."
|
||||
},
|
||||
"settings": {
|
||||
"accountTitle": "Nastavenia účtu",
|
||||
"loggedInAs": "Prihlásený používateľ",
|
||||
"logout": "Odhlásiť sa"
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,9 +69,3 @@ export interface DiaryDay {
|
||||
export type DayMealsByType = Record<MealType, Meal | null>
|
||||
|
||||
export const MEAL_TYPES: MealType[] = ['breakfast', 'lunch', 'dinner']
|
||||
|
||||
export const MEAL_TYPE_LABELS_SK: Record<MealType, string> = {
|
||||
breakfast: 'Raňajky',
|
||||
lunch: 'Obed',
|
||||
dinner: 'Večera',
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import IngredientForm from '@/components/ingredients/IngredientForm.vue'
|
||||
import { useIngredientsStore } from '@/stores/ingredients'
|
||||
import type { Ingredient } from '@/types/domain'
|
||||
@ -7,6 +8,7 @@ import type { Ingredient } from '@/types/domain'
|
||||
const ingredientsStore = useIngredientsStore()
|
||||
const editingId = ref<number | null>(null)
|
||||
const saving = ref(false)
|
||||
const { t } = useI18n()
|
||||
|
||||
onMounted(async () => {
|
||||
if (ingredientsStore.items.length <= 0) {
|
||||
@ -67,37 +69,39 @@ const removeIngredient = async (ingredientId: number) => {
|
||||
<section class="page">
|
||||
<IngredientForm
|
||||
:initial="editingIngredient"
|
||||
:submit-label="saving ? 'Ukladám...' : 'Uložiť'"
|
||||
:submit-label="saving ? t('common.saving') : t('common.save')"
|
||||
@save="saveIngredient"
|
||||
@cancel="cancelEdit"
|
||||
/>
|
||||
|
||||
<section class="card">
|
||||
<div class="section-header">
|
||||
<h3>Databáza surovín</h3>
|
||||
<button class="btn" type="button" @click="startCreate">Nová surovina</button>
|
||||
<h3>{{ t('ingredients.databaseTitle') }}</h3>
|
||||
<button class="btn" type="button" @click="startCreate">{{ t('ingredients.newButton') }}</button>
|
||||
</div>
|
||||
<div class="list" v-if="ingredientsStore.sortedItems.length > 0">
|
||||
<div class="list-row" v-for="ingredient in ingredientsStore.sortedItems" :key="ingredient.ingredient_id">
|
||||
<div>
|
||||
<strong>{{ ingredient.name }}</strong>
|
||||
<p>
|
||||
B {{ ingredient.protein_g_100 }} · S {{ ingredient.carbs_g_100 }} · T {{ ingredient.fat_g_100 }}
|
||||
{{ t('nutrition.short.protein') }} {{ ingredient.protein_g_100 }}
|
||||
· {{ t('nutrition.short.carbs') }} {{ ingredient.carbs_g_100 }}
|
||||
· {{ t('nutrition.short.fat') }} {{ ingredient.fat_g_100 }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="list-row__actions">
|
||||
<button class="btn" type="button" @click="startEdit(ingredient.ingredient_id)">Upraviť</button>
|
||||
<button class="btn" type="button" @click="startEdit(ingredient.ingredient_id)">{{ t('common.edit') }}</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
type="button"
|
||||
@click="removeIngredient(ingredient.ingredient_id)"
|
||||
>
|
||||
Zmazať
|
||||
{{ t('common.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty-state">Zatiaľ nemáš uložené suroviny.</p>
|
||||
<p v-else class="empty-state">{{ t('ingredients.empty') }}</p>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MealItemsEditor from '@/components/meals/MealItemsEditor.vue'
|
||||
import { useIngredientsStore } from '@/stores/ingredients'
|
||||
import { useMealsStore } from '@/stores/meals'
|
||||
@ -10,6 +11,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const mealsStore = useMealsStore()
|
||||
const ingredientsStore = useIngredientsStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
@ -117,29 +119,29 @@ const removeItem = async (mealItemId: number) => {
|
||||
|
||||
<template>
|
||||
<section class="page">
|
||||
<div v-if="loading" class="card">Načítavam jedálniček...</div>
|
||||
<div v-if="loading" class="card">{{ t('meals.loadingMeal') }}</div>
|
||||
|
||||
<template v-else-if="meal">
|
||||
<section class="card">
|
||||
<div class="grid-two">
|
||||
<label>
|
||||
<span>Názov</span>
|
||||
<span>{{ t('meals.nameLabel') }}</span>
|
||||
<input v-model="mealName" class="input-text" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Typ jedla</span>
|
||||
<span>{{ t('meals.mealTypeLabel') }}</span>
|
||||
<select v-model="mealType" class="input-select">
|
||||
<option value="breakfast">Raňajky</option>
|
||||
<option value="lunch">Obed</option>
|
||||
<option value="dinner">Večera</option>
|
||||
<option value="breakfast">{{ t('mealTypes.breakfast') }}</option>
|
||||
<option value="lunch">{{ t('mealTypes.lunch') }}</option>
|
||||
<option value="dinner">{{ t('mealTypes.dinner') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn btn-primary" type="button" :disabled="saving" @click="saveMeal">
|
||||
{{ saving ? 'Ukladám...' : 'Uložiť zmeny' }}
|
||||
{{ saving ? t('common.saving') : t('meals.saveChanges') }}
|
||||
</button>
|
||||
<button class="btn btn-danger" type="button" @click="removeMeal">Zmazať jedálniček</button>
|
||||
<button class="btn btn-danger" type="button" @click="removeMeal">{{ t('meals.deleteMeal') }}</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -152,6 +154,6 @@ const removeItem = async (mealItemId: number) => {
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div v-else class="card">Jedálniček neexistuje.</div>
|
||||
<div v-else class="card">{{ t('meals.notFound') }}</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MealTypeFilter from '@/components/meals/MealTypeFilter.vue'
|
||||
import { useMealsStore } from '@/stores/meals'
|
||||
import type { MealType } from '@/types/domain'
|
||||
import { MEAL_TYPE_LABELS_SK } from '@/types/domain'
|
||||
|
||||
const router = useRouter()
|
||||
const mealsStore = useMealsStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const filterType = ref<MealType | ''>('')
|
||||
const newMealName = ref('')
|
||||
@ -53,19 +54,19 @@ const createMeal = async () => {
|
||||
</div>
|
||||
|
||||
<section class="card form-inline">
|
||||
<input v-model="newMealName" class="input-text" type="text" placeholder="Názov jedálnička" />
|
||||
<input v-model="newMealName" class="input-text" type="text" :placeholder="t('meals.namePlaceholder')" />
|
||||
<select v-model="newMealType" class="input-select">
|
||||
<option value="breakfast">Raňajky</option>
|
||||
<option value="lunch">Obed</option>
|
||||
<option value="dinner">Večera</option>
|
||||
<option value="breakfast">{{ t('mealTypes.breakfast') }}</option>
|
||||
<option value="lunch">{{ t('mealTypes.lunch') }}</option>
|
||||
<option value="dinner">{{ t('mealTypes.dinner') }}</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" type="button" :disabled="creating" @click="createMeal">
|
||||
{{ creating ? 'Ukladám...' : 'Vytvoriť' }}
|
||||
{{ creating ? t('common.saving') : t('meals.createButton') }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h3>Knižnica jedálničkov</h3>
|
||||
<h3>{{ t('meals.libraryTitle') }}</h3>
|
||||
<div class="list" v-if="filteredMeals.length > 0">
|
||||
<RouterLink
|
||||
v-for="meal in filteredMeals"
|
||||
@ -75,12 +76,12 @@ const createMeal = async () => {
|
||||
>
|
||||
<div>
|
||||
<strong>{{ meal.name }}</strong>
|
||||
<p>{{ MEAL_TYPE_LABELS_SK[meal.meal_type] }}</p>
|
||||
<p>{{ t(`mealTypes.${meal.meal_type}`) }}</p>
|
||||
</div>
|
||||
<div class="list-row__meta">{{ meal.totals?.kcal ?? 0 }} kcal</div>
|
||||
<div class="list-row__meta">{{ meal.totals?.kcal ?? 0 }} {{ t('common.kcalUnit') }}</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<p v-else class="empty-state">Zatiaľ nemáš žiadne jedálničky.</p>
|
||||
<p v-else class="empty-state">{{ t('meals.empty') }}</p>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const onLogout = async () => {
|
||||
await auth.logout()
|
||||
@ -14,9 +16,11 @@ const onLogout = async () => {
|
||||
<template>
|
||||
<section class="page">
|
||||
<section class="card settings-card">
|
||||
<h3>Nastavenia účtu</h3>
|
||||
<p v-if="auth.userEmail">Prihlásený používateľ: <strong>{{ auth.userEmail }}</strong></p>
|
||||
<button class="btn btn-danger" type="button" @click="onLogout">Odhlásiť sa</button>
|
||||
<h3>{{ t('settings.accountTitle') }}</h3>
|
||||
<p v-if="auth.userEmail">
|
||||
{{ t('settings.loggedInAs') }}: <strong>{{ auth.userEmail }}</strong>
|
||||
</p>
|
||||
<button class="btn btn-danger" type="button" @click="onLogout">{{ t('settings.logout') }}</button>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page">
|
||||
<section class="card">
|
||||
<h3>Štatistiky</h3>
|
||||
<p>Základný prehľad bude doplnený v ďalšej iterácii.</p>
|
||||
<h3>{{ t('stats.title') }}</h3>
|
||||
<p>{{ t('stats.placeholder') }}</p>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import DayMealCard from '@/components/today/DayMealCard.vue'
|
||||
import DayTotalsCard from '@/components/today/DayTotalsCard.vue'
|
||||
import { useDiaryStore } from '@/stores/diary'
|
||||
import { useMealsStore } from '@/stores/meals'
|
||||
import { MEAL_TYPES, MEAL_TYPE_LABELS_SK, type MealType } from '@/types/domain'
|
||||
import { MEAL_TYPES, type MealType } from '@/types/domain'
|
||||
import { todayISO } from '@/utils/date'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const diaryStore = useDiaryStore()
|
||||
const mealsStore = useMealsStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isWorking = ref(false)
|
||||
const selectedDate = ref(todayISO())
|
||||
@ -21,6 +23,8 @@ const resolveDate = (): string => {
|
||||
return dateFromRoute.length > 0 ? dateFromRoute : todayISO()
|
||||
}
|
||||
|
||||
const mealTypeLabel = (mealType: MealType): string => t(`mealTypes.${mealType}`)
|
||||
|
||||
const reloadDay = async (date: string) => {
|
||||
selectedDate.value = date
|
||||
diaryStore.ensureCurrentDay(date)
|
||||
@ -66,12 +70,12 @@ const dayTotals = computed(() => diaryStore.computedTotals)
|
||||
<section class="page">
|
||||
<div class="page-header">
|
||||
<label class="filter-control">
|
||||
<span>Dátum</span>
|
||||
<span>{{ t('common.date') }}</span>
|
||||
<input v-model="selectedDate" class="input-date" type="date" @change="onDateChange" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="isWorking || diaryStore.loading" class="card">Načítavam deň...</div>
|
||||
<div v-if="isWorking || diaryStore.loading" class="card">{{ t('today.loadingDay') }}</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="grid-three">
|
||||
@ -79,7 +83,7 @@ const dayTotals = computed(() => diaryStore.computedTotals)
|
||||
v-for="mealType in MEAL_TYPES"
|
||||
:key="mealType"
|
||||
:meal-type="mealType"
|
||||
:label="MEAL_TYPE_LABELS_SK[mealType]"
|
||||
:label="mealTypeLabel(mealType)"
|
||||
:meal-options="diaryStore.selectedMealOptions[mealType]"
|
||||
:selected-meal-id="diaryStore.selectedMealId(mealType)"
|
||||
:meal="dayMeals[mealType]"
|
||||
|
||||
@ -9,13 +9,17 @@ import pkg from './package.json';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0", // sprístupní server na všetkých interfejsoch
|
||||
port: 5173, // môžeš zmeniť port, ak potrebuješ
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user