SPB Git forge

spb/qc26

Public
10commits 1branches 0releases
2.8 MBsize
maindefault branch
17 days agolast push
Python 51.6% TypeScript 46.7% CSS 1.7%
3.7 KB · 40 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { apiSafe, type Party } from "@/lib/api";4import { PartyLogo } from "@/components/party";5import { Empty } from "@/components/ui";6import { dateFr, int, pct, prob } from "@/lib/format";7import { partyVar } from "@/lib/parties";89export const metadata: Metadata = { title: "Partis — chefs, candidatures, projections, documents", description: "Les cinq principaux partis de l'élection québécoise 2026 : chef·fe (registre DGEQ), candidatures officielles, sièges projetés, documents et propositions." };10export const revalidate = 120;1112export default async function Page() {13  const data = await apiSafe<{ parties: Party[] }>("/api/parties");14  if (!data) return <Empty title="Partis indisponibles" />;15  return (16    <div className="space-y-6">17      <div className="flex items-end justify-between flex-wrap gap-3">18        <div><div className="eyebrow eyebrow-accent">Partis · registre officiel des partis autorisés (REPAQ)</div><h1 className="display text-[40px] md:text-[56px] mt-3">Party Explorer</h1></div>19        <Link href="/partis/comparateur" className="inline-flex items-center rounded-[8px] bg-brand text-white px-4 py-2.5 text-[14px] font-semibold">Comparateur des positions →</Link>20      </div>21      <div className="grid md:grid-cols-2 xl:grid-cols-3 gap-4">22        {data.parties.sort((a, b) => (b.forecast?.seatsMedian ?? 0) - (a.forecast?.seatsMedian ?? 0)).map((p) => (23          <Link key={p.id} href={`/partis/${p.id}`} className="card card-lg p-5 block hover:shadow-md transition-shadow" style={{ borderTop: `4px solid ${p.color}` }}>24            <div className="flex items-center justify-between gap-3"><PartyLogo id={p.id} height={30} /><span className="text-[11px] uppercase tracking-wide text-ink-3">{p.abbreviation}</span></div>25            <div className="font-bold text-[18px] mt-3 leading-tight">{p.officialName}</div>26            <div className="text-[13px] text-ink-2 mt-1">{p.leader ? <>Chef·fe : <b className="text-ink">{p.leader}</b>{p.leaderSince ? <span className="text-ink-3"> · depuis {dateFr(p.leaderSince, { month: "long", year: "numeric" })}</span> : null}</> : "—"}</div>27            <div className="grid grid-cols-3 gap-3 mt-4 text-center">28              <div className="surface-2 rounded-[8px] p-2.5"><div className="text-[11px] text-ink-3">Sièges projetés</div><div className="num font-bold text-[22px]" style={{ color: partyVar(p.id) }}>{p.forecast?.seatsMedian ?? "—"}</div><div className="text-[11px] text-ink-3 num">{p.forecast ? `${p.forecast.seats80[0]}–${p.forecast.seats80[1]}` : ""}</div></div>29              <div className="surface-2 rounded-[8px] p-2.5"><div className="text-[11px] text-ink-3">Vote projeté</div><div className="num font-bold text-[22px]">{p.forecast ? pct(p.forecast.voteMean, 1, "") : "—"}<span className="text-[13px]"> %</span></div><div className="text-[11px] text-ink-3 num">2022 : {pct(p.vote2022, 1)}</div></div>30              <div className="surface-2 rounded-[8px] p-2.5"><div className="text-[11px] text-ink-3">P(majorité)</div><div className="num font-bold text-[22px]">{prob(p.forecast?.pMajority)}</div><div className="text-[11px] text-ink-3 num">2022 : {p.seats2022} sièges</div></div>31            </div>32            <div className="flex justify-between text-[12.5px] text-ink-2 mt-4 num"><span>{int(p.candidatesCount)} candidatures</span><span>{p.documents} documents · {p.proposals} propositions</span></div>33          </Link>34        ))}35      </div>36      <p className="text-[12px] text-ink-3">Chef·fe et nom officiel tels qu&apos;inscrits au registre des partis autorisés d&apos;Élections Québec (pp_autorises.json). Logos officiels tirés des sites des partis, non modifiés.</p>37    </div>38  );39}40