TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1import type { ToveliEstimate } from "@/lib/estimate";2import { fmtPrice, fmtPct } from "@/lib/format";3export function EstimateBreakdown({ e, price }: { e: ToveliEstimate; price?: number | null }) {4 const r = e.result;5 const w = (v: number | null) => (v && r.estimate ? Math.min(100, Math.max(0, ((v - r.low) / Math.max(1, r.high - r.low)) * 100)) : null);6 return (7 <div className="tv-card p-4">8 <div className="relative h-2 rounded-full bg-surface-2 mt-6 mb-7">9 <div className="absolute inset-y-0 rounded-full bg-brand-soft" style={{ left: 0, right: 0 }} />10 {[["Estimation", r.estimate, "bg-brand"], ...(r.modelEstimate ? [["Modèle", r.modelEstimate, "bg-ink-3"]] : []), ...(r.compsEstimate ? [["Comparables", r.compsEstimate, "bg-ink-3"]] : []), ...(price ? [["Prix demandé", price, "bg-info"]] : [])].map(([k, v, c]) => { const x = w(v as number); return x == null ? null : <span key={String(k)} className="absolute -top-5 text-[10px] text-ink-3 -translate-x-1/2 whitespace-nowrap" style={{ left: `${x}%` }}>{k}<span className={`block mx-auto mt-0.5 w-2.5 h-2.5 rounded-full ${c}`} /></span>; })}11 <span className="absolute -bottom-5 left-0 text-[11px] text-ink-3">{fmtPrice(r.low)}</span><span className="absolute -bottom-5 right-0 text-[11px] text-ink-3">{fmtPrice(r.high)}</span>12 </div>13 <dl className="grid sm:grid-cols-2 gap-x-6 gap-y-1 text-sm">14 {r.modelEstimate != null && <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Modèle hédonique (poids {Math.round(r.modelWeight * 100)} %)</dt><dd className="tabular-nums">{fmtPrice(r.modelEstimate)}</dd></div>}15 {r.compsEstimate != null && <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Comparables ajustés (poids {Math.round((1 - r.modelWeight) * 100)} %)</dt><dd className="tabular-nums">{fmtPrice(r.compsEstimate)}</dd></div>}16 <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Comparables retenus</dt><dd>{r.nCompsUsed}</dd></div>17 {r.compsDispersionPct != null && <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Dispersion des comparables</dt><dd>{r.compsDispersionPct} %</dd></div>}18 {e.roleUnit?.role_value != null && <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Valeur au rôle</dt><dd className="tabular-nums">{fmtPrice(e.roleUnit.role_value)} <span className="text-ink-3 text-xs">({fmtPct(r.estimate / e.roleUnit.role_value - 1)})</span></dd></div>}19 <div className="flex justify-between border-b border-line py-1"><dt className="text-ink-3">Largeur de la fourchette</dt><dd>{fmtPct((r.high - r.low) / r.estimate, 0, false)}</dd></div>20 </dl>21 <p className="mt-3 text-xs text-ink-3">Entrées : {e.method.inputs.join(" ; ")}.</p>22 </div>23 );24}25