SPB Git forge

spb/uqo-eval

Public
55commits 2branches 0releases
134.4 MBsize
maindefault branch
2 h agolast push
TypeScript 90.1% JavaScript 3.2% Python 3.2% CSS 1.8% HTML 1.8%
5.0 KB · 112 lines tsx
Raw Blame History
1// UQO Éval — laboratoire pédagogique d'évaluation immobilière2/**3 * /stats — bandeau « LE CHIFFRE » (valeur totale du parc immobilier4 * québécois, comparaisons vulgarisées). Les sections analytiques5 * (KPI, jauges, séries, répartitions, tableaux, records, rapports PDF)6 * sont servies par <StatsDashboard/> — module Stats commun v2.7 */8"use client";9import { CountUp } from "./MetricViz";10import { useLang } from "./LangContext";1112export interface ProvStats {13  generated: string;14  unites: number;15  valeur_totale_2026: number;16  valeur_role_totale: number;17  valeur_mediane_2026: number;18  croissance_2021_2026_pct: number;19  logements: number;20  aire_etages_km2: number;21  terrain_km2: number;22  municipalites: number;23  totaux_annee: { year: number; total: number; n: number }[];24  par_type: { type: string; n: number; total: number; mediane: number | null }[];25  par_ville: { ville: string; n: number; total: number; mediane: number | null }[];26}2728const POP_QC = 9_111_000; // population du Québec (2026, ISQ)29const BUDGET_QC = 165_800_000_000; // dépenses budget QC 2026-2027 (approx.)30const PIB_QC = 640_000_000_000; // PIB nominal QC (approx. 2025)3132const money = (v: number, lang: string, frac = 0) =>33  new Intl.NumberFormat(lang === "fr" ? "fr-CA" : "en-CA", {34    style: "currency",35    currency: "CAD",36    maximumFractionDigits: frac,37  }).format(v);3839const compact = (v: number, lang: string) => {40  const fr = lang === "fr";41  if (v >= 1e12) return `${(v / 1e12).toLocaleString(fr ? "fr-CA" : "en-CA", { maximumFractionDigits: 2 })} ${fr ? "billions $" : "T$"}`;42  if (v >= 1e9) return `${(v / 1e9).toLocaleString(fr ? "fr-CA" : "en-CA", { maximumFractionDigits: 1 })} G$`;43  if (v >= 1e6) return `${(v / 1e6).toLocaleString(fr ? "fr-CA" : "en-CA", { maximumFractionDigits: 1 })} M$`;44  return money(v, lang);45};4647const num = (v: number, lang: string) =>48  v.toLocaleString(lang === "fr" ? "fr-CA" : "en-CA");4950export default function StatsView({ s }: { s: ProvStats }) {51  const { lang } = useLang();52  const fr = lang === "fr";5354  return (55    <div className="pt-10 sm:pt-14">56      {/* ---- LE CHIFFRE ---- */}57      <section>58        <span className="kicker">{fr ? "Juste pour le show" : "Just for the show"}</span>59        <h1 className="vp-display mt-3 text-[clamp(26px,4.6vw,46px)] font-bold uppercase leading-[1.02] tracking-[-0.03em]">60          {fr ? (61            <>62              Tout l&apos;immobilier du Québec vaut,63              <br />64              <span className="hl">au dollar près</span>…65            </>66          ) : (67            <>68              All Québec real estate is worth,69              <br />70              <span className="hl">to the dollar</span>…71            </>72          )}73        </h1>74        <div className="rise mt-6 rounded-[2px] bg-[var(--ink-deep)] p-6 sm:p-10">75          <p className="vp-display break-all text-[clamp(30px,6.2vw,76px)] font-bold leading-none tracking-[-0.03em] text-[var(--accent-bright)]">76            <CountUp77              value={s.valeur_totale_2026}78              duration={2600}79              format={(v) => money(v, lang)}80            />81          </p>82          <p className="vp-mono mt-4 text-[11.5px] uppercase tracking-[0.1em] text-[rgba(255,81,72,0.75)]">83            {fr84              ? `≈ ${compact(s.valeur_totale_2026, lang)} · ${num(s.unites, lang)} propriétés · ${num(s.municipalites, lang)} municipalités · millésime 2026`85              : `≈ ${compact(s.valeur_totale_2026, lang)} · ${num(s.unites, lang)} properties · ${num(s.municipalites, lang)} municipalities · 2026 vintage`}86          </p>87          <div className="mt-6 flex flex-wrap gap-2.5">88            {(89              [90                [fr ? "par Québécois·e" : "per Quebecer", money(s.valeur_totale_2026 / POP_QC, lang)],91                [fr ? "budgets annuels du Québec" : "annual Québec budgets", `× ${(s.valeur_totale_2026 / BUDGET_QC).toFixed(1)}`],92                [fr ? "fois le PIB du Québec" : "× Québec GDP", `× ${(s.valeur_totale_2026 / PIB_QC).toFixed(1)}`],93                [fr ? "depuis 2021 (périmètre constant)" : "since 2021 (constant perimeter)", `+${s.croissance_2021_2026_pct.toLocaleString(fr ? "fr-CA" : "en-CA")} %`],94              ] as [string, string][]95            ).map(([k, v]) => (96              <span key={k} className="stat-chip max-w-full !whitespace-normal !border-[rgba(245,243,238,0.25)] !text-[rgba(245,243,238,0.85)]">97                <b className="!text-[var(--accent-bright)]">{v}</b> {k}98              </span>99            ))}100          </div>101        </div>102        <p className="vp-mono mt-3 text-[10.5px] uppercase tracking-[0.06em] text-ink-3">103          {fr104            ? "Somme des 3 747 008 estimations UQO Éval (modèle hédonique, rôle 2026). Évaluation municipale totale correspondante : "105            : "Sum of 3,747,008 UQO Éval estimates (hedonic model, 2026 roll). Corresponding total municipal assessment: "}106          {compact(s.valeur_role_totale, lang)}.107        </p>108      </section>109    </div>110  );111}112