This commit is contained in:
204
app/hooks/useBiens.ts
Normal file
204
app/hooks/useBiens.ts
Normal file
@ -0,0 +1,204 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user