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.3 KB · 87 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { PageHeader } from '@/components/ui/section';4import { EmptyState } from '@/components/ui/empty-state';5import { Badge } from '@/components/ui/badge';6import { Pagination } from '@/components/ui/pagination';7import { listGenes } from '@/lib/queries/genomics';8import { fmtInt } from '@/lib/format';9import { str, int, bool, withParams, type SP } from '@/lib/search-params';1011export const metadata: Metadata = { title: 'Genes', description: 'HGNC genes with curated cancer evidence, variants and cohort frequencies.' };12// Filtered list: rendered per request (searchParams); 600 s is the CDN/ISR hint shared by all list pages.13export const revalidate = 600;14const PAGE_SIZE = 50;1516export default async function GenesPage({ searchParams }: { searchParams: Promise<SP> }) {17  const sp = await searchParams;18  const q = str(sp, 'q');19  const cancerOnly = bool(sp, 'cancer') ?? false;20  const page = int(sp, 'page', 1, 1, 100_000);21  const { rows, total } = await listGenes({ q, cancerOnly, page, pageSize: PAGE_SIZE });22  const href = (o: Record<string, string | number | null | undefined>) => `/genes${withParams({ q, cancer: cancerOnly ? '1' : '' }, o)}`;23  return (24    <div>25      <PageHeader kicker="Genes" title="Genes" lede="Gene symbols and names follow HGNC. 'Cancer gene' means the gene has at least one curated cancer edge — a derived flag, not a biological verdict." />26      <form method="get" action="/genes" className="flex flex-wrap items-end gap-2 border-y border-rule py-3 text-[13.5px]">27        <label className="flex flex-col gap-1">28          <span className="ci-kicker">Symbol, alias or name</span>29          <input name="q" defaultValue={q} placeholder="e.g. KRAS, HER2, tumor protein" className="border border-rule-strong bg-white px-2 py-1.5 outline-none focus:border-accent" />30        </label>31        <label className="inline-flex items-center gap-1 pb-2">32          <input type="checkbox" name="cancer" value="1" defaultChecked={cancerOnly} /> Cancer genes only33        </label>34        <button type="submit" className="border border-ink bg-ink px-3 py-1.5 text-paper hover:bg-ink-2">35          Apply36        </button>37      </form>38      <p className="mt-3 text-[13px] text-ink-2">39        <span className="ci-num font-medium text-ink">{fmtInt(total)}</span> genes40      </p>41      {rows.length === 0 ? (42        <div className="mt-3">43          <EmptyState title={total === 0 && !q ? 'Genes not yet available' : 'No gene matches'} knows={[{ label: 'Cancers explorer', href: '/cancers' }, { label: 'Sources', href: '/sources' }]}>44            {total === 0 && !q ? 'The HGNC connector has not run on this environment. Genes appear here once ingested, with CIViC evidence and GDC cohort frequencies attached.' : 'Try a different symbol or alias.'}45          </EmptyState>46        </div>47      ) : (48        <>49          <div className="ci-table-wrap mt-3">50            <table className="ci-table">51              <thead>52                <tr>53                  <th>Symbol</th>54                  <th>Name</th>55                  <th>Location</th>56                  <th>Locus type</th>57                  <th className="num">Evidence items</th>58                  <th className="num">Variants</th>59                  <th>Flags</th>60                </tr>61              </thead>62              <tbody>63                {rows.map((g) => (64                  <tr key={g.id}>65                    <td>66                      <Link className="ci-mono ci-link font-medium" href={`/gene/${g.symbol}`}>67                        {g.symbol}68                      </Link>69                    </td>70                    <td className="min-w-[220px]">{g.name ?? '—'}</td>71                    <td className="ci-mono text-[12px]">{g.location ?? '—'}</td>72                    <td className="text-[12.5px]">{g.locus_type ?? '—'}</td>73                    <td className="num">{fmtInt(g.evidence_count ?? 0)}</td>74                    <td className="num">{fmtInt(g.variant_count ?? 0)}</td>75                    <td>{g.is_cancer_gene ? <Badge tone="accent">Cancer gene</Badge> : null}</td>76                  </tr>77                ))}78              </tbody>79            </table>80          </div>81          <Pagination page={page} pageSize={PAGE_SIZE} total={total} hrefFor={(p) => href({ page: p === 1 ? '' : p })} />82        </>83      )}84    </div>85  );86}87