spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import { Section } from '@/components/ui/section';2import { EmptyState } from '@/components/ui/empty-state';3import { Freshness } from '@/components/ui/freshness';4import { ClaimBadge } from '@/components/ui/badge';5import { SourceBadge, TableProvenance } from '@/components/ui/source-badge';6import { Pager } from '@/components/ui/pager';7import { PublicationList } from '@/components/data/publication-list';8import { literatureCountsFor, recentPublicationsFor, recentPublicationsForCount, PUBLICATION_PAGE_SIZE } from '@/lib/queries/publications';9import { loadProvenance, toInfo } from '@/lib/queries/provenance';10import { fmtInt, isoDate } from '@/lib/format';11import { pageInfo } from '@/lib/pagination';12import type { CancerBundle } from '../load';1314const WINDOW_LABEL: Record<string, string> = { all: 'All time', '10y': 'Last 10 years', '5y': 'Last 5 years', '5y_prior': 'Preceding 5-year window', '12m': 'Last 12 months' };1516export async function ResearchTab({ b, pPage = 1 }: { b: CancerBundle; pPage?: number }) {17 const [counts, pubTotal] = await Promise.all([literatureCountsFor(b.cancer.id), recentPublicationsForCount('cancer', b.descendants)]);18 const info = pageInfo(pPage, PUBLICATION_PAGE_SIZE, pubTotal);19 const [pubs, prov] = await Promise.all([pubTotal > 0 ? recentPublicationsFor('cancer', b.descendants, { page: info.page, pageSize: info.pageSize }) : Promise.resolve([]), loadProvenance(counts.map((c) => c.provenance_id))]);20 const countsProv = counts[0] ? toInfo(prov.get(counts[0].provenance_id), 'derived') : null;21 const hrefFor = (p: number) => `/cancer/${b.cancer.slug}/research${p > 1 ? `?pPage=${p}` : ''}#recent-publications`;22 return (23 <div className="space-y-8">24 <Section id="literature-counts" kicker="Research activity" title="PubMed record counts" description="Each count is the number of PubMed records returned by the exact query shown, at the time it was run. Counts are research-activity signals, not quality measures.">25 {counts.length ? (26 <>27 <TableProvenance p={countsProv ?? { sourceSlug: 'pubmed', sourceName: 'PubMed', layer: 'derived' }} claim={<ClaimBadge kind="computed" />}>28 {counts.length} window{counts.length === 1 ? '' : 's'} · counts computed by CancerIndex from PubMed E-utilities; the exact query is stored with each count.29 </TableProvenance>30 <div className="ci-table-wrap">31 <table className="ci-table">32 <thead>33 <tr>34 <th scope="col">Window</th>35 <th scope="col">Period</th>36 <th scope="col" className="num">37 Records (count)38 </th>39 <th scope="col">Exact query</th>40 <th scope="col">Computed</th>41 <th scope="col">Source</th>42 </tr>43 </thead>44 <tbody>45 {counts.map((c) => (46 <tr key={c.id}>47 <td>{WINDOW_LABEL[c.window_key] ?? c.window_key}</td>48 <td className="whitespace-nowrap text-[12.5px] text-ink-3">{c.window_start || c.window_end ? `${c.window_start ?? '…'} → ${c.window_end ?? '…'}` : '—'}</td>49 <td className="num font-medium">{fmtInt(c.count)}</td>50 <td>51 <code className="ci-mono block max-w-[520px] whitespace-pre-wrap break-words text-[11.5px] text-ink-2">{c.query}</code>52 </td>53 <td className="whitespace-nowrap text-[12.5px]">{isoDate(c.updated_at)}</td>54 <td>55 <SourceBadge compact title={null} p={toInfo(prov.get(c.provenance_id), 'derived') ?? { sourceSlug: 'pubmed', sourceName: 'PubMed' }} />56 </td>57 </tr>58 ))}59 </tbody>60 </table>61 </div>62 <Freshness dataUpdatedAt={counts[0]!.updated_at} extra="source: pubmed E-utilities" />63 </>64 ) : (65 <EmptyState knows={[{ label: 'Trials tab', href: `/cancer/${b.cancer.slug}/trials` }, { label: 'Methodology: publication counts', href: '/methodology#publications_5y' }]}>66 No PubMed query has been run for this entity. Counts require a MeSH-anchored query built from the entity's terminology mappings; the query string is stored with each count so it can be reproduced.67 </EmptyState>68 )}69 </Section>70 <Section id="recent-publications" kicker="Literature" title={`Linked publications (${fmtInt(pubTotal)})`} description={`Publications linked to this entity or its descendants, newest first, ${PUBLICATION_PAGE_SIZE} per page. Link method and validation status are shown; 'candidate' links have not been reviewed.`}>71 {pubs.length ? (72 <>73 <PublicationList74 rows={pubs}75 summary={76 <>77 Showing {fmtInt(info.from)}–{fmtInt(info.to)} of {fmtInt(pubTotal)} publications78 </>79 }80 />81 <Pager total={pubTotal} pageSize={PUBLICATION_PAGE_SIZE} page={info.page} hrefFor={hrefFor} label="Publication pages" noun="publications" />82 </>83 ) : (84 <EmptyState compact>No publication is linked to this entity yet.</EmptyState>85 )}86 </Section>87 </div>88 );89}90