SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
5.3 KB · 101 lines tsx
Raw Blame History
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';8import { Pager } from '@/components/ui/pager';9import { pageInfo } from '@/lib/pagination';1011/**12 * Jurisdiction-aware regulatory status (§13). Never a bare "approved". Approvals may come from13 * different authorities' datasets, so rows keep a compact badge with a dataset · version title;14 * the caption carries one full popover (first row's provenance) and the claim label.15 */16export function ApprovalsTable({ rows: allRows, prov, showDrug = true, showCancer = true, page = 1, pageSize = 50, hrefFor }: { rows: ApprovalRow[]; prov: Map<number, ProvRow>; showDrug?: boolean; showCancer?: boolean; page?: number; pageSize?: number; hrefFor?: (page: number) => string }) {17  // Optional server-side pagination (URL state); without hrefFor the first page only is shown with a count.18  const info = pageInfo(page, pageSize, allRows.length);19  const rows = allRows.slice(info.offset, info.offset + pageSize);20  const first = rows[0];21  const caption = first ? (toInfo(prov.get(first.provenance_id)) ?? { sourceSlug: first.source_slug, sourceName: first.source_name }) : null;22  const sources = new Set(rows.map((r) => r.source_slug));23  return (24    <div>25      {caption ? (26        <TableProvenance p={caption} claim={<ClaimBadge kind="regulatory" />}>27          {fmtInt(rows.length)} approval record{rows.length === 1 ? '' : 's'}28          {sources.size > 1 ? ` from ${sources.size} sources (hover a row badge for its dataset)` : ''} · authority, jurisdiction and indication text as published.29        </TableProvenance>30      ) : null}31      <div className="ci-table-wrap">32        <table className="ci-table">33          <thead>34            <tr>35              {showDrug ? <th scope="col">Drug</th> : null}36              <th scope="col">Jurisdiction · authority</th>37              {showCancer ? <th scope="col">Cancer</th> : null}38              <th scope="col">Indication</th>39              <th scope="col">Status</th>40              <th scope="col">Approval date</th>41              <th scope="col">Source</th>42            </tr>43          </thead>44          <tbody>45            {rows.map((a) => (46              <tr key={a.id}>47                {showDrug ? (48                  <td>49                    <Link className="ci-link" href={`/drug/${a.drug_slug}`}>50                      {a.drug_name}51                    </Link>52                  </td>53                ) : null}54                <td className="whitespace-nowrap">55                  <span className="ci-mono font-medium">{a.jurisdiction}</span> <span className="text-ink-3">{a.authority}</span>56                </td>57                {showCancer ? (58                  <td>59                    {a.tumor_agnostic ? <Badge tone="accent">Tumor-agnostic</Badge> : null}60                    {a.cancer_slug ? (61                      <Link className="ci-link" href={`/cancer/${a.cancer_slug}`}>62                        {a.cancer_name}63                      </Link>64                    ) : !a.tumor_agnostic ? (65                      <span className="text-ink-3">—</span>66                    ) : null}67                  </td>68                ) : null}69                <td className="min-w-[280px] max-w-[520px] text-[12.5px]">70                  {a.indication}71                  <span className="mt-0.5 flex flex-wrap gap-1">72                    {a.line_of_therapy ? <Badge tone="outline">{a.line_of_therapy}</Badge> : null}73                    {a.disease_stage ? <Badge tone="outline">{a.disease_stage}</Badge> : null}74                    {a.accelerated ? <Badge tone="warn">accelerated</Badge> : null}75                    {a.conditional ? <Badge tone="warn">conditional</Badge> : null}76                    {a.biomarker_ids.length ? <Badge tone="outline">biomarker-restricted</Badge> : null}77                  </span>78                </td>79                <td>80                  <StatusBadge status={a.status} />81                  {a.source_status && a.source_status.toLowerCase() !== a.status ? (82                    <span className="block text-[11px] text-ink-3" title="Status as published by the source for this one product (DIN); not the status of the molecule.">83                      source: {a.source_status}84                    </span>85                  ) : null}86                  {a.withdrawal_date ? <span className="block text-[11px] text-danger">since {fmtDate(a.withdrawal_date)}</span> : null}87                </td>88                <td className="whitespace-nowrap">{fmtDate(a.approval_date)}</td>89                <td>90                  <SourceBadge compact p={toInfo(prov.get(a.provenance_id)) ?? { sourceSlug: a.source_slug, sourceName: a.source_name }} />91                </td>92              </tr>93            ))}94          </tbody>95        </table>96      </div>97      {hrefFor ? <Pager total={allRows.length} pageSize={pageSize} page={info.page} hrefFor={hrefFor} label="Approval pages" noun="approval records" /> : allRows.length > rows.length ? <p className="mt-1 text-[12px] text-ink-3">Showing {rows.length} of {fmtInt(allRows.length)} approval records.</p> : null}98    </div>99  );100}101