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.1 KB · 40 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { apiSafe, type Meta } from "@/lib/api";4import { PartyLogo } from "@/components/party";5import { Card, Empty } from "@/components/ui";6import { PARTY_IDS, partyVar, partyLabel } from "@/lib/parties";78export const metadata: Metadata = { title: "Comparateur des partis — matrice enjeux × partis", description: "Ce que proposent la CAQ, le PQ, le PLQ, QS et le PCQ sur chaque enjeu, d'après leurs documents officiels ; chaque cellule renvoie à la source." };9export const revalidate = 300;1011interface Matrix { topics: { id: string; label: string; counts: Record<string, number> }[]; top: Record<string, { id: number; proposal: string; page: number | null; documentId: number }> }1213export default async function Page() {14  const [m, meta] = await Promise.all([apiSafe<Matrix>("/api/matrix"), apiSafe<Meta>("/api/meta")]);15  if (!m || !meta) return <Empty title="Comparateur indisponible" />;16  return (17    <div className="space-y-6">18      <div><div className="eyebrow eyebrow-accent">Policy Matrix · documents officiels</div><h1 className="display text-[40px] md:text-[56px] mt-3">Comparateur des partis</h1><p className="text-ink-2 mt-3 max-w-2xl text-[15px] leading-relaxed">Une cellule = la proposition la mieux documentée du parti sur l&apos;enjeu (cliquez pour la source, la page et la citation). Une case vide signifie qu&apos;aucun document officiel ingéré n&apos;aborde le sujet, pas que le parti n&apos;a pas de position.</p></div>19      {m.topics.length === 0 ? <Empty title="Extraction en cours">Les documents officiels des partis sont en cours d&apos;analyse ; la matrice se remplit au fil des heures.</Empty> : (20        <Card pad={false} className="overflow-x-auto">21          <table className="data-table min-w-[900px] max-md:min-w-[1100px]">22            <thead><tr><th className="w-[160px] sticky left-0 z-[2] bg-surface">Enjeu</th>{PARTY_IDS.map((id) => <th key={id}><span className="inline-flex items-center gap-2"><PartyLogo id={id} height={14} />{partyLabel(id)}</span></th>)}</tr></thead>23            <tbody>24              {m.topics.map((t) => (25                <tr key={t.id}>26                  <td className="font-semibold align-top sticky left-0 z-[1] bg-surface min-w-[120px] max-md:text-[12.5px]"><Link href={`/enjeux/${t.id}`} className="hover:underline">{t.label}</Link></td>27                  {PARTY_IDS.map((id) => { const cell = m.top[`${t.id}:${id}`]; return (28                    <td key={id} className="align-top text-[12.5px] leading-snug" style={{ borderLeft: `3px solid ${cell ? partyVar(id) : "transparent"}` }}>29                      {cell ? <Link href={`/documents/${cell.documentId}#p${cell.id}`} className="hover:underline">{cell.proposal.length > 150 ? cell.proposal.slice(0, 150) + "…" : cell.proposal}<span className="text-ink-3"> {cell.page ? `· p. ${cell.page}` : ""} · {t.counts[id] ?? 0} prop.</span></Link> : <span className="text-ink-3">—</span>}30                    </td>); })}31                </tr>32              ))}33            </tbody>34          </table>35        </Card>36      )}37    </div>38  );39}40