spb/cancerindex
Public
1import Link from 'next/link';2import { Badge, ClaimBadge, StatusBadge } from '@/components/ui/badge';3import { SourceBadge, TableProvenance } from '@/components/ui/source-badge';4import type { ApprovalRow } from '@/lib/queries/drugs';5import type { ProvRow } from '@/lib/queries/provenance';6import { toInfo } from '@/lib/queries/provenance';7import { fmtDate, fmtInt } from '@/lib/format';89/**10 * Jurisdiction-aware regulatory status (§13). Never a bare "approved". Approvals may come from11 * different authorities' datasets, so rows keep a compact badge with a dataset · version title;12 * the caption carries one full popover (first row's provenance) and the claim label.13 */14export function ApprovalsTable({ rows, prov, showDrug = true, showCancer = true }: { rows: ApprovalRow[]; prov: Map<number, ProvRow>; showDrug?: boolean; showCancer?: boolean }) {15 const first = rows[0];16 const caption = first ? (toInfo(prov.get(first.provenance_id)) ?? { sourceSlug: first.source_slug, sourceName: first.source_name }) : null;17 const sources = new Set(rows.map((r) => r.source_slug));18 return (19 <div>20 {caption ? (21 <TableProvenance p={caption} claim={<ClaimBadge kind="regulatory" />}>22 {fmtInt(rows.length)} approval record{rows.length === 1 ? '' : 's'}23 {sources.size > 1 ? ` from ${sources.size} sources (hover a row badge for its dataset)` : ''} · authority, jurisdiction and indication text as published.24 </TableProvenance>25 ) : null}26 <div className="ci-table-wrap">27 <table className="ci-table">28 <thead>29 <tr>30 {showDrug ? <th scope="col">Drug</th> : null}31 <th scope="col">Jurisdiction · authority</th>32 {showCancer ? <th scope="col">Cancer</th> : null}33 <th scope="col">Indication</th>34 <th scope="col">Status</th>35 <th scope="col">Approval date</th>36 <th scope="col">Source</th>37 </tr>38 </thead>39 <tbody>40 {rows.map((a) => (41 <tr key={a.id}>42 {showDrug ? (43 <td>44 <Link className="ci-link" href={`/drug/${a.drug_slug}`}>45 {a.drug_name}46 </Link>47 </td>48 ) : null}49 <td className="whitespace-nowrap">50 <span className="ci-mono font-medium">{a.jurisdiction}</span> <span className="text-ink-3">{a.authority}</span>51 </td>52 {showCancer ? (53 <td>54 {a.tumor_agnostic ? <Badge tone="accent">Tumor-agnostic</Badge> : null}55 {a.cancer_slug ? (56 <Link className="ci-link" href={`/cancer/${a.cancer_slug}`}>57 {a.cancer_name}58 </Link>59 ) : !a.tumor_agnostic ? (60 <span className="text-ink-3">—</span>61 ) : null}62 </td>63 ) : null}64 <td className="min-w-[280px] max-w-[520px] text-[12.5px]">65 {a.indication}66 <span className="mt-0.5 flex flex-wrap gap-1">67 {a.line_of_therapy ? <Badge tone="outline">{a.line_of_therapy}</Badge> : null}68 {a.disease_stage ? <Badge tone="outline">{a.disease_stage}</Badge> : null}69 {a.accelerated ? <Badge tone="warn">accelerated</Badge> : null}70 {a.conditional ? <Badge tone="warn">conditional</Badge> : null}71 {a.biomarker_ids.length ? <Badge tone="outline">biomarker-restricted</Badge> : null}72 </span>73 </td>74 <td>75 <StatusBadge status={a.status} />76 {a.withdrawal_date ? <span className="block text-[11px] text-danger">withdrawn {fmtDate(a.withdrawal_date)}</span> : null}77 </td>78 <td className="whitespace-nowrap">{fmtDate(a.approval_date)}</td>79 <td>80 <SourceBadge compact p={toInfo(prov.get(a.provenance_id)) ?? { sourceSlug: a.source_slug, sourceName: a.source_name }} />81 </td>82 </tr>83 ))}84 </tbody>85 </table>86 </div>87 </div>88 );89}90