Front : programmation des artistes et vœux des bénévoles

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

- Types : retrait de `tshirt_size` / `diet` (Volunteer et mapping
  d'import) ; ajout de `ArtistRead/Create/Update` et des champs
  `artist_ids` / `wished_position_ids` sur la fiche bénévole.
- Surface API `artists` (HTTP + mock) et hooks
  `useArtists` / `useCreateArtist` / `useUpdateArtist` / `useDeleteArtist`.
- Nouvel onglet Configuration « Programmation » : liste d'artistes
  éditable, colonne par jour de l'édition (création, renommage,
  suppression ; 409 doublon, 422 nom vide).
- Fiche bénévole & formulaire d'ajout manuel : champs t-shirt / régime
  retirés, sélecteurs « Artistes à voir » et « Postes souhaités »
  (puces retirables, 422 si id étranger).
- Mock : jeu de démo enrichi (2 artistes par jour, vœux aléatoires),
  cascade sur suppression d'artiste / de poste, import CSV sans
  t-shirt / régime.

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 22:22:45 +02:00
co-authored by Claude Sonnet 5
parent 24caece7df
commit 81609eecc5
10 changed files with 533 additions and 99 deletions
+32 -2
View File
@@ -2,6 +2,7 @@ import { hoursBetween } from '@/lib/time';
import type {
AccountRead,
ArtistRead,
AssignmentRead,
DayPartRead,
EditionRead,
@@ -27,6 +28,7 @@ export const DEMO_COORDINATOR_PASSWORD = 'demo';
export interface MockState {
edition: EditionRead;
dayParts: DayPartRead[];
artists: ArtistRead[];
volunteers: VolunteerRead[];
positions: PositionRead[];
shifts: ShiftRead[];
@@ -87,6 +89,16 @@ const LAST_NAMES = [
'Renard', 'Dupuy',
];
const SKILL_POOL = ['CAISSE', 'CARISTE', 'REGIE', 'SECOURISME'];
const ARTIST_POOL = [
'La Battante',
'Orchestre Miroir',
'Kntongo',
'Louve',
'Les Frères Vent',
'Nuage Bleu',
'Sœur Machine',
'Comète',
];
const NOW = '2026-10-10T09:00:00';
const NEVER_ASSIGNED = new Set([7, 21, 34, 48, 55]);
@@ -168,6 +180,18 @@ export function buildMockState(): MockState {
}
}
// Programmation : 2 artistes par jour de l'édition.
const artists: ArtistRead[] = DAY_DATES.flatMap((dateStr, d) =>
[0, 1].map((k) => ({
id: `art${d * 2 + k}`,
edition_id,
name: ARTIST_POOL[(d * 2 + k) % ARTIST_POOL.length],
performance_date: dateStr,
created_at: NOW,
updated_at: NOW,
})),
);
const volunteers: VolunteerRead[] = [];
const availability = new Map<string, Set<string>>();
for (let i = 0; i < 64; i += 1) {
@@ -186,6 +210,11 @@ export function buildMockState(): MockState {
availability.set(id, avail);
const pair = () => `0${Math.floor(rnd() * 90 + 10)}`.slice(-2);
const skill_codes = rnd() < 0.3 ? [SKILL_POOL[Math.floor(rnd() * SKILL_POOL.length)]] : [];
const artist_ids = artists
.filter(() => rnd() < 0.25)
.map((a) => a.id);
const wished_position_ids =
rnd() < 0.4 ? [positions[Math.floor(rnd() * positions.length)].id] : [];
volunteers.push({
id,
edition_id,
@@ -193,10 +222,10 @@ export function buildMockState(): MockState {
last_name,
email: rnd() < 0.85 ? demoEmail(first_name, last_name) : null,
phone: `06 ${pair()} ${pair()} ${pair()} ${pair()}`,
tshirt_size: (['S', 'M', 'L', 'XL'] as const)[Math.floor(rnd() * 4)],
diet: rnd() < 0.15 ? 'végétarien' : null,
comment: null,
skill_codes,
artist_ids,
wished_position_ids,
created_at: NOW,
updated_at: NOW,
});
@@ -361,6 +390,7 @@ export function buildMockState(): MockState {
return {
edition,
dayParts,
artists,
volunteers,
positions,
shifts,