Back-end :
- GET /editions/{id} (tout compte de l'édition) et PATCH /editions/{id}
(COORDINATION) : name, year, start_date, end_date, retention_days.
Refus si end_date < start_date (edition.dates_inconsistent). Tests.
Front-end :
- api : EditionRead/Update, editions.get/update (http + mock), hooks
useEdition / useUpdateEdition.
- ConfigGrid : le bouton « ajouter un poste » sélectionne le poste créé et
affiche l'état (pending) + les erreurs (ErrorNote) — avant, un échec ou
même un succès passaient inaperçus. Renommage du poste + choix de la
catégorie + suppression dans l'en-tête. Une colonne par jour de l'édition
(même vide) → on peut ajouter un créneau n'importe quel jour.
- ConfigRules : formulaire d'édition (nom + dates) → c'est là qu'on ajoute
ou retire des jours ; compteur de jours ; retour d'enregistrement.
- i18n mis à jour.
144 tests back OK ; typecheck + lint + build + smoke front OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
253 lines
7.3 KiB
TypeScript
253 lines
7.3 KiB
TypeScript
import {
|
|
useMutation,
|
|
useQuery,
|
|
useQueryClient,
|
|
type UseQueryOptions,
|
|
} from '@tanstack/react-query';
|
|
|
|
import { EDITION_ID, api } from './index';
|
|
import type {
|
|
AssignmentCreate,
|
|
AssignmentUpdate,
|
|
AssignmentValidateResult,
|
|
EditionUpdate,
|
|
PlanningNotificationRequest,
|
|
PositionCreate,
|
|
PositionUpdate,
|
|
ShiftCreate,
|
|
ShiftUpdate,
|
|
VolunteerCreate,
|
|
VolunteerUpdate,
|
|
} from './schema';
|
|
|
|
const edition = EDITION_ID;
|
|
|
|
export const queryKeys = {
|
|
edition: ['edition', edition] as const,
|
|
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 useEdition() {
|
|
return useQuery({ queryKey: queryKeys.edition, queryFn: () => api.editions.get(edition) });
|
|
}
|
|
|
|
export function useVolunteers(options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: queryKeys.volunteers,
|
|
queryFn: () => api.volunteers.list(edition),
|
|
enabled: options?.enabled ?? true,
|
|
});
|
|
}
|
|
|
|
export function usePositions() {
|
|
return useQuery({ queryKey: queryKeys.positions, queryFn: () => api.positions.list(edition) });
|
|
}
|
|
|
|
/** Fiche d'un bénévole (un bénévole connecté peut lire la sienne). */
|
|
export function useVolunteer(id: string | null | undefined) {
|
|
return useQuery({
|
|
queryKey: ['volunteer', edition, id ?? null],
|
|
queryFn: () => api.volunteers.get(edition, id as string),
|
|
enabled: Boolean(id),
|
|
});
|
|
}
|
|
|
|
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 },
|
|
options?: { enabled?: boolean },
|
|
) {
|
|
return useQuery({
|
|
queryKey: queryKeys.assignments(params),
|
|
queryFn: () => api.assignments.list(edition, params),
|
|
enabled: options?.enabled ?? true,
|
|
});
|
|
}
|
|
|
|
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 useUpdateEdition() {
|
|
const client = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (body: EditionUpdate) => api.editions.update(edition, body),
|
|
onSuccess: () => client.invalidateQueries({ queryKey: ['edition', 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,
|
|
});
|
|
}
|
|
|
|
export function useSendPlanningNotification() {
|
|
return useMutation({
|
|
mutationFn: (body: PlanningNotificationRequest) =>
|
|
api.notifications.sendPlanning(edition, body),
|
|
});
|
|
}
|