SPB Git

spb/cancerindex

Public
6.5 KB · 119 lines tsx
1import Link from 'next/link';2import { ExternalLink } from 'lucide-react';3import { Badge } from '@/components/ui/badge';4import { Breadcrumbs } from '@/components/layout/breadcrumbs';5import { Tabs } from '@/components/ui/tabs';6import { CODE_SYSTEM_LABEL, codeUrl } from '@/lib/site';7import { humanize, fmtInt } from '@/lib/format';8import { TABS, type CancerBundle, type TabKey } from './load';910const PRIMARY_SYSTEMS = ['ncit', 'oncotree', 'icd10', 'icd10cm', 'doid', 'umls', 'mesh', 'mondo'];1112export function CancerHeader({ b, tab }: { b: CancerBundle; tab: TabKey }) {13  const { cancer: c, counters, codes, aliases, path } = b;14  const abbreviations = aliases.filter((a) => a.alias_type === 'abbreviation').map((a) => a.alias);15  const shown = codes.filter((k) => PRIMARY_SYSTEMS.includes(k.system)).sort((a, b2) => PRIMARY_SYSTEMS.indexOf(a.system) - PRIMARY_SYSTEMS.indexOf(b2.system));16  const otherCodes = codes.length - shown.length;1718  const badges: Array<{ label: string; tone?: 'neutral' | 'accent' | 'warn' | 'outline'; title: string }> = [];19  if (c.rare_cancer === true) badges.push({ label: 'Rare', tone: 'accent', title: 'Flagged rare from incidence data (rule: age-standardized incidence < 6 per 100,000)' });20  if (c.pediatric_relevant) badges.push({ label: 'Pediatric', title: 'Pediatric-relevant entity (NCIt childhood neoplasm branch)' });21  if (c.hematologic) badges.push({ label: 'Hematologic', title: 'Hematologic malignancy' });22  if (!c.malignant) badges.push({ label: c.entity_type === 'precursor_condition' ? 'Precursor' : 'Non-malignant', tone: 'outline', title: 'Not a malignant neoplasm; kept for taxonomy completeness' });23  if (c.top_level) badges.push({ label: 'Top-level', tone: 'outline', title: 'Member of the mutually exclusive global ranking set' });24  if (counters) {25    if (counters.active_trial_count > 0) badges.push({ label: `${fmtInt(counters.active_trial_count)} active trials`, tone: 'outline', title: 'From ClinicalTrials.gov, over descendants' });26    if (counters.approved_drug_count > 0) badges.push({ label: `${fmtInt(counters.approved_drug_count)} approved drugs`, tone: 'outline', title: 'Drugs with at least one approval in any jurisdiction' });27    if (counters.evidence_count > 0) badges.push({ label: `${fmtInt(counters.evidence_count)} evidence items`, tone: 'outline', title: 'Accepted CIViC evidence items' });28  }2930  const crumbs = [{ label: 'Cancers', href: '/cancers' }, ...path.slice(0, -1).map((p) => ({ label: p.canonical_name, href: `/cancer/${p.slug}` })), { label: c.canonical_name }];3132  return (33    <header className="pt-5">34      <Breadcrumbs items={crumbs} />35      {b.redirectedFrom ? (36        <p className="mt-2 border-l-2 border-warn pl-3 text-[12.5px] text-warn">37          <span className="ci-mono">{b.redirectedFrom.id}</span> ({b.redirectedFrom.canonical_name}) was merged into this entity.38        </p>39      ) : null}40      {c.status === 'deprecated' ? <p className="mt-2 border-l-2 border-danger pl-3 text-[12.5px] text-danger">Deprecated entity{c.deprecated_reason ? `: ${c.deprecated_reason}` : ''}. Kept for identifier stability.</p> : null}41      <div className="mt-3 flex flex-wrap items-start justify-between gap-4">42        <div className="min-w-0">43          <p className="ci-kicker mb-1">{humanize(c.entity_type)}</p>44          <h1 className="text-3xl leading-tight sm:text-[40px]">{c.canonical_name}</h1>45          <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-[13px] text-ink-3">46            <span className="ci-mono text-ink-2">{c.id}</span>47            {c.short_name && c.short_name !== c.canonical_name ? <span>{c.short_name}</span> : null}48            {abbreviations.length ? <span>{abbreviations.slice(0, 6).join(' · ')}</span> : null}49          </p>50          {badges.length ? (51            <div className="mt-2 flex flex-wrap gap-1.5">52              {badges.map((bd) => (53                <Badge key={bd.label} tone={bd.tone ?? 'neutral'} title={bd.title}>54                  {bd.label}55                </Badge>56              ))}57            </div>58          ) : null}59        </div>60        <dl className="grid shrink-0 grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-[12.5px]">61          {shown.map((k) => {62            const url = codeUrl(k.system, k.code);63            return (64              <div key={`${k.system}-${k.code}`} className="contents">65                <dt className="text-ink-3">{CODE_SYSTEM_LABEL[k.system] ?? k.system}</dt>66                <dd>67                  {url ? (68                    <a href={url} target="_blank" rel="noopener noreferrer" className="ci-mono inline-flex items-center gap-1 border border-rule px-1.5 py-[1px] text-ink no-underline hover:border-accent hover:text-accent" title={`Open ${k.code} in the ${CODE_SYSTEM_LABEL[k.system] ?? k.system} browser`}>69                      {k.code} <ExternalLink className="h-3 w-3" aria-hidden />70                    </a>71                  ) : (72                    <span className="ci-mono border border-rule px-1.5 py-[1px]">{k.code}</span>73                  )}74                </dd>75              </div>76            );77          })}78          {otherCodes > 0 ? (79            <div className="contents">80              <dt className="text-ink-3">Other</dt>81              <dd>82                <Link href={`/cancer/${c.slug}/sources`} className="ci-link">83                  {otherCodes} more {otherCodes === 1 ? 'code' : 'codes'}84                </Link>85              </dd>86            </div>87          ) : null}88          {shown.length === 0 && otherCodes === 0 ? <dd className="col-span-2 text-ink-4">No cross-reference codes yet</dd> : null}89        </dl>90      </div>91      <div className="mt-4">92        <Tabs93          ariaLabel="Entity sections"94          current={tab}95          tabs={TABS.map((t) => ({96            key: t.key,97            label: t.label,98            href: t.key === 'overview' ? `/cancer/${c.slug}` : `/cancer/${c.slug}/${t.key}`,99            count:100              t.key === 'trials'101                ? counters?.trial_count102                : t.key === 'evidence'103                  ? counters?.evidence_count104                  : t.key === 'drugs'105                    ? counters?.drug_count106                    : t.key === 'statistics'107                      ? counters?.epidemiology_obs_count108                      : t.key === 'survival'109                        ? counters?.survival_obs_count110                        : t.key === 'genomics'111                          ? counters?.cohort_count112                          : null,113          }))}114        />115      </div>116    </header>117  );118}119
Files · 602dc111f3dd7d7dec082e01832909f07dca7bd3
Menu

Theme