Python 51.6%
TypeScript 46.7%
CSS 1.7%
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'enjeu (cliquez pour la source, la page et la citation). Une case vide signifie qu'aucun document officiel ingéré n'aborde le sujet, pas que le parti n'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'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