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.4 KB · 99 lines tsx
Raw Blame History
1import Link from 'next/link';2import { Section, Note } from '@/components/ui/section';3import { EmptyState } from '@/components/ui/empty-state';4import { Freshness } from '@/components/ui/freshness';5import { Badge } from '@/components/ui/badge';6import { ApprovalsTable } from '@/components/data/approvals-table';7import { approvalsForCancer } from '@/lib/queries/drugs';8import { therapyMentionsForCancer } from '@/lib/queries/evidence';9import { loadProvenance } from '@/lib/queries/provenance';10import { fmtInt } from '@/lib/format';11import type { CancerBundle } from '../load';1213export async function DrugsTab({ b, jurisdiction, aPage = 1 }: { b: CancerBundle; jurisdiction: string | null; aPage?: number }) {14  // Therapies mentioned in curated evidence (not approvals) — aggregated in SQL (no evidence rows15  // are shipped to the page), listed separately and never called "approved".16  const [approvals, therapies] = await Promise.all([approvalsForCancer(b.descendants), therapyMentionsForCancer(b.descendants)]);17  const jurisdictions = [...new Set(approvals.map((a) => a.jurisdiction))].sort();18  const selected = jurisdiction && jurisdictions.includes(jurisdiction) ? jurisdiction : null;19  const shownApprovals = selected ? approvals.filter((a) => a.jurisdiction === selected) : approvals;2021  if (approvals.length === 0 && therapies.length === 0) {22    return (23      <Section id="drugs" kicker="Drugs" title="Regulatory approvals and therapies in evidence">24        <EmptyState knows={[{ label: 'Variants & evidence', href: `/cancer/${b.cancer.slug}/evidence` }, { label: 'Trials', href: `/cancer/${b.cancer.slug}/trials` }, { label: 'Drugs index', href: '/drugs' }]}>25          No regulatory approval or evidence-linked therapy is recorded for this entity or its descendants. Approvals are always shown with jurisdiction, authority and indication text — a drug is never marked simply "approved".26        </EmptyState>27      </Section>28    );29  }30  const prov = await loadProvenance(approvals.map((a) => a.provenance_id));31  return (32    <div className="space-y-8">33      <Section id="approvals" kicker="Regulatory" title="Approvals by jurisdiction" description={approvals.length ? `${fmtInt(approvals.length)} approval records across ${jurisdictions.length} jurisdiction${jurisdictions.length === 1 ? '' : 's'}, including tumor-agnostic approvals.` : undefined}>34        {approvals.length ? (35          <>36            <nav aria-label="Jurisdiction" className="mb-3 flex flex-wrap gap-1.5 text-[12.5px]">37              <Link href={`/cancer/${b.cancer.slug}/drugs`} aria-current={!selected ? 'page' : undefined} className="ci-chip">38                All jurisdictions39              </Link>40              {jurisdictions.map((j) => (41                <Link key={j} href={`/cancer/${b.cancer.slug}/drugs?jurisdiction=${encodeURIComponent(j)}`} aria-current={selected === j ? 'page' : undefined} className="ci-chip ci-mono">42                  {j}43                </Link>44              ))}45            </nav>46            <ApprovalsTable rows={shownApprovals} prov={prov} page={aPage} hrefFor={(p) => `/cancer/${b.cancer.slug}/drugs?${new URLSearchParams({ ...(selected ? { jurisdiction: selected } : {}), ...(p > 1 ? { aPage: String(p) } : {}) }).toString()}#approvals`} />47            <Freshness dataUpdatedAt={approvals.reduce((m, a) => (a.updated_at > m ? a.updated_at : m), approvals[0]!.updated_at)} />48          </>49        ) : (50          <EmptyState compact>No regulatory approval recorded for this entity yet.</EmptyState>51        )}52      </Section>5354      <Section id="therapies-in-evidence" kicker="Curated evidence" title="Therapies appearing in curated evidence" description="Counts of CIViC evidence items mentioning each therapy for this entity or its descendants. Presence here is not an approval.">55        {therapies.length ? (56          <div className="ci-table-wrap">57            <table className="ci-table">58              <thead>59                <tr>60                  <th scope="col">Therapy</th>61                  <th scope="col" className="num">62                    Evidence items (count)63                  </th>64                  <th scope="col" className="num">65                    Sensitivity / response66                  </th>67                  <th scope="col" className="num">68                    Resistance69                  </th>70                </tr>71              </thead>72              <tbody>73                {therapies.map((t) => (74                  <tr key={t.slug}>75                    <td>76                      <Link className="ci-link" href={`/drug/${t.slug}`}>77                        {t.name}78                      </Link>79                    </td>80                    <td className="num">{fmtInt(t.n)}</td>81                    <td className="num">{fmtInt(t.sensitivity)}</td>82                    <td className="num">{fmtInt(t.resistance)}</td>83                  </tr>84                ))}85              </tbody>86            </table>87          </div>88        ) : (89          <EmptyState compact>No therapy appears in curated evidence for this entity.</EmptyState>90        )}91        <p className="mt-2 text-[12px] text-ink-3">92          <Badge>Curated</Badge> Items are counted regardless of level or direction; see the evidence tab for the detail.93        </p>94      </Section>95      <Note tone="warn">Regulatory status is jurisdiction-specific and changes over time. This page is not treatment guidance.</Note>96    </div>97  );98}99