Files
siop2/apps/mobile/src/api/exploitation.ts
pr-daaif a22ea60f83 feat(r4.2): terrain mobile — scan QR, fiches ascenseur/OT, clôture, grille
Scanner expo-camera (QR seulement) : analyseScan testée — URL portail
…/q/REF des étiquettes A6 ou référence tapée, QR étrangers refusés ;
résolution D4 dans le parc en cache d'abord (sous-sol inclus), message
honnête sinon. Fiche ascenseur en consultation (D3, historique scopé
« voir autre »). Fiche OT : un bouton principal selon la machine à
états, coûts figés, garde alimentée par closureBlockers (source unique
API). Clôture terrain : bilan codé 6 champs au pouce (sélecteur plein
écran), garde visible, clôture en ligne. Préventif : mes grilles →
checklist cochable, appui long = N/A, aria-checked (leçon RN web :
accessibilityState.checked ne produit pas aria-checked).

Écritures en ligne assumées en R4.2 (bandeaux hors-ligne explicites) —
la file persistée et le verrou optimiste sont R4.3 (D1/D2).

Vérifié 12/12 en Expo web piloté : scan → fiche → OT-0341 (505 MAD,
garde), coche/décoche restituée, OT de test Démarrer → bilan → Terminé,
zéro erreur console, données purgées. 12 tests jest-expo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:47:04 +01:00

92 lines
2.7 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import type { ChecklistState, ReportUpsert, WorkOrderStatus } from '@siop/shared';
import { api, unwrap } from './client';
/** Hooks R4.2 — mêmes opérations que le web (contrat unique). Les écritures
* restent EN LIGNE dans cette release ; la mise en file arrive en R4.3 (D1). */
export function useWorkOrders() {
return useQuery({
queryKey: ['work-orders'],
queryFn: async () => (await unwrap(await api.GET('/work-orders'))).workOrders,
});
}
export function useWorkOrder(id: string) {
return useQuery({
queryKey: ['work-orders', id],
queryFn: async () =>
unwrap(await api.GET('/work-orders/{id}', { params: { path: { id } } })),
});
}
export function useAssets() {
return useQuery({
queryKey: ['assets'],
queryFn: async () => (await unwrap(await api.GET('/assets'))).assets,
});
}
export function useAsset(id: string) {
return useQuery({
queryKey: ['assets', id],
queryFn: async () => unwrap(await api.GET('/assets/{id}', { params: { path: { id } } })),
});
}
export function useReferenceValues() {
return useQuery({
queryKey: ['reference-values'],
staleTime: 3600_000, // référentiels administrables : stables en journée
queryFn: async () => (await unwrap(await api.GET('/reference-values'))).referenceValues,
});
}
function useInvalideOT(id: string) {
const queryClient = useQueryClient();
return () =>
Promise.all([
queryClient.invalidateQueries({ queryKey: ['work-orders', id] }),
queryClient.invalidateQueries({ queryKey: ['work-orders'] }),
]);
}
export function useTransition(id: string) {
const invalide = useInvalideOT(id);
return useMutation({
mutationFn: async (input: { to: WorkOrderStatus; comment?: string }) =>
unwrap(
await api.POST('/work-orders/{id}/transition', {
params: { path: { id } },
body: input,
}),
),
onSuccess: invalide,
});
}
export function useCocheChecklist(otId: string) {
const invalide = useInvalideOT(otId);
return useMutation({
mutationFn: async (input: { itemId: string; state: ChecklistState }) =>
unwrap(
await api.PATCH('/work-orders/{id}/checklist/{itemId}', {
params: { path: { id: otId, itemId: input.itemId } },
body: { state: input.state },
}),
),
onSuccess: invalide,
});
}
export function useBilan(otId: string) {
const invalide = useInvalideOT(otId);
return useMutation({
mutationFn: async (body: ReportUpsert) =>
unwrap(
await api.PUT('/work-orders/{id}/report', { params: { path: { id: otId } }, body }),
),
onSuccess: invalide,
});
}