feat(heures) : calendrier des jours validés (vue Jour) + harmonisation Malio UI

- Calendrier MalioDate en vue Jour (Heures + Heures Conducteurs) : jours
  entièrement validés (admin) peints en vert. Endpoint GET
  /work-hours/validation-status?from=&to=[&driver=1] (scope conducteur inversé),
  chargement à la volée par mois, refresh après validation/saisie/absence.
- Suite à @malio/layer-ui 1.7.11 : reserveMessageSpace=false sur les champs ;
  tous les drawers migrés sur MalioDrawer (titre via slot #header, AppDrawer
  custom supprimé) ; boutons d'action en MalioButton (deux boutons partagent
  l'espace) ; inputs date en MalioDate ; MalioDateWeek en vue Semaine.
- Boutons d'ajout uniformisés sur « Ajouter » + icône.
- .env : EXCLUDED_PUBLIC_HOLIDAYS="null".
- Doc : doc/hours-validated-days.md, documentation-content.ts, CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 15:47:23 +02:00
parent 5d2b5d1c54
commit 34dc52d92b
37 changed files with 1881 additions and 495 deletions
+1 -1
View File
@@ -36,7 +36,7 @@
</div>
</div>
<div v-if="showPicker" class="mt-3 flex items-center gap-3">
<MalioSelect
<MalioSelect :reserve-message-space="false"
label="Contrat"
:model-value="selectedPhase?.id ?? null"
:options="phaseOptions"
+48 -53
View File
@@ -12,7 +12,7 @@
@click="openExportDrawer"
/>
<MalioButton
label="Ajouter un employé"
label="Ajouter"
icon-name="mdi:plus"
icon-position="left"
@click="openCreate"
@@ -21,14 +21,14 @@
</div>
<div class="flex items-center gap-3 py-7">
<div class="w-80">
<MalioInputText
<MalioInputText :reserve-message-space="false"
v-model="employeeFilter"
label="Recherche d'un employé"
icon-name="mdi:magnify"
/>
</div>
<div v-if="sites.length > 0" class="relative z-50 w-80">
<MalioSelectCheckbox
<MalioSelectCheckbox :reserve-message-space="false"
v-model="selectedSiteIds"
:options="siteOptions"
groupClass="w-80"
@@ -37,7 +37,7 @@
/>
</div>
<MalioSelect
<MalioSelect :reserve-message-space="false"
v-model="contractStatusFilter"
label="Statut contrat"
:options="contractStatusOptions"
@@ -84,21 +84,24 @@
</NuxtLink>
</div>
<MalioDrawer v-model="isDrawerOpen" :title="drawerTitle">
<MalioDrawer v-model="isDrawerOpen">
<template #header>
<h2 class="text-[32px] font-semibold text-primary-500">{{ drawerTitle }}</h2>
</template>
<form class="space-y-4" @submit.prevent="handleSubmit">
<MalioInputText
<MalioInputText :reserve-message-space="false"
v-model="form.firstName"
label="Prénom *"
group-class="mt-2"
:error="showFirstNameError ? 'Le prénom est obligatoire.' : ''"
/>
<MalioInputText
<MalioInputText :reserve-message-space="false"
v-model="form.lastName"
label="Nom *"
group-class="mt-2"
:error="showLastNameError ? 'Le nom est obligatoire.' : ''"
/>
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="form.siteId === '' ? null : form.siteId"
:options="formSiteOptions"
label="Site *"
@@ -107,7 +110,7 @@
@update:model-value="(v) => { form.siteId = v === null ? '' : Number(v) }"
/>
<template v-if="!editingEmployee">
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="form.contractNature"
:options="contractNatureFormOptions"
label="Type de contrat *"
@@ -115,7 +118,7 @@
:error="showContractNatureError ? 'Le type de contrat est obligatoire.' : ''"
@update:model-value="(v) => { if (v !== null) form.contractNature = v as 'CDI' | 'CDD' | 'INTERIM' }"
/>
<MalioSelect
<MalioSelect :reserve-message-space="false"
v-if="form.contractNature === 'INTERIM'"
:model-value="form.interimAgencyId === '' ? null : form.interimAgencyId"
:options="interimAgencyOptions"
@@ -123,7 +126,7 @@
min-width=""
@update:model-value="(v) => { form.interimAgencyId = v === null ? '' : Number(v) }"
/>
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="form.contractId === '' ? null : form.contractId"
:options="contractFormOptions"
label="Temps de travail *"
@@ -131,37 +134,27 @@
:error="showContractError ? 'Le temps de travail est obligatoire.' : ''"
@update:model-value="(v) => { form.contractId = v === null ? '' : Number(v) }"
/>
<div>
<label class="text-md font-semibold text-neutral-700" for="contract-start-date">
Début contrat <span class="text-red-600">*</span>
</label>
<input
id="contract-start-date"
v-model="form.contractStartDate"
type="date"
:class="[dateInputBaseClass, form.contractStartDate ? 'border-black' : 'border-m-muted', showContractStartDateError ? '!border-m-danger' : '']"
/>
<p v-if="showContractStartDateError" class="mt-1 text-sm text-red-600">
La date de début est obligatoire.
</p>
</div>
<div v-if="showsContractEndDateComputed">
<label class="text-md font-semibold text-neutral-700" for="contract-end-date">
Fin contrat
<span v-if="requiresContractEndDateComputed" class="text-red-600">*</span>
</label>
<input
id="contract-end-date"
v-model="form.contractEndDate"
type="date"
:class="[dateInputBaseClass, form.contractEndDate ? 'border-black' : 'border-m-muted', showContractEndDateError ? '!border-m-danger' : '']"
/>
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
La date de fin est obligatoire pour un CDD ou un Intérim.
</p>
</div>
<MalioDate
:model-value="form.contractStartDate"
label="Début contrat"
required
:reserve-message-space="false"
:error="showContractStartDateError ? 'La date de début est obligatoire.' : ''"
group-class="w-full"
@update:model-value="(v) => form.contractStartDate = v ?? ''"
/>
<MalioDate
v-if="showsContractEndDateComputed"
:model-value="form.contractEndDate"
label="Fin contrat"
:required="requiresContractEndDateComputed"
:reserve-message-space="false"
:error="showContractEndDateError ? 'La date de fin est obligatoire pour un CDD ou un Intérim.' : ''"
group-class="w-full"
@update:model-value="(v) => form.contractEndDate = v ?? ''"
/>
<div class="flex h-10 items-center rounded-md border border-neutral-200 bg-neutral-50 px-3">
<MalioCheckbox
<MalioCheckbox :reserve-message-space="false"
v-model="form.isDriver"
label="Chauffeur"
group-class="flex items-center"
@@ -173,24 +166,29 @@
:contract-weekly-hours="selectedContract?.weeklyHours ?? null"
/>
</template>
<div class="flex justify-end gap-3 pt-2">
<div class="grid grid-cols-2 gap-3 pt-2">
<MalioButton
label="Annuler"
variant="tertiary"
button-class="w-full"
@click="isDrawerOpen = false"
/>
<MalioButton
type="submit"
label="Enregistrer"
button-class="w-full"
:disabled="isSubmitting || !isFormValid"
@click="handleSubmit"
/>
</div>
</form>
</MalioDrawer>
<MalioDrawer v-model="isExportDrawerOpen" title="Export">
<MalioDrawer v-model="isExportDrawerOpen">
<template #header>
<h2 class="text-[32px] font-semibold text-primary-500">Export</h2>
</template>
<div class="space-y-4">
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="exportChoice === '' ? null : exportChoice"
:options="exportTypeOptions"
label="Type d'export"
@@ -213,14 +211,14 @@
</div>
<template v-else-if="exportChoice === 'yearly-hours'">
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="exportYear"
:options="exportYearOptions"
label="Année *"
min-width=""
@update:model-value="(v) => { if (v !== null) exportYear = Number(v) }"
/>
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="exportMonth === '' ? null : exportMonth"
:options="exportMonthOptions"
label="Mois *"
@@ -231,7 +229,7 @@
</template>
<div v-else-if="exportChoice === 'night-contingent'">
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="exportYear"
:options="exportYearOptions"
label="Année *"
@@ -241,14 +239,14 @@
</div>
<div v-else-if="exportChoice === 'overtime-contingent'" class="flex flex-col gap-4">
<MalioSelect
<MalioSelect :reserve-message-space="false"
:model-value="exportYear"
:options="exportYearOptions"
label="Année *"
min-width=""
@update:model-value="(v) => { if (v !== null) exportYear = Number(v) }"
/>
<MalioSelectCheckbox
<MalioSelectCheckbox :reserve-message-space="false"
v-model="exportSiteIds"
:options="siteOptions"
label="Sites"
@@ -467,9 +465,6 @@ const showContractEndDateError = computed(
() => !editingEmployee.value && validationTouched.contractEndDate && !isContractEndDateValid.value
)
const dateInputBaseClass =
'mt-2 h-10 w-full rounded-md border px-3 text-md text-black outline-none focus:border-2 focus:border-m-primary'
const formSiteOptions = computed(() =>
sites.value.map((site) => ({ label: site.name, value: site.id }))
)