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%
4.2 KB · 78 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 { FrequencyTables } from '@/components/data/frequency-table';5import { cohortsForCancer, frequenciesForCancer } from '@/lib/queries/genomics';6import { loadProvenance } from '@/lib/queries/provenance';7import { fmtInt } from '@/lib/format';8import type { CancerBundle } from '../load';910/**11 * Cohort selector renders ONE cohort by default — the one with the largest number of cases12 * profiled — plus an explicit "All cohorts" option (`?cohort=all`). Cohorts are never pooled.13 */14export async function GenomicsTab({ b, cohort }: { b: CancerBundle; cohort: string | null }) {15  const cohorts = await cohortsForCancer(b.descendants);16  if (cohorts.length === 0) {17    return (18      <Section id="genomics" kicker="Genomics" title="Gene alteration frequencies by cohort">19        <EmptyState knows={[{ label: 'Variants & evidence', href: `/cancer/${b.cancer.slug}/evidence` }, { label: 'Genes index', href: '/genes' }]}>20          No genomic cohort is mapped to this entity or its descendants. Frequencies come from open-access cohorts (GDC projects) and always carry their denominator (cases profiled); they are never pooled across cohorts.21        </EmptyState>22      </Section>23    );24  }25  const withRows = cohorts.filter((c) => c.frequency_rows > 0);26  const defaultCohort = (withRows[0] ?? cohorts[0])!;27  const showAll = cohort === 'all';28  const selected = showAll ? null : cohorts.find((c) => c.cohort_id === cohort || c.study_id === cohort) ?? defaultCohort;29  const rows = await frequenciesForCancer(b.descendants, { cohortId: selected?.cohort_id ?? null });30  const prov = await loadProvenance(rows.map((r) => r.provenance_id));31  const base = `/cancer/${b.cancer.slug}/genomics`;32  const hrefFor = (c: (typeof cohorts)[number]) => (c.cohort_id === defaultCohort.cohort_id ? base : `${base}?cohort=${encodeURIComponent(c.study_id)}`);3334  return (35    <div className="space-y-6">36      <Section37        id="genomics"38        kicker="Genomics"39        title="Gene alteration frequencies by cohort"40        description={`${fmtInt(cohorts.length)} cohort${cohorts.length === 1 ? '' : 's'} mapped to this entity or its descendants. Each frequency is affected / profiled within one cohort; cohorts are shown separately because case selection, sequencing depth and definitions differ. By default the cohort with the most cases profiled is shown.`}41      >42        <nav aria-label="Cohort" className="mb-4">43          <ul className="m-0 flex list-none flex-wrap gap-1.5 p-0 text-[12.5px]">44            {cohorts.map((c) => {45              const isCurrent = !showAll && selected?.cohort_id === c.cohort_id;46              return (47                <li key={c.cohort_id}>48                  <Link href={hrefFor(c)} aria-current={isCurrent ? 'page' : undefined} className="ci-chip" title={`${c.cohort_name} · ${fmtInt(c.cases_profiled)} cases profiled · ${fmtInt(c.frequency_rows)} gene rows`}>49                    {c.study_id}50                    <span className="ci-num ml-1 text-ink-3">{fmtInt(c.cases_profiled)}</span>51                    {c.cancer_name && c.cancer_id !== b.cancer.id ? <span className="ml-1 text-ink-3">({c.cancer_name})</span> : null}52                  </Link>53                </li>54              );55            })}56            {cohorts.length > 1 ? (57              <li>58                <Link href={`${base}?cohort=all`} aria-current={showAll ? 'page' : undefined} className="ci-chip">59                  All cohorts60                </Link>61              </li>62            ) : null}63          </ul>64          <p className="mt-1 text-[11.5px] text-ink-3">Chip number = cases profiled in that cohort.</p>65        </nav>66        {rows.length ? (67          <FrequencyTables rows={rows} prov={prov} showCancer />68        ) : (69          <EmptyState compact title="No frequency rows for this cohort">70            The cohort is mapped to this entity but no gene frequency has been ingested for it yet.71          </EmptyState>72        )}73      </Section>74      <Note>Cohorts mapped to a descendant (e.g. a subtype) are included because their cases belong to this entity by definition; the mapping and its match type are shown per cohort.</Note>75    </div>76  );77}78