// ----------------------------------------------------------------------------- // Immo-Ka — Agrégateur de propriétés à vendre (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/useFicheData.ts : orchestration des données secondaires de la fiche. // Un seul endroit charge les API annexes (au lieu d'un fetch par bloc) : // · groupe CRITIQUE (au montage) : inondation, qualité de l'air — nécessaires // à « En bref » et au premier écran ; // · groupe DIFFÉRÉ (quand l'utilisateur approche la carte, ou après 2,5 s) : // commerces/transport, essence, Hydro-Québec (cache seulement). // Le prix (Vrai-Prix), le quartier, les POI et l'historique de prix arrivent // déjà avec l'annonce (/api/listings/{uid}). Le financement a son propre // cycle (calculateur interactif). Chaque ressource porte son statut // (loading / ok / error / na) : squelettes et états vides propres. // ----------------------------------------------------------------------------- import { useCallback, useEffect, useRef, useState } from "react"; import { AirNearby, CommercesNearby, GazNearby, HydroEstimate, Inondation, Listing, fetchAir, fetchCommerces, fetchGaz, fetchHydro, fetchInondation, } from "../api"; export type Res = | { status: "idle" | "loading" } | { status: "ok"; data: T } | { status: "error"; error: string } | { status: "na" }; // non applicable (pas de coordonnées, pas d'adresse…) export interface FicheData { inondation: Res; air: Res; commerces: Res; gaz: Res; hydro: Res; /** déclenche le groupe différé (appelé par la sentinelle de la carte) */ wake: () => void; /** relance une ressource en erreur */ retry: (key: keyof Omit) => void; } type Key = keyof Omit; const LOADING = { status: "loading" } as const; const NA = { status: "na" } as const; const CRITIQUE: Key[] = ["inondation", "air"]; const DIFFERE: Key[] = ["commerces", "gaz", "hydro"]; export function dataOf(r: Res): T | null { return r.status === "ok" ? r.data : null; } export default function useFicheData(l: Listing | null): FicheData { const [state, setState] = useState>>({ inondation: LOADING, air: LOADING, commerces: { status: "idle" }, gaz: { status: "idle" }, hydro: { status: "idle" }, }); const [awake, setAwake] = useState(false); const uidRef = useRef(null); const started = useRef>(new Set()); const set = useCallback((k: Key, r: Res) => setState((s) => ({ ...s, [k]: r })), []); const load = useCallback((k: Key, p: (() => Promise) | null) => { if (!p) { set(k, NA); return; } const uid = uidRef.current; set(k, LOADING); p().then((d) => { if (uidRef.current === uid) set(k, { status: "ok", data: d }); }) .catch((e: unknown) => { if (uidRef.current !== uid) return; // 404 = base absente ou hors couverture → état « non applicable » (pas une erreur) const msg = String(e); set(k, /API 404/.test(msg) ? NA : { status: "error", error: msg }); }); }, [set]); const loaders = useCallback((k: Key): (() => Promise) | null => { if (!l) return null; const geo = l.lat != null && l.lng != null; const lat = l.lat as number, lng = l.lng as number; switch (k) { case "inondation": return geo ? () => fetchInondation(lat, lng) : null; case "air": return geo ? () => fetchAir(lat, lng) : null; case "commerces": return geo ? () => fetchCommerces(lat, lng) : null; case "gaz": return geo ? () => fetchGaz(lat, lng, 60) : null; case "hydro": return (l.address || l.title) ? () => fetchHydro(l.address || l.title, { uid: l.uid, lat: l.lat, lng: l.lng }, false) : null; } }, [l]); // groupe critique : dès que l'annonce est connue useEffect(() => { if (!l) return; uidRef.current = l.uid; started.current = new Set(); setAwake(false); for (const k of CRITIQUE) load(k, loaders(k)); for (const k of DIFFERE) set(k, { status: "idle" }); const t = setTimeout(() => setAwake(true), 2500); // filet : réveil après 2,5 s return () => clearTimeout(t); }, [l, load, loaders, set]); // groupe différé : au réveil (proximité de la carte ou délai) useEffect(() => { if (!l || !awake) return; for (const k of DIFFERE) { if (started.current.has(k)) continue; started.current.add(k); load(k, loaders(k)); } }, [awake, l, load, loaders]); const wake = useCallback(() => setAwake(true), []); const retry = useCallback((k: Key) => load(k, loaders(k)), [load, loaders]); return { ...(state as unknown as Omit), wake, retry }; }