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
+29 -34
View File
@@ -15,7 +15,6 @@ export type PositionCategory =
// Depuis l'Étape 9 back-end : une affectation est valide dès sa création,
// il n'y a plus de statut (proposée / confirmée…). La retirer = supprimer la ligne.
export type AssignmentSource = 'SOLVER' | 'MANUAL';
export type AvailabilityStatus = 'AVAILABLE' | 'IF_NEEDED' | 'UNAVAILABLE';
export type Role = 'COORDINATOR' | 'LEAD' | 'VOLUNTEER';
export type AuthMethod = 'password' | 'magic_link';
@@ -23,8 +22,7 @@ export type ViolationCode =
| 'SHIFT_OVERLAP'
| 'OUTSIDE_AVAILABILITY'
| 'DAILY_HOURS_EXCEEDED'
| 'INSUFFICIENT_REST'
| 'MISSING_REQUIRED_SKILL';
| 'INSUFFICIENT_REST';
export type ViolationSeverity = 'BLOCKING' | 'WARNING';
@@ -104,7 +102,6 @@ export interface VolunteerRead {
email: string | null;
phone: string | null;
comment: string | null;
skill_codes: string[];
/** Vœux : artistes que le bénévole veut voir. */
artist_ids: string[];
/** Vœux : postes que le bénévole voudrait tenir. */
@@ -119,7 +116,6 @@ export interface VolunteerCreate {
email?: string | null;
phone?: string | null;
comment?: string | null;
skill_codes?: string[];
artist_ids?: string[];
wished_position_ids?: string[];
}
@@ -130,12 +126,37 @@ export interface VolunteerUpdate {
email?: string | null;
phone?: string | null;
comment?: string | null;
/** null / absent = inchangé ; [] = vider. Idem artist_ids / wished_position_ids. */
skill_codes?: string[] | null;
/** null / absent = inchangé ; [] = vider. */
artist_ids?: string[] | null;
wished_position_ids?: string[] | null;
}
// --------------------------------------------------------------------------- //
// Availability (disponibilités déclarées par le bénévole) //
// --------------------------------------------------------------------------- //
export interface AvailabilityWindowInput {
/** Date ISO (YYYY-MM-DD). */
date: string;
/** Heure « HH:MM » ou « HH:MM:SS ». end <= start ⇒ se termine le lendemain. */
start_time: string;
end_time: string;
}
export interface AvailabilitySet {
windows: AvailabilityWindowInput[];
}
export interface AvailabilityRead {
id: string;
edition_id: string;
volunteer_id: string;
date: string;
start_time: string;
end_time: string;
created_at: string;
updated_at: string;
}
// --------------------------------------------------------------------------- //
// Artist (programmation) //
// --------------------------------------------------------------------------- //
@@ -167,8 +188,7 @@ export type ImportFieldKey =
| 'last_name'
| 'email'
| 'phone'
| 'comment'
| 'skills';
| 'comment';
export interface ImportColumnMapping {
first_name: string;
@@ -176,20 +196,11 @@ export interface ImportColumnMapping {
email?: string | null;
phone?: string | null;
comment?: string | null;
skills?: string | null;
}
export interface ImportAvailabilityColumn {
header: string;
/** Date ISO (YYYY-MM-DD). */
date: string;
day_part_code: string;
}
export interface ImportMapping {
dry_run: boolean;
columns: ImportColumnMapping;
availability_columns: ImportAvailabilityColumn[];
}
export interface ImportPreviewRow {
@@ -197,14 +208,6 @@ export interface ImportPreviewRow {
values: Record<string, string>;
}
export interface ImportDayPartInfo {
code: string;
label: string;
start_time: string;
end_time: string;
order: number;
}
export interface ImportAnalyzeResponse {
filename: string;
format: 'csv' | 'xlsx';
@@ -212,9 +215,6 @@ export interface ImportAnalyzeResponse {
row_count: number;
preview: ImportPreviewRow[];
suggested_mapping: Partial<Record<ImportFieldKey, string>>;
/** Dates ISO couvertes par l'édition. */
edition_dates: string[];
day_parts: ImportDayPartInfo[];
}
export type ImportSeverity = 'ERROR' | 'WARNING';
@@ -240,8 +240,6 @@ export interface ImportReport {
created: number;
updated: number;
skipped: number;
availabilities_written: number;
skills_created: string[];
rows: ImportRowReport[];
}
@@ -287,7 +285,6 @@ export interface ShiftRead {
ends_at: string;
min_staff: number;
ideal_staff: number;
required_skill_codes: string[];
created_at: string;
updated_at: string;
}
@@ -298,7 +295,6 @@ export interface ShiftCreate {
ends_at: string;
min_staff?: number;
ideal_staff?: number;
required_skill_codes?: string[];
}
export interface ShiftUpdate {
@@ -307,7 +303,6 @@ export interface ShiftUpdate {
ends_at?: string;
min_staff?: number;
ideal_staff?: number;
required_skill_codes?: string[] | null;
}
// --------------------------------------------------------------------------- //