mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
- liste OT (filtres, strie rouge, « immédiat ») ; fiche OT : transitions pilotées par allowedTransitions, clôture grisée avec la garde expliquée, bilan codé (6 selects sur référentiels), checklist Fait→N-A→à faire, activité + commentaires, assignation, annulation motivée - nouvel OT : interrupteur « personne bloquée » qui force la priorité - demandes : table + panneau d'approbation (priorité, assignation, approuver → fiche OT), rejet en modale à motif obligatoire, signalement interne ; statut « Résolue » dérivé de l'OT lié - préventif : tuiles réelles, générer + résumé (« regénérer ne double rien »), gabarits administrables ; compteurs : saisie + historique - tableau de bord réel : bandeau urgence cliquable, KPIs, OT par statut, interventions récentes ; accueil dédié aux rôles sans exploitation - urgence traversante : chip topbar pulsante (60 s), badges de nav - GET /assets/options (auth seule) : le Demandeur peut désigner l'appareil qu'il signale — trou débusqué par l'e2e (49 opérations au contrat) - génération préventive durcie : collision de référence RETENTÉE (plus de saut silencieux), P2003 toléré ; 3 runs Jest complets consécutifs verts - 9 tests Playwright (recette R2 officielle rejouée intégralement), 50 tests API (94,6 % / 78,9 %) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
236 lines
6.7 KiB
TypeScript
236 lines
6.7 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import type {
|
|
AssigneesUpdate,
|
|
ChecklistPatch,
|
|
MeterReadingCreate,
|
|
PreventiveGenerate,
|
|
ReportUpsert,
|
|
RequestApprove,
|
|
RequestCreate,
|
|
RequestReject,
|
|
TaskTemplateCreate,
|
|
TaskTemplateUpdate,
|
|
TransitionRequest,
|
|
WorkOrderCreate,
|
|
} from '@siop/shared';
|
|
import { api } from './client';
|
|
|
|
/** Hooks R2 — client typé, invalidations ciblées. */
|
|
|
|
async function unwrap<T>(res: { data?: T; error?: unknown; response: Response }): Promise<T> {
|
|
if (res.error || res.data === undefined) {
|
|
const message =
|
|
(res.error as { message?: string } | undefined)?.message ??
|
|
`Le serveur a répondu ${res.response.status}`;
|
|
throw new Error(Array.isArray(message) ? message.join(' — ') : message);
|
|
}
|
|
return res.data;
|
|
}
|
|
|
|
function useInvalidate(...keys: string[]) {
|
|
const queryClient = useQueryClient();
|
|
return () =>
|
|
Promise.all(keys.map((key) => queryClient.invalidateQueries({ queryKey: [key] })));
|
|
}
|
|
|
|
// ————— Ordres de travail —————
|
|
|
|
export function useWorkOrders(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['work-orders'],
|
|
enabled,
|
|
queryFn: async () => (await unwrap(await api.GET('/work-orders'))).workOrders,
|
|
});
|
|
}
|
|
|
|
export function useWorkOrder(id: string | undefined) {
|
|
return useQuery({
|
|
queryKey: ['work-orders', id],
|
|
enabled: !!id,
|
|
queryFn: async () =>
|
|
unwrap(await api.GET('/work-orders/{id}', { params: { path: { id: id! } } })),
|
|
});
|
|
}
|
|
|
|
export function useCreateWorkOrder() {
|
|
const invalidate = useInvalidate('work-orders');
|
|
return useMutation({
|
|
mutationFn: async (body: WorkOrderCreate) =>
|
|
unwrap(await api.POST('/work-orders', { body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useTransitionWorkOrder(id: string) {
|
|
const invalidate = useInvalidate('work-orders', 'requests', 'preventive-status');
|
|
return useMutation({
|
|
mutationFn: async (body: TransitionRequest) =>
|
|
unwrap(await api.POST('/work-orders/{id}/transition', { params: { path: { id } }, body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useCommentWorkOrder(id: string) {
|
|
const invalidate = useInvalidate('work-orders');
|
|
return useMutation({
|
|
mutationFn: async (message: string) =>
|
|
unwrap(
|
|
await api.POST('/work-orders/{id}/comments', {
|
|
params: { path: { id } },
|
|
body: { message },
|
|
}),
|
|
),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useSetAssignees(id: string) {
|
|
const invalidate = useInvalidate('work-orders');
|
|
return useMutation({
|
|
mutationFn: async (body: AssigneesUpdate) =>
|
|
unwrap(await api.PUT('/work-orders/{id}/assignees', { params: { path: { id } }, body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useUpsertReport(id: string) {
|
|
const invalidate = useInvalidate('work-orders');
|
|
return useMutation({
|
|
mutationFn: async (body: ReportUpsert) =>
|
|
unwrap(await api.PUT('/work-orders/{id}/report', { params: { path: { id } }, body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function usePatchChecklist(id: string) {
|
|
const invalidate = useInvalidate('work-orders');
|
|
return useMutation({
|
|
mutationFn: async ({ itemId, ...body }: ChecklistPatch & { itemId: string }) =>
|
|
unwrap(
|
|
await api.PATCH('/work-orders/{id}/checklist/{itemId}', {
|
|
params: { path: { id, itemId } },
|
|
body,
|
|
}),
|
|
),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
// ————— Demandes —————
|
|
|
|
export function useRequests() {
|
|
return useQuery({
|
|
queryKey: ['requests'],
|
|
queryFn: async () => (await unwrap(await api.GET('/requests'))).requests,
|
|
});
|
|
}
|
|
|
|
export function useCreateRequest() {
|
|
const invalidate = useInvalidate('requests');
|
|
return useMutation({
|
|
mutationFn: async (body: RequestCreate) => unwrap(await api.POST('/requests', { body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useApproveRequest() {
|
|
const invalidate = useInvalidate('requests', 'work-orders');
|
|
return useMutation({
|
|
mutationFn: async ({ id, ...body }: RequestApprove & { id: string }) =>
|
|
unwrap(await api.POST('/requests/{id}/approve', { params: { path: { id } }, body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useRejectRequest() {
|
|
const invalidate = useInvalidate('requests');
|
|
return useMutation({
|
|
mutationFn: async ({ id, ...body }: RequestReject & { id: string }) =>
|
|
unwrap(await api.POST('/requests/{id}/reject', { params: { path: { id } }, body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
// ————— Référentiels du bilan —————
|
|
|
|
export function useReferenceValues() {
|
|
return useQuery({
|
|
queryKey: ['reference-values'],
|
|
staleTime: 5 * 60_000,
|
|
queryFn: async () =>
|
|
(await unwrap(await api.GET('/reference-values'))).referenceValues,
|
|
});
|
|
}
|
|
|
|
// ————— Préventif —————
|
|
|
|
export function useTaskTemplates() {
|
|
return useQuery({
|
|
queryKey: ['task-templates'],
|
|
queryFn: async () => (await unwrap(await api.GET('/preventive/templates'))).templates,
|
|
});
|
|
}
|
|
|
|
export function useCreateTaskTemplate() {
|
|
const invalidate = useInvalidate('task-templates');
|
|
return useMutation({
|
|
mutationFn: async (body: TaskTemplateCreate) =>
|
|
unwrap(await api.POST('/preventive/templates', { body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function useUpdateTaskTemplate() {
|
|
const invalidate = useInvalidate('task-templates');
|
|
return useMutation({
|
|
mutationFn: async ({ id, ...body }: TaskTemplateUpdate & { id: string }) =>
|
|
unwrap(
|
|
await api.PATCH('/preventive/templates/{id}', { params: { path: { id } }, body }),
|
|
),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
export function usePreventiveStatus(enabled = true) {
|
|
return useQuery({
|
|
queryKey: ['preventive-status'],
|
|
enabled,
|
|
queryFn: async () => unwrap(await api.GET('/preventive/status')),
|
|
});
|
|
}
|
|
|
|
export function useGeneratePreventive() {
|
|
const invalidate = useInvalidate('preventive-status', 'work-orders');
|
|
return useMutation({
|
|
mutationFn: async (body: PreventiveGenerate) =>
|
|
unwrap(await api.POST('/preventive/generate', { body })),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|
|
|
|
// ————— Compteurs —————
|
|
|
|
export function useMeters(assetId: string | undefined) {
|
|
return useQuery({
|
|
queryKey: ['meters', assetId],
|
|
enabled: !!assetId,
|
|
queryFn: async () =>
|
|
(await unwrap(await api.GET('/assets/{id}/meters', { params: { path: { id: assetId! } } })))
|
|
.meters,
|
|
});
|
|
}
|
|
|
|
export function useAddMeterReading(assetId: string) {
|
|
const invalidate = useInvalidate('meters');
|
|
return useMutation({
|
|
mutationFn: async (body: MeterReadingCreate) =>
|
|
unwrap(
|
|
await api.POST('/assets/{id}/meter-readings', {
|
|
params: { path: { id: assetId } },
|
|
body,
|
|
}),
|
|
),
|
|
onSuccess: invalidate,
|
|
});
|
|
}
|