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:
co-authored by
Claude Sonnet 5
parent
ac469f0cd1
commit
c9dda65361
@@ -0,0 +1,213 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
type UseQueryOptions,
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { EDITION_ID, api } from './index';
|
||||
import type {
|
||||
AssignmentCreate,
|
||||
AssignmentUpdate,
|
||||
AssignmentValidateResult,
|
||||
PositionCreate,
|
||||
PositionUpdate,
|
||||
ShiftCreate,
|
||||
ShiftUpdate,
|
||||
VolunteerCreate,
|
||||
VolunteerUpdate,
|
||||
} from './schema';
|
||||
|
||||
const edition = EDITION_ID;
|
||||
|
||||
export const queryKeys = {
|
||||
volunteers: ['volunteers', edition] as const,
|
||||
positions: ['positions', edition] as const,
|
||||
shifts: (positionId?: string) => ['shifts', edition, positionId ?? null] as const,
|
||||
assignments: (params?: { volunteerId?: string; shiftId?: string }) =>
|
||||
['assignments', edition, params?.volunteerId ?? null, params?.shiftId ?? null] as const,
|
||||
matrix: ['matrix', edition] as const,
|
||||
validate: (volunteerId: string, shiftId: string) =>
|
||||
['validate', edition, volunteerId, shiftId] as const,
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
// Lectures //
|
||||
// --------------------------------------------------------------------------- //
|
||||
export function useVolunteers() {
|
||||
return useQuery({ queryKey: queryKeys.volunteers, queryFn: () => api.volunteers.list(edition) });
|
||||
}
|
||||
|
||||
export function usePositions() {
|
||||
return useQuery({ queryKey: queryKeys.positions, queryFn: () => api.positions.list(edition) });
|
||||
}
|
||||
|
||||
export function useShifts(positionId?: string) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.shifts(positionId),
|
||||
queryFn: () => api.shifts.list(edition, positionId ? { positionId } : undefined),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAssignments(params?: { volunteerId?: string; shiftId?: string }) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.assignments(params),
|
||||
queryFn: () => api.assignments.list(edition, params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMatrix() {
|
||||
return useQuery({ queryKey: queryKeys.matrix, queryFn: () => api.matrix.get(edition) });
|
||||
}
|
||||
|
||||
export function useValidateAssignment(
|
||||
volunteerId: string,
|
||||
shiftId: string,
|
||||
options?: Partial<UseQueryOptions<AssignmentValidateResult>>,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.validate(volunteerId, shiftId),
|
||||
queryFn: () => api.assignments.validate(edition, { volunteer_id: volunteerId, shift_id: shiftId }),
|
||||
staleTime: 60_000,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
// Écritures //
|
||||
// --------------------------------------------------------------------------- //
|
||||
function useInvalidate() {
|
||||
const client = useQueryClient();
|
||||
return {
|
||||
volunteers: () => client.invalidateQueries({ queryKey: ['volunteers', edition] }),
|
||||
positions: () => client.invalidateQueries({ queryKey: ['positions', edition] }),
|
||||
shifts: () => client.invalidateQueries({ queryKey: ['shifts', edition] }),
|
||||
planning: () => {
|
||||
client.invalidateQueries({ queryKey: ['assignments', edition] });
|
||||
client.invalidateQueries({ queryKey: ['matrix', edition] });
|
||||
client.invalidateQueries({ queryKey: ['validate', edition] });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateVolunteer() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: VolunteerCreate) => api.volunteers.create(edition, body),
|
||||
onSuccess: invalidate.volunteers,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateVolunteer() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: VolunteerUpdate }) =>
|
||||
api.volunteers.update(edition, id, body),
|
||||
onSuccess: invalidate.volunteers,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteVolunteer() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.volunteers.remove(edition, id),
|
||||
onSuccess: () => {
|
||||
invalidate.volunteers();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreatePosition() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: PositionCreate) => api.positions.create(edition, body),
|
||||
onSuccess: () => {
|
||||
invalidate.positions();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdatePosition() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: PositionUpdate }) =>
|
||||
api.positions.update(edition, id, body),
|
||||
onSuccess: () => {
|
||||
invalidate.positions();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePosition() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.positions.remove(edition, id),
|
||||
onSuccess: () => {
|
||||
invalidate.positions();
|
||||
invalidate.shifts();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateShift() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: ShiftCreate) => api.shifts.create(edition, body),
|
||||
onSuccess: () => {
|
||||
invalidate.shifts();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateShift() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: ShiftUpdate }) =>
|
||||
api.shifts.update(edition, id, body),
|
||||
onSuccess: () => {
|
||||
invalidate.shifts();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteShift() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.shifts.remove(edition, id),
|
||||
onSuccess: () => {
|
||||
invalidate.shifts();
|
||||
invalidate.planning();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAssignment() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: AssignmentCreate) => api.assignments.create(edition, body),
|
||||
onSuccess: invalidate.planning,
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateAssignment() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: AssignmentUpdate }) =>
|
||||
api.assignments.update(edition, id, body),
|
||||
onSuccess: invalidate.planning,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteAssignment() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => api.assignments.remove(edition, id),
|
||||
onSuccess: invalidate.planning,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user