// Auteur : Simon-Pierre Boucher — contact@spboucher.ai "use client"; import { useState } from "react"; import Link from "next/link"; import { useLang } from "./LangContext"; import LeadForm from "./LeadForm"; import RadarMap from "./RadarMap"; import FavButton from "./FavButton"; import { BuildingGlyph, ConfidenceDial, CountUp, EraLine, locNum, LotDiagram, MarketIndexChart, PercentileBar, RangeBar, ValueSplit, } from "./MetricViz"; import type { EstimateResult } from "@/lib/engine"; import type { UnitEstimate } from "@/lib/estimator"; import type { TKey } from "@/lib/i18n"; const fmt = (v: number | null | undefined, lang: string) => v == null ? "—" : new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0, }).format(v); const signed = (v: number, lang: string) => `${v >= 0 ? "+" : "−"}${fmt(Math.abs(Math.round(v)), lang)}`; function Chip({ k, v }: { k: string; v: string | number }) { return (

{k}

{v}

); } export default function ResultView({ data }: { data: UnitEstimate }) { const { lang, t } = useLang(); const [activeComp, setActiveComp] = useState(null); const r: EstimateResult = data.result; const u = data.unit; const ctx = data.context; const s = u?.specs; const fr = lang === "fr"; const histMax = Math.max(...(u?.history.map((h) => h.value ?? 0) ?? [0]), 1); const moneyFmt = (v: number) => new Intl.NumberFormat(fr ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0, }).format(Math.round(v / 100) * 100); return (
{/* fil d'ariane */} {/* ---- Panneau prix ---- */}
{/* basis large : sous ~380px de large la jauge passe dessous au lieu d'écraser la colonne */}
{t("estimatedValue")} {/* ♥ Mon univers Ka — favori unifié Groupe KA */} {u && ( 0 ? moneyFmt(r.estimate) : "", url: `https://www.vrai-prix.com/estimation/${encodeURIComponent(u.id)}`, }} /> )}
{u && (

{u.adresse} {s?.apt ? ` app. ${s.apt}` : ""} {u.municipalite ? · {u.municipalite} : null}

)}

{/* faits rapides */}
{[ u ? t(u.typeProp as TKey) : null, u?.aireEtagesM2 && r.estimate > 0 ? `${locNum(Math.round(r.estimate / u.aireEtagesM2), lang, { maxFrac: 0 })} $/m²` : null, u?.aireEtagesM2 ? locNum(u.aireEtagesM2, lang, { unit: "m²" }) : null, u?.anneeConstruction ? `${fr ? "constr." : "built"} ${u.anneeConstruction}` : null, u?.superficieTerrainM2 ? `${fr ? "terrain" : "lot"} ${locNum(u.superficieTerrainM2, lang, { unit: "m²" })}` : null, ] .filter((x): x is string => x != null) .map((x) => ( {x} ))}
{/* fourchette visuelle P10-P90 avec position du rôle */}

{t("range")}

{u?.valeurRole != null && (

{t("assessed")}

{fmt(u.valeurRole, lang)}

)} {u?.valeurRole != null && u.valeurRole > 0 && r.estimate > 0 && (

{fr ? "Écart vs rôle" : "vs assessment"}

{r.estimate >= u.valeurRole ? "+" : ""} {((r.estimate / u.valeurRole - 1) * 100).toFixed(0)} %

)}
{u && ( )}

{t("disclaimer")}

{/* ---- Portrait de la propriété (bento du registre) ---- */} {u && s && (
{fr ? "Registre officiel — dessiné par les données" : "Official record — drawn by the data"}

{fr ? "Portrait de la propriété" : "Property portrait"}

{/* Bâtiment */}

{fr ? "Bâtiment" : "Building"}

{/* Terrain */}

{fr ? "Terrain — à l'échelle" : "Lot — to scale"}

= 1 && !u.superficieTerrainM2)} />
{/* Valeur au rôle */}

{fr ? "Valeur au rôle 2026 — composition" : "2026 assessed value — split"}

{fmt(u.valeurRole, lang)}

{/* registre brut complet */}
{fr ? "▸ Toutes les données du registre (brutes)" : "▸ All registry data (raw)"}
)} {/* ---- Transparence du calcul ---- */}
{fr ? "Transparence" : "Transparency"}

{t("whyTitle")}

{t("modelPart")}

{r.modelEstimate != null ? ( ) : ( "—" )}

{t("weightModel")} : {(r.modelWeight * 100).toFixed(0)} %

{t("compsPart")}

{r.compsEstimate != null ? ( ) : ( "—" )}

{t("weightComps")} : {((1 - r.modelWeight) * 100).toFixed(0)} % · {r.nCompsUsed} comps {r.compsDispersionPct != null ? ` · ±${r.compsDispersionPct} %` : ""}

