import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ClientResponseError } from 'pocketbase'; import { getCurrentUserId, pb } from '@/services/pocketbase'; import type { AnalyseFinanciereRecord, BienCreate, BienRecord, BienUpdate, ContactRecord, DocumentRecord, EtapePipelineRecord, NoteRecord, VisiteRecord, } from '@/types/collections'; export type BiensFilters = { search?: string; }; export type BienExpanded = BienRecord & { expand?: { etape?: EtapePipelineRecord; source_contact?: ContactRecord; }; }; export type BienDetailBundle = { bien: BienExpanded; visites: VisiteRecord[]; notes: NoteRecord[]; documents: DocumentRecord[]; analyse: AnalyseFinanciereRecord | null; }; async function fetchPrixMapForUser(uid: string): Promise> { const analyses = await pb.collection('analyses_financieres').getFullList({ filter: `user="${uid}"`, }); const map = new Map(); for (const a of analyses) { if (a.prix_achat != null && a.bien) { map.set(a.bien, a.prix_achat); } } return map; } export async function fetchBienDetail(bienId: string): Promise { const uid = getCurrentUserId(); if (!uid) throw new Error('Utilisateur non connecté'); let bien: BienExpanded; try { bien = await pb.collection('biens').getOne(bienId, { expand: 'etape,source_contact', }); } catch (e) { if (e instanceof ClientResponseError && (e.status === 404 || e.status === 400)) { throw new Error( "Ce bien n'existe pas ou a été supprimé (vérifie l'admin PocketBase). Retourne à la liste des biens.", ); } throw e; } if (bien.user !== uid) { throw new Error('Accès refusé'); } const [visites, notes, documents, analyses] = await Promise.all([ pb.collection('visites').getFullList({ filter: `bien="${bienId}"`, sort: '-date_visite', }), pb.collection('notes_biens').getFullList({ filter: `bien="${bienId}"`, sort: '-id', }), pb.collection('documents_biens').getFullList({ filter: `bien="${bienId}"`, sort: '-id', }), pb.collection('analyses_financieres').getList(1, 1, { filter: `bien="${bienId}" && user="${uid}"`, sort: '-id', }), ]); const analyse = analyses.items[0] ?? null; const byUpdatedDesc = (a: { updated?: string }, b: { updated?: string }) => (b.updated ?? '').localeCompare(a.updated ?? ''); return { bien, visites, notes: [...notes].sort(byUpdatedDesc), documents: [...documents].sort(byUpdatedDesc), analyse, }; } export function useBiens(filters?: BiensFilters) { const uid = getCurrentUserId(); const queryClient = useQueryClient(); const search = filters?.search?.trim() ?? ''; const query = useQuery({ queryKey: ['biens', uid, search], queryFn: async () => { if (!uid) return { biens: [] as BienExpanded[], prixByBien: new Map() }; const parts = [`user="${uid}"`]; if (search.length > 0) { const esc = search.replace(/"/g, '\\"'); parts.push(`(titre ~ "${esc}" || ville ~ "${esc}" || adresse ~ "${esc}" || code_postal ~ "${esc}")`); } const filter = parts.join(' && '); const [biens, prixByBien] = await Promise.all([ pb.collection('biens').getFullList({ filter, sort: '-id', expand: 'etape,source_contact', }), fetchPrixMapForUser(uid), ]); return { biens, prixByBien }; }, enabled: Boolean(uid), }); const invalidateBiens = () => { void queryClient.invalidateQueries({ queryKey: ['biens'] }); }; const createBien = useMutation({ mutationFn: async (payload: { bien: BienCreate; prixEstime?: number }) => { if (!uid) throw new Error('Utilisateur non connecté'); const created = await pb.collection('biens').create(payload.bien); if ( payload.prixEstime != null && !Number.isNaN(payload.prixEstime) && payload.prixEstime > 0 ) { await pb.collection('analyses_financieres').create({ user: uid, bien: created.id, prix_achat: payload.prixEstime, type_bien_fiscal: 'ancien', }); } return created.id; }, onSuccess: invalidateBiens, }); const updateBien = useMutation({ mutationFn: async ({ id, data }: { id: string; data: BienUpdate }) => { return pb.collection('biens').update(id, data); }, onSuccess: (_, variables) => { invalidateBiens(); void queryClient.invalidateQueries({ queryKey: ['bien_detail', variables.id] }); }, }); const deleteBien = useMutation({ mutationFn: async (id: string) => pb.collection('biens').delete(id), onSuccess: invalidateBiens, }); const moveBienToEtape = useMutation({ mutationFn: async ({ bienId, etapeId }: { bienId: string; etapeId: string }) => { return pb.collection('biens').update(bienId, { etape: etapeId }); }, onSuccess: (_, variables) => { invalidateBiens(); void queryClient.invalidateQueries({ queryKey: ['bien_detail', variables.bienId] }); }, }); return { biens: query.data?.biens ?? [], prixByBien: query.data?.prixByBien ?? new Map(), isLoading: query.isPending, error: query.error, refetch: query.refetch, fetchBiens: query.refetch, createBien: createBien.mutateAsync, updateBien: updateBien.mutateAsync, deleteBien: deleteBien.mutateAsync, moveBienToEtape: moveBienToEtape.mutateAsync, }; } export function useBienDetail(bienId: string | undefined) { const query = useQuery({ queryKey: ['bien_detail', bienId], queryFn: () => fetchBienDetail(bienId!), enabled: Boolean(bienId), }); return { bundle: query.data ?? null, isLoading: query.isPending, error: query.error, refetch: query.refetch, fetchBienDetail: query.refetch, }; }