Front : retrait des compétences, disponibilités en fenêtres horaires

Aligne le frontend sur le back-end (étape 14).

- Types : suppression de `skill_codes` / `required_skill_codes` /
  `AvailabilityStatus` / `MISSING_REQUIRED_SKILL` ; ajout de
  `AvailabilityRead` / `AvailabilitySet` et de la surface API
  `availability` (list / replace / listEdition) + hooks associés.
- Fiche bénévole : bloc « compétences » remplacé par un éditeur de
  disponibilités (plages date + heures libres, `PUT` en bloc).
- Assistant d'import : plus de colonnes compétences ni disponibilités
  (mapping réduit à l'identité) ; rapport simplifié.
- Grille des besoins : chaque candidat porte un badge de couverture
  (disponible / partiel / non déclaré) calculé sur ses fenêtres, et la
  liste est triée « disponibles d'abord ». Nouvel util
  `availabilityCoverage` / `uncoveredMinutes` dans `lib/planning`,
  portage du moteur back-end.
- Mock : `state.availability` passe à des fenêtres `AvailabilityRead[]`,
  validation `OUTSIDE_AVAILABILITY` recalculée sur l'union des fenêtres,
  jeu de démo et import CSV adaptés.

typecheck / lint / build / smoke au vert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-10 23:01:59 +02:00
co-authored by Claude Sonnet 5
parent be51770b51
commit 36d108b47d
11 changed files with 487 additions and 439 deletions
+16 -24
View File
@@ -62,15 +62,12 @@
"validation.SHIFT_OVERLAP": "Se chevauche avec un autre créneau",
"validation.SHIFT_OVERLAP.at": "Déjà affecté sur {position} au même moment",
"validation.SHIFT_OVERLAP.rule": "Deux créneaux d'un même bénévole se chevauchent.",
"validation.OUTSIDE_AVAILABILITY": "Hors disponibilités déclarées",
"validation.OUTSIDE_AVAILABILITY.unavailable": "Déclaré indisponible sur ce créneau",
"validation.OUTSIDE_AVAILABILITY.rule": "Une demi-journée du créneau est indisponible ou sans déclaration.",
"validation.OUTSIDE_AVAILABILITY": "Hors des disponibilités déclarées ({gap} non couvertes).",
"validation.OUTSIDE_AVAILABILITY.rule": "Le créneau n'est pas entièrement couvert par les disponibilités déclarées.",
"validation.DAILY_HOURS_EXCEEDED": "Plus de 8 h sur la journée ({hours})",
"validation.DAILY_HOURS_EXCEEDED.rule": "Plus de 8 h 00 d'affectations cumulées sur une même journée.",
"validation.INSUFFICIENT_REST": "Moins de 8 h de repos entre deux créneaux ({rest})",
"validation.INSUFFICIENT_REST.rule": "Moins de 8 h 00 entre la fin d'un créneau et le début du suivant.",
"validation.MISSING_REQUIRED_SKILL": "Compétence requise manquante : {skill}",
"validation.MISSING_REQUIRED_SKILL.rule": "Le bénévole n'a pas une compétence requise par le créneau.",
"grille.filter.day": "JOUR",
"grille.filter.position": "POSTE",
"grille.legend.critical": "SOUS-EFFECTIF CRITIQUE",
@@ -163,15 +160,11 @@
"config.vol.colName": "NOM",
"config.vol.colPhone": "TÉLÉPHONE",
"config.vol.colEmail": "EMAIL",
"config.vol.colSkills": "COMPÉTENCES",
"config.vol.ficheKicker": "FICHE BÉNÉVOLE",
"config.vol.firstName": "PRÉNOM",
"config.vol.lastName": "NOM",
"config.vol.phone": "TÉLÉPHONE",
"config.vol.email": "EMAIL (LIEN MAGIQUE)",
"config.vol.skills": "COMPÉTENCES",
"config.vol.noSkills": "Aucune compétence déclarée",
"config.vol.skillPlaceholder": "Code (ex. CAISSE)",
"config.vol.delete": "SUPPRIMER (RGPD — EFFACEMENT COMPLET)",
"config.vol.deleteConfirm": "Supprimer définitivement ce bénévole et toutes ses données (disponibilités, affectations) ?",
"config.vol.note": "Modifications enregistrées immédiatement via l'API. La suppression est un effacement complet en cascade (RGPD).",
@@ -231,7 +224,6 @@
"config.vol.field.email": "Email (lien magique)",
"config.vol.field.phone": "Téléphone",
"config.vol.field.comment": "Commentaire",
"config.vol.field.skills": "Compétences",
"config.vol.addTitle": "NOUVEAU BÉNÉVOLE",
"config.vol.addHint": "Le bénévole reçoit un accès par lien magique uniquement si un email est renseigné.",
"config.vol.create": "CRÉER LE BÉNÉVOLE",
@@ -248,12 +240,6 @@
"config.vol.wiz.mapTitle": "ASSOCIER LES COLONNES",
"config.vol.wiz.mapHint": "« Prénom » et « Nom » sont obligatoires. Laissez « — » pour ignorer une colonne.",
"config.vol.wiz.ignore": "—",
"config.vol.wiz.availTitle": "DISPONIBILITÉS (OPTIONNEL)",
"config.vol.wiz.availHint": "Associez une colonne du fichier à une date et une demi-journée pour importer les disponibilités déclarées.",
"config.vol.wiz.availAdd": "AJOUTER UNE COLONNE DE DISPONIBILITÉ",
"config.vol.wiz.availColumn": "Colonne",
"config.vol.wiz.availDate": "Date",
"config.vol.wiz.availPart": "Demi-journée",
"config.vol.wiz.mapMissing": "Associez au moins les colonnes « Prénom » et « Nom ».",
"config.vol.wiz.simulate": "SIMULER",
"config.vol.wiz.runImport": "IMPORTER POUR DE VRAI",
@@ -264,8 +250,6 @@
"config.vol.wiz.created": "{count} créé(s)",
"config.vol.wiz.updated": "{count} mis à jour",
"config.vol.wiz.skipped": "{count} ignoré(s)",
"config.vol.wiz.availWritten": "{count} disponibilité(s)",
"config.vol.wiz.skillsCreated": "Compétences créées : {list}",
"config.vol.wiz.rowsWithIssues": "LIGNES AVEC REMARQUES",
"config.vol.wiz.line": "Ligne {line}",
"config.vol.wiz.noIssues": "Aucune remarque : tout est prêt à être importé.",
@@ -278,17 +262,12 @@
"import.mapping.invalid": "Le paramétrage des colonnes est invalide.",
"import.mapping.required_field_missing": "Le champ « {field} » doit être associé à une colonne.",
"import.mapping.column_not_found": "La colonne « {column} » est absente du fichier.",
"import.mapping.unknown_day_part": "Demi-journée « {day_part_code} » inconnue (colonne « {column} »).",
"import.mapping.date_outside_edition": "La date {date} de la colonne « {column} » est hors des dates de l’édition.",
"import.mapping.duplicate_availability_column": "La colonne « {column} » est associée plusieurs fois.",
"import.row.missing_required_field": "Champ obligatoire manquant : {field}.",
"import.row.invalid_email": "Adresse email invalide : {value}.",
"import.row.duplicate_email_in_file": "Email déjà présent à la ligne {first_line} du fichier.",
"import.row.duplicate_name_in_file": "Nom et prénom déjà présents à la ligne {first_line} du fichier.",
"import.row.updated_existing": "Bénévole existant mis à jour.",
"import.row.possible_duplicate_name": "Un bénévole portant le même nom existe déjà dans l’édition.",
"import.row.skill_created": "Nouvelle compétence créée : {label}.",
"import.row.unknown_availability_value": "Valeur de disponibilité non comprise « {value} » (colonne « {column} »).",
"config.tab.artists": "PROGRAMMATION",
"config.artists.title": "PROGRAMMATION",
"config.artists.hint": "La liste des artistes de l'édition, jour par jour. Les bénévoles y choisissent ceux qu'ils veulent voir depuis leur fiche.",
@@ -301,5 +280,18 @@
"config.vol.noArtists": "Aucun artiste choisi",
"config.vol.noPositions": "Aucun poste choisi",
"config.vol.pickArtist": "Ajouter un artiste…",
"config.vol.pickPosition": "Ajouter un poste…"
"config.vol.pickPosition": "Ajouter un poste…",
"config.vol.colWishes": "VŒUX",
"config.vol.wishesCount": "{count} vœu(x)",
"config.vol.addAvailabilityHint": "Les disponibilités se renseignent sur la fiche du bénévole, une fois celui-ci créé.",
"config.vol.avail.title": "Disponibilités",
"config.vol.avail.hint": "Plages horaires où le bénévole peut venir. Une heure de fin ≤ début signifie que la plage se termine le lendemain.",
"config.vol.avail.none": "Aucune disponibilité déclarée",
"config.vol.avail.add": "Ajouter une plage",
"config.vol.avail.save": "Enregistrer les disponibilités",
"grille.panel.coverage.full": "Disponible",
"grille.panel.coverage.partial": "Partiellement dispo",
"grille.panel.coverage.none": "Dispo non déclarée",
"availability.date_outside_edition": "Cette date est hors des dates de l'édition.",
"availability.empty_window": "Une plage doit avoir un début et une fin différents."
}