// ----------------------------------------------------------------------------- // Rent-Ka — Rental listings aggregator (Canada, outside Québec) // Author: Simon-Pierre Boucher — contact@spboucher.ai // components/HistoriqueLouka.tsx: "Listing history" block (detail page) // Timeline of REAL sync observations: price, description, area, // availability, inclusions, photo changes, removals/returns. + "Past // lives": likely recycled listings of the same unit (multi-signal score, // explainable inference). // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; import { HistoriqueLouka as HistoT, Recyclees, fetchHistorique, fetchRecyclees, fmtPrice, } from "../api"; const NBSP = " "; const fmtTs = (ts: number | null | undefined): string => ts ? new Date(ts * 1000).toLocaleDateString("en-CA", { day: "numeric", month: "short", year: "numeric" }) : "—"; const EVENT_LABEL: Record = { prix: "Price", description: "Description changed", superficie: "Area changed", dispo: "Availability changed", inclusions: "Inclusions changed", photos: "Photos changed", disparition: "Listing removed", reapparition: "Listing republished", }; export default function HistoriqueLouka({ uid }: { uid: string }) { const [d, setD] = useState(null); const [rec, setRec] = useState(null); useEffect(() => { setD(null); setRec(null); fetchHistorique(uid).then(setD).catch(() => setD(null)); fetchRecyclees(uid).then(setRec).catch(() => setRec(null)); }, [uid]); if (!d) return null; const variationPct = d.variation != null ? `${d.variation > 0 ? "+" : "−"}${Math.abs(Math.round(d.variation * 100))}${NBSP}%` : null; const items = d.timeline.slice(0, 12); const matches = rec?.matches ?? []; return (

Listing history

Tracked since
{fmtTs(d.premiere_observation)}
Online
{d.jours_en_ligne}{NBSP}day{d.jours_en_ligne > 1 ? "s" : ""}
{d.prix_initial != null && d.prix_actuel != null && d.prix_initial !== d.prix_actuel && (
Initial → current price
{fmtPrice(d.prix_initial)} → {fmtPrice(d.prix_actuel)} {variationPct && ` (${variationPct})`}
)}
Changes observed
{d.modifications}
{items.length > 0 && (
    {items.map((it, i) => (
  • {fmtTs(it.ts)} {it.type === "prix" ? ( it.prix_avant != null ? <>Price {it.prix_avant! > (it.prix ?? 0) ? "lowered" : "raised"} from {fmtPrice(it.prix_avant ?? null)} to {fmtPrice(it.prix ?? null)} : <>First observed price: {fmtPrice(it.prix ?? null)} ) : ( EVENT_LABEL[it.type] ?? it.type )}
  • ))}
)} {items.length === 0 && (

No change observed since this listing's first sync.

)} {matches.length > 0 && (

Likely past lives of this unit

{matches.slice(0, 3).map((m) => (
likely repost ({m.confiance}{NBSP}% confidence) {" "} {m.prix != null && <>advertised at {fmtPrice(m.prix)}} {m.derniere_observation != null && <> until {fmtTs(m.derniere_observation)}}
{m.signaux.join(" · ")}
))}

{rec?.methode} — inference, no automatic merging.

)}

{d.methode}

); }