refactor(front) : composant PageHeader unifié + standardisation des titres

- Nouveau composant ui/PageHeader.vue : source unique du style des titres
  (30px / semi-bold / bleu malio), sticky avec masquage du scroll, slots
  #actions et #subheader (barres de filtres/onglets collées au titre)
- Layout : marges <main> réduites (sm:px-6 lg:px-12 xl:px-11) + suppression
  du bloc-spacer sticky devenu inutile
- ~17 pages migrées vers <PageHeader>, un seul pattern partout
- Espacement titre -> contenu uniforme (30px), porté par le composant

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 11:47:47 +02:00
parent 386242c84d
commit 30b067afdd
19 changed files with 433 additions and 388 deletions
@@ -1,98 +1,101 @@
<template>
<div class="flex min-h-0 flex-1 flex-col">
<div ref="pageHeaderEl" class="sticky top-8 z-20 flex-shrink-0 bg-white pb-4 sm:top-12">
<div class="flex items-center justify-between gap-3">
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">Suivi des temps</h1>
<MalioButton
icon-name="mdi:plus"
icon-position="left"
button-class="shrink-0"
@click="openCreateDrawer()"
>
<span class="hidden sm:inline">Ajouter une Activité</span>
<span class="sm:hidden">Activité</span>
</MalioButton>
</div>
<div class="relative z-30 mt-4 flex flex-wrap items-center gap-3 sm:gap-4">
<div class="flex shrink-0 items-center gap-1 h-8">
<MalioButtonIcon
icon="mdi:chevron-left"
aria-label="Précédent"
variant="ghost"
@click="navigatePrev"
/>
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
<h2 class="shrink-0 whitespace-nowrap text-lg font-bold leading-8 text-orange-500">
{{ currentMonthLabel }}
</h2>
<MalioButtonIcon
icon="mdi:chevron-right"
aria-label="Suivant"
variant="ghost"
@click="navigateNext"
/>
</div>
<div class="flex items-center rounded-full bg-neutral-100 p-1">
<button
v-for="mode in (['week', 'day', 'list'] as const)"
:key="mode"
class="rounded-full px-4 py-1.5 text-sm font-semibold transition-all"
:class="viewMode === mode
? 'bg-primary-500 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-700'"
@click="viewMode = mode"
<div ref="pageHeaderEl" class="flex-shrink-0">
<PageHeader>
Suivi des temps
<template #actions>
<MalioButton
icon-name="mdi:plus"
icon-position="left"
button-class="shrink-0"
@click="openCreateDrawer()"
>
{{ mode === 'week' ? 'Semaine' : mode === 'day' ? 'Jour' : 'Liste' }}
</button>
</div>
<span class="hidden sm:inline">Ajouter une Activité</span>
<span class="sm:hidden">Activité</span>
</MalioButton>
</template>
<template #subheader>
<div class="relative z-30 mt-4 flex flex-wrap items-center gap-3 sm:gap-4">
<div class="flex shrink-0 items-center gap-1 h-8">
<MalioButtonIcon
icon="mdi:chevron-left"
aria-label="Précédent"
variant="ghost"
@click="navigatePrev"
/>
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
<h2 class="shrink-0 whitespace-nowrap text-lg font-bold leading-8 text-orange-500">
{{ currentMonthLabel }}
</h2>
<MalioButtonIcon
icon="mdi:chevron-right"
aria-label="Suivant"
variant="ghost"
@click="navigateNext"
/>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedUserId"
:options="userOptions"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
label="User"
empty-option-label="Tous"
/>
</div>
<div class="flex items-center rounded-full bg-neutral-100 p-1">
<button
v-for="mode in (['week', 'day', 'list'] as const)"
:key="mode"
class="rounded-full px-4 py-1.5 text-sm font-semibold transition-all"
:class="viewMode === mode
? 'bg-primary-500 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-700'"
@click="viewMode = mode"
>
{{ mode === 'week' ? 'Semaine' : mode === 'day' ? 'Jour' : 'Liste' }}
</button>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedProjectId"
:options="projectOptions"
empty-option-label="Tous"
label="Projet"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
/>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedUserId"
:options="userOptions"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
label="User"
empty-option-label="Tous"
/>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedTagId"
:options="tagOptions"
empty-option-label="Tous"
label="Tag"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
/>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedProjectId"
:options="projectOptions"
empty-option-label="Tous"
label="Projet"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
/>
</div>
<MalioButton
:label="$t('timeEntries.export')"
variant="secondary"
icon-name="mdi:download"
icon-position="left"
button-class="w-auto px-4"
@click="exportDrawerOpen = true"
/>
</div>
<div class="[&>div]:!mt-0">
<MalioSelect
v-model="selectedTagId"
:options="tagOptions"
empty-option-label="Tous"
label="Tag"
group-class="!w-36 sm:!w-44"
text-field="text-sm"
text-value="text-sm"
/>
</div>
<MalioButton
:label="$t('timeEntries.export')"
variant="secondary"
icon-name="mdi:download"
icon-position="left"
button-class="w-auto px-4"
@click="exportDrawerOpen = true"
/>
</div>
</template>
</PageHeader>
</div>
<div class="relative z-0 mt-4 -mb-24 min-h-0 flex-1">