StreakFit web: exercices planifiés en séance + filtre matériel + config matos profil
Build & Deploy / build (push) Successful in 23s
Build & Deploy / build (push) Successful in 23s
- session/[id]: affiche les exercices planifies de la routine (objectif series x reps) + pre-remplit la 1re serie - exercises/index: select Materiel + bouton 'Mon matos' (filtre sur le materiel du profil) - profile: multi-select du materiel possede + PATCH /api/me - i18n fr/en/pt-BR Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n()
|
||||
const { api } = useApi()
|
||||
const auth = useAuthStore()
|
||||
|
||||
interface ExerciseRow { id: number, name: string, slug: string, body_part: string | null, equipment: string | null, target: string | null, image_url: string | null }
|
||||
interface Facet { value: string, label: string }
|
||||
@@ -9,45 +10,78 @@ interface Paginated { data: ExerciseRow[], meta: { current_page: number, last_pa
|
||||
|
||||
const search = ref('')
|
||||
const bodyPart = ref<string | undefined>()
|
||||
const equipment = ref<string | undefined>()
|
||||
const myGear = ref(false)
|
||||
const page = ref(1)
|
||||
|
||||
const { data: facets } = await useAsyncData('ex-facets', () => api<Facets>('/api/exercises/facets', { query: { locale: locale.value } }), { lazy: true, watch: [locale] })
|
||||
|
||||
const query = computed(() => ({ search: search.value || undefined, body_part: bodyPart.value || undefined, page: page.value, locale: locale.value }))
|
||||
// Matériel possédé (config profil) → filtre "mon matos" (plusieurs équipements, envoyés en CSV).
|
||||
const myEquipmentCsv = computed(() => (auth.user?.available_equipment ?? []).join(','))
|
||||
const hasMyGear = computed(() => myEquipmentCsv.value.length > 0)
|
||||
const equipmentParam = computed(() => (myGear.value && hasMyGear.value) ? myEquipmentCsv.value : (equipment.value || undefined))
|
||||
|
||||
const query = computed(() => ({ search: search.value || undefined, body_part: bodyPart.value || undefined, equipment: equipmentParam.value, page: page.value, locale: locale.value }))
|
||||
const { data, pending } = await useAsyncData<Paginated>(
|
||||
'exercises',
|
||||
() => api<Paginated>('/api/exercises', { query: query.value }),
|
||||
{ watch: [query], lazy: true }
|
||||
)
|
||||
|
||||
watch([search, bodyPart], () => { page.value = 1 })
|
||||
watch([search, bodyPart, equipment, myGear], () => { page.value = 1 })
|
||||
|
||||
const bodyPartOptions = computed(() => [
|
||||
{ label: t('exercises.all'), value: undefined },
|
||||
...(facets.value?.body_parts ?? []).map(b => ({ label: b.label, value: b.value }))
|
||||
])
|
||||
const equipmentOptions = computed(() => [
|
||||
{ label: t('exercises.all'), value: undefined },
|
||||
...(facets.value?.equipments ?? []).map(e => ({ label: e.label, value: e.value }))
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-2xl font-black tracking-tight">{{ t('exercises.title') }}</h1>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<UInput
|
||||
v-model="search"
|
||||
icon="i-lucide-search"
|
||||
:placeholder="t('exercises.search')"
|
||||
size="lg"
|
||||
class="flex-1"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-model="bodyPart"
|
||||
:items="bodyPartOptions"
|
||||
value-key="value"
|
||||
:placeholder="t('exercises.bodyPart')"
|
||||
size="lg"
|
||||
class="w-36"
|
||||
/>
|
||||
<div class="space-y-2">
|
||||
<div class="flex gap-2">
|
||||
<UInput
|
||||
v-model="search"
|
||||
icon="i-lucide-search"
|
||||
:placeholder="t('exercises.search')"
|
||||
size="lg"
|
||||
class="flex-1"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-model="bodyPart"
|
||||
:items="bodyPartOptions"
|
||||
value-key="value"
|
||||
:placeholder="t('exercises.bodyPart')"
|
||||
size="lg"
|
||||
class="w-36"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<USelectMenu
|
||||
v-model="equipment"
|
||||
:items="equipmentOptions"
|
||||
value-key="value"
|
||||
:placeholder="t('exercises.equipment')"
|
||||
size="lg"
|
||||
class="flex-1"
|
||||
:disabled="myGear && hasMyGear"
|
||||
/>
|
||||
<UButton
|
||||
v-if="hasMyGear"
|
||||
:color="myGear ? 'primary' : 'neutral'"
|
||||
:variant="myGear ? 'solid' : 'subtle'"
|
||||
size="lg"
|
||||
icon="i-lucide-dumbbell"
|
||||
:label="t('exercises.myGear')"
|
||||
@click="myGear = !myGear"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="pending && !data" class="flex justify-center py-16">
|
||||
|
||||
Reference in New Issue
Block a user