{/* ---- Marché local ---- */} {ctx && (ctx.index.length >= 6 || ctx.sector || ctx.muniPercentile != null) && (
{fr ? "Contexte de marché" : "Market context"}

{fr ? "Le marché autour de cette propriété" : "The market around this property"}

{/* trajectoire du marché */} {ctx.index.length >= 6 && (

{fr ? `Indice de marché — ${t(ctx.typeProp as TKey)} (base 100 en ${ctx.index[0].month.slice(0, 4)})` : `Market index — ${t(ctx.typeProp as TKey)} (base 100 in ${ctx.index[0].month.slice(0, 4)})`}

{ctx.index12mPct != null && ( = 0 ? "bg-lime text-ink" : "bg-danger text-white"}`}> 12 {fr ? "mois" : "mo"} {ctx.index12mPct >= 0 ? "+" : ""} {ctx.index12mPct.toFixed(1)} % )} {ctx.indexSince2021Pct != null && ( {ctx.index[0].month.slice(0, 4)}→ {ctx.indexSince2021Pct >= 0 ? "+" : ""} {ctx.indexSince2021Pct.toFixed(0)} % )}

{fr ? "Indice utilisé pour l'ajustement « marché » des comparables — même donnée, même calcul." : "Index used for the comparables' market adjustment — same data, same math."}

)}
{/* ventes du secteur */} {ctx.sector && (

{fr ? `Ventes réelles — rayon ${ctx.sector.radiusKm} km · 12 mois` : `Real sales — ${ctx.sector.radiusKm} km radius · 12 months`}

{fr ? "Ventes" : "Sales"}

{ctx.sector.nSales12m}

{fr ? "Prix médian" : "Median price"}

{ctx.sector.medianPrice != null ? fmt(Math.round(ctx.sector.medianPrice), lang) : "—"}

$/m² {fr ? "médian" : "median"}

{ctx.sector.medianPpm2 != null ? locNum(Math.round(ctx.sector.medianPpm2), lang, { maxFrac: 0 }) : "—"}

{ctx.sector.medianPrice != null && r.estimate > 0 && (

{fr ? "Cette propriété" : "This property"} :{" "} {r.estimate >= ctx.sector.medianPrice ? "+" : "−"} {Math.abs((r.estimate / ctx.sector.medianPrice - 1) * 100).toFixed(0)} % {" "} {fr ? "vs médiane du secteur" : "vs sector median"}

)}
)} {/* position dans la municipalité */} {ctx.muniPercentile != null && u?.municipalite && (

{fr ? `Position dans ${u.municipalite}` : `Position in ${u.municipalite}`}

{fr ? ( <> Vaut plus que{" "} {Math.round(ctx.muniPercentile)} % des{" "} {ctx.muniTotal != null ? locNum(ctx.muniTotal, lang, { maxFrac: 0 }) : ""}{" "} propriétés estimées de la municipalité. ) : ( <> Worth more than{" "} {Math.round(ctx.muniPercentile)} % of the{" "} {ctx.muniTotal != null ? locNum(ctx.muniTotal, lang, { maxFrac: 0 }) : ""}{" "} estimated properties in the municipality. )}

)}
)} {/* ---- Historique ---- */} {u && u.history.some((h) => h.value != null) && (
2021 → 2026

{t("history")}

{u.history.map((h, i) => { const prev = i > 0 ? u.history[i - 1].value : null; const yoy = h.value != null && prev != null && prev > 0 ? ((h.value / prev - 1) * 100) : null; return (
{h.year} {fmt(h.value, lang)} {yoy != null && ( = 0 ? "bg-lime-soft text-green-deep" : "bg-[var(--danger-soft)] text-[var(--danger)]"}`}> {yoy >= 0 ? "+" : ""} {yoy.toFixed(0)} % )}
); })}
)} {/* ---- Plan des comparables (carte maison) ---- */}
{r.nCompsUsed} {fr ? "ventes réelles" : "real sales"}

{t("compsTitle")}

({ id: c.id, lat: c.lat, lng: c.lng, label: `${c.street ?? ""}, ${c.city ?? ""}`, price: fmt(c.amount, lang), adjusted: fmt(Math.round(c.adjustedPrice), lang), date: c.date, distanceM: c.distanceM, weight: c.weight, }))} highlight={activeComp} onHover={setActiveComp} />
{r.comps.map((c, i) => ( setActiveComp(c.id)} onMouseLeave={() => setActiveComp(null)} className={activeComp === c.id ? "!bg-lime-soft" : ""} > ))}
# {fr ? "Vente" : "Sale"} {t("adjusted")}
{i + 1}

{c.street}, {c.city}

{c.date} · {c.distanceM < 1000 ? `${Math.round(c.distanceM)} m` : `${(c.distanceM / 1000).toFixed(1)} km`} ·{" "} {t("sold")} {fmt(c.amount, lang)} {c.floorArea && c.floorArea > 20 ? ` (${locNum(Math.round(c.amount / c.floorArea), lang, { maxFrac: 0 })} $/m²)` : ""}

{(c.floorArea || c.yearBuilt) && (

{[ c.floorArea ? locNum(c.floorArea, lang, { unit: "m²" }) : null, c.yearBuilt ? `${fr ? "constr." : "built"} ${c.yearBuilt}` : null, ] .filter(Boolean) .join(" · ")}

)}

{t("adjTime")} {signed(c.adjTime, lang)} · {t("adjArea")} {signed(c.adjArea, lang)} ·{" "} {t("adjAge")} {signed(c.adjAge, lang)}

{fmt(Math.round(c.adjustedPrice), lang)}

{t("weight")} {(c.weight * 100).toFixed(0)} %

{/* ---- Lead ---- */}

{t("leadTitle")}

); }