Python 51.6%
TypeScript 46.7%
CSS 1.7%
1const fmtPct1 = new Intl.NumberFormat("fr-CA", { minimumFractionDigits: 1, maximumFractionDigits: 1 });2const fmtPct0 = new Intl.NumberFormat("fr-CA", { maximumFractionDigits: 0 });3const fmtInt = new Intl.NumberFormat("fr-CA");45export function pct(v: number | null | undefined, digits: 0 | 1 = 1, suffix = " %"): string {6 if (v === null || v === undefined || Number.isNaN(v)) return "—";7 return (digits === 1 ? fmtPct1 : fmtPct0).format(v) + suffix;8}9export function prob(p: number | null | undefined): string {10 if (p === null || p === undefined) return "—";11 if (p > 0.995) return "> 99 %";12 if (p < 0.005) return "< 1 %";13 return fmtPct0.format(p * 100) + " %";14}15export function delta(v: number | null | undefined, digits: 0 | 1 = 1, suffix = ""): string {16 if (v === null || v === undefined || Number.isNaN(v)) return "—";17 const f = (digits === 1 ? fmtPct1 : fmtPct0).format(Math.abs(v));18 if (Math.abs(v) < (digits === 1 ? 0.05 : 0.5)) return "0" + suffix;19 return (v > 0 ? "+" : "−") + f + suffix;20}21export function int(v: number | null | undefined): string {22 if (v === null || v === undefined) return "—";23 return fmtInt.format(Math.round(v));24}25export function dateFr(iso: string | null | undefined, opts: Intl.DateTimeFormatOptions = { day: "numeric", month: "long", year: "numeric" }): string {26 if (!iso) return "—";27 const d = iso.length === 10 ? new Date(iso + "T12:00:00") : new Date(iso);28 return new Intl.DateTimeFormat("fr-CA", { timeZone: "America/Toronto", ...opts }).format(d);29}30export function dateShort(iso: string | null | undefined): string {31 return dateFr(iso, { day: "numeric", month: "short" });32}33export function dateTimeFr(iso: string | null | undefined): string {34 if (!iso) return "—";35 return new Intl.DateTimeFormat("fr-CA", { timeZone: "America/Toronto", day: "numeric", month: "long", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));36}37export function timeFr(iso: string | null | undefined): string {38 if (!iso) return "—";39 return new Intl.DateTimeFormat("fr-CA", { timeZone: "America/Toronto", hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(new Date(iso));40}41export function relTime(iso: string | null | undefined): string {42 if (!iso) return "—";43 const s = Math.round((Date.now() - new Date(iso).getTime()) / 1000);44 if (s < 60) return `il y a ${s} s`;45 if (s < 3600) return `il y a ${Math.round(s / 60)} min`;46 if (s < 86400) return `il y a ${Math.round(s / 3600)} h`;47 return `il y a ${Math.round(s / 86400)} j`;48}49export function daysLabel(d: number): string {50 if (d > 0) return `J-${d}`;51 if (d === 0) return "JOUR J";52 return `J+${-d}`;53}54export function ratingLabel(r: string | null | undefined): string {55 return ({ safe: "Acquis", likely: "Probable", lean: "Favori fragile", tossup: "Serré" } as Record<string, string>)[r ?? ""] ?? "—";56}57export function statusLabel(s: string | null | undefined): string {58 return ({ NOT_STARTED: "En attente", REPORTING: "Dépouillement", LEADING: "En avance", PROJECTED: "Projeté QC26", ELECTED: "Élu·e", FINAL: "Final" } as Record<string, string>)[s ?? ""] ?? "—";59}60export function modeLabel(m: string | null | undefined): string {61 return ({ web: "Web", phone: "Téléphone", ivr: "IVR (automatisé)", mixed: "Mixte", sms: "SMS", unknown: "Non précisé" } as Record<string, string>)[m ?? ""] ?? (m ?? "—");62}63export function cls(...xs: (string | false | null | undefined)[]): string {64 return xs.filter(Boolean).join(" ");65}66