Frontend : consommation de l'API CRUD (matrice, affectations, validation)

Remplace le magasin d'état fabriqué par une vraie couche API typée, alignée
sur le back-end (Étape 5) :

- src/api/ : schema.ts (miroir de backend/app/api/schemas.py), client HTTP,
  implémentation mock en mémoire (même surface + portage du moteur de
  validation), sélection mock/http via VITE_API_MODE, hooks React Query
  (lectures + mutations avec invalidation).
- Grille des besoins ← GET /matrix ; treillis de colonnes dérivé des
  créneaux réels (plus de plages figées).
- Panneau d'affectation ← GET /assignments?shift_id, POST /assignments/validate
  par candidat (alertes bloquantes / avertissements du moteur),
  POST / PATCH / DELETE /assignments.
- Vue par bénévole ← volunteers + shifts + assignments (surcharge / oubliés
  calculés côté client).
- Confirmations (ex-« Publication ») ← affectations PROPOSED ; publication
  versionnée et emails signalés hors périmètre.
- Mobiles ← assignments filtrées ; confirmation = PATCH ; pointage référent
  local (hors périmètre).
- Configuration ← positions & shifts CRUD (min/idéal, ajout/retrait),
  volunteers CRUD dont suppression RGPD ; messages = maquette locale.
- i18n mis à jour, aucun texte d'interface en dur.
- Vérifs : build tsc + vite, eslint (0 problème), npm run smoke (API mock +
  rendu headless des 6 écrans avec données préchargées).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
This commit is contained in:
AdamThomas
2026-09-09 23:16:38 +02:00
co-authored by Claude Sonnet 5
parent ac469f0cd1
commit c9dda65361
38 changed files with 2930 additions and 2414 deletions
+29
View File
@@ -0,0 +1,29 @@
import { ApiError } from '@/api';
import { t } from '@/i18n';
export function Loading() {
return (
<div className="flex items-center gap-3 p-6 font-mono text-[11px] uppercase tracking-widest opacity-60">
<span className="h-4 w-4 animate-spin border-2 border-ink border-t-transparent" />
{t('app.loading')}
</div>
);
}
export function ErrorNote({ error }: { error: unknown }) {
const code =
error instanceof ApiError
? error.code
: error instanceof Error
? error.message
: 'error.generic';
const known = code.startsWith('error.') || code.includes('.');
return (
<div className="m-4 max-w-[640px] border-[3px] border-ink bg-orange p-4 font-display text-[13px] font-bold shadow-brutal-md">
{t('app.error.generic')}
<span className="mt-1 block font-mono text-[10px] font-normal opacity-80">
{known ? t(code) : code}
</span>
</div>
);
}