// 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 {
BuildingGlyph,
ConfidenceDial,
CountUp,
EraLine,
locNum,
LotDiagram,
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 (
);
}
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 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 */}
Vrai-Prix
/
{u ? `${u.adresse ?? ""} · ${u.municipalite ?? ""}` : t("manualTitle")}
{/* ---- Panneau prix ---- */}
{t("estimatedValue")}
{u && (
{u.adresse}
{s?.apt ? ` app. ${s.apt}` : ""}
{u.municipalite ? · {u.municipalite} : null}
)}
{t("range")}
{fmt(r.low, lang)} → {fmt(r.high, lang)}
{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} %` : ""}
{/* ---- 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}
/>
#
{fr ? "Vente" : "Sale"}
{t("adjusted")}
{r.comps.map((c, i) => (
setActiveComp(c.id)}
onMouseLeave={() => setActiveComp(null)}
className={activeComp === c.id ? "!bg-lime-soft" : ""}
>
{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)}
{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 ---- */}
);
}