# Calendrier des jours validés (écran Heures — vue Jour)

## Objectif
Sur l'écran **Heures**, en **vue Jour**, le sélecteur de date est un calendrier
(composant `MalioDate` du layer `@malio/layer-ui`) qui **peint en vert** les jours
entièrement validés par un admin. La RH repère ainsi d'un coup d'œil les jours où
il reste de la validation à faire.

## Définition « jour validé » (vert)
Un jour est vert ssi, dans le **périmètre complet** de l'utilisateur :
- il porte **au moins une ligne `WorkHour`** dans le scope ciblé ce jour-là, **et**
- **aucune** de ces lignes n'est en attente de validation (`isValid = false`).

La même mécanique sert les **deux écrans**, avec un scope opposé : écran **Heures** →
non-conducteurs (défaut) ; écran **Heures Conducteurs** → conducteurs (`?driver=1`).

Conséquences :
- Un jour **sans aucune ligne** (rien saisi, ex. week-end, jour futur) reste **neutre**
  (jamais vert) — « rien fait » n'est pas « tout validé ».
- On se base sur la **seule** colonne `work_hours.is_valid` (validation admin/RH).
  `isSiteValid` (chef de site) n'entre pas en compte → modifier une validation site
  ne change pas la couleur.
- **Scope conducteur** : écran Heures → conducteurs exclus ; écran Heures Conducteurs →
  seuls les conducteurs (le filtre est inversé via `?driver=1`).

## Périmètre
- `ROLE_ADMIN` → tous les employés / tous les sites.
- Chef de site → ses sites uniquement.
- Le **filtre sites de l'écran est volontairement ignoré** : le vert reflète tout le
  périmètre (objectif : repérer le moindre jour incomplet, où qu'il soit). Changer le
  filtre sites de la vue Jour ne recalcule pas le calendrier.

## Chargement des données
- Endpoint : `GET /work-hours/validation-status?from=YYYY-MM-DD&to=YYYY-MM-DD[&driver=1]`
  (`ROLE_USER`). Réponse : `{ from, to, validatedDays: string[] }` (dates `Y-m-d`).
- Provider : `App\State\WorkHourValidationStatusProvider`
  (ressource `App\ApiResource\WorkHourValidationStatus`).
  - `EmployeeRepository::findScoped($user)` pour le périmètre (ignore tout `siteIds`).
  - Une requête `WorkHourReadRepositoryInterface::findByDateRangeAndEmployees`.
  - Filtrage conducteur **par date** via `EmployeeContractResolver::resolveIsDriverForEmployeeAndDate`
    (mémoïsé par couple employé/jour) : `if ($isDriver !== $driverOnly) continue;`
    (`driverOnly` = `?driver=1`).
  - Agrégation par jour : vert ⇔ `total > 0` (lignes du scope) et `pending = 0`
    (aucune `isValid=false`).
  - Garde-fou : plage bornée à 366 jours.
- Le chargement est **à la volée par mois affiché** (jamais préchargé sur plusieurs
  années) : `MalioDate` émet `@month-change { month, year }` à l'ouverture du popover et
  à chaque navigation ; le front fetch la **grille visible** (lundi avant le 1er →
  dimanche après le dernier jour, pour colorer aussi les jours débordants) et met le
  résultat en cache par mois (`useHoursPage` / `useDriverHoursPage` → `validatedDaysByMonth` ;
  ce dernier appelle le service avec `{ driver: true }`). La prop réactive `markedDates`
  (ISO → `'success'`) recolore la grille.

## Rafraîchissement
Toute action qui touche la validation d'un jour recharge le mois concerné s'il est en
cache (`reloadValidationMonth`), donc le calendrier se recolore aussitôt :
- validation admin d'une ligne (`toggleValidation`) ou en masse (`toggleValidationBulk`) ;
- sauvegarde d'heures (`handleSave`) — toute modification réelle remet `isValid=false` ;
- création / suppression d'absence (`refreshAfterAbsenceChange`).
La validation **site** ne déclenche pas de rechargement (sans effet sur le vert).

## Périmètre d'affichage
- **Vue Jour uniquement** : le vert (calendrier `MalioDate` + `markedDates`) est à la maille
  jour, sur les **deux écrans** (Heures et Heures Conducteurs, via `showValidationCalendar`).
  La **vue Semaine** utilise un `MalioDateWeek` (sélecteur de semaine, sans coloration).
  Le `PeriodStepperPicker` ne subsiste que comme fallback de la vue Jour quand
  `showValidationCalendar` est absent (aucun appelant actuel, conservé par flexibilité).
- Précédence d'affichage dans la grille (côté layer) : sélection (fond plein primary) >
  variante marquée ; le **jour courant** (`today`) garde sa bordure **et** reçoit le fond
  vert s'il est validé.

## Dépendance layer
Nécessite `@malio/layer-ui >= 1.7.x` : prop `markedDates`
(`Record<"YYYY-MM-DD", 'success' | 'danger'>`) + event `month-change` sur `MalioDate`
(ticket Malio UI MUI-45).
