spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
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