205 lines
6.0 KiB
TypeScript
205 lines
6.0 KiB
TypeScript
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<Map<string, number>> {
|
|
const analyses = await pb.collection('analyses_financieres').getFullList<AnalyseFinanciereRecord>({
|
|
filter: `user="${uid}"`,
|
|
});
|
|
const map = new Map<string, number>();
|
|
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<BienDetailBundle> {
|
|
const uid = getCurrentUserId();
|
|
if (!uid) throw new Error('Utilisateur non connecté');
|
|
let bien: BienExpanded;
|
|
try {
|
|
bien = await pb.collection('biens').getOne<BienExpanded>(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<VisiteRecord>({
|
|
filter: `bien="${bienId}"`,
|
|
sort: '-date_visite',
|
|
}),
|
|
pb.collection('notes_biens').getFullList<NoteRecord>({
|
|
filter: `bien="${bienId}"`,
|
|
sort: '-id',
|
|
}),
|
|
pb.collection('documents_biens').getFullList<DocumentRecord>({
|
|
filter: `bien="${bienId}"`,
|
|
sort: '-id',
|
|
}),
|
|
pb.collection('analyses_financieres').getList<AnalyseFinanciereRecord>(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<string, number>() };
|
|
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<BienExpanded>({
|
|
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<BienRecord>(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<BienRecord>(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<string, number>(),
|
|
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,
|
|
};
|
|
}
|