spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import Link from 'next/link';2import { Section } from '@/components/ui/section';3import { EmptyState } from '@/components/ui/empty-state';4import { Freshness } from '@/components/ui/freshness';5import { ClaimBadge } from '@/components/ui/badge';6import { topPhase3Recruiting } from '@/lib/queries/trial-intelligence';7import { fmtInt, fmtPct, toDate } from '@/lib/format';8import { fmtGrowth } from '@/lib/trial-intel';910/**11 * Home module "Clinical trial intelligence" (SPEC §10): the top-level cancers with the most recruiting12 * Phase III interventional studies, with registration growth and industry share. Every figure comes from13 * `trial_intelligence` (computed, formula-versioned); nothing is shown until the layer has been computed.14 */15export async function TrialIntelModule({ limit = 8 }: { limit?: number }) {16 const rows = await topPhase3Recruiting(limit);17 const computedAt = rows.reduce<Date | null>((m, r) => {18 const d = toDate(r.computed_at);19 return d && (!m || d > m) ? d : m;20 }, null);21 return (22 <Section23 id="trial-intel"24 kicker="Clinical trials"25 title="Clinical trial intelligence"26 description="Top-level cancers with the most recruiting Phase III interventional studies, with the year-over-year change in registrations. Counts aggregate a cancer and its NCIt descendants; statuses and phases are as posted on ClinicalTrials.gov."27 actions={28 <Link href="/trials/intelligence" className="ci-link">29 All measures →30 </Link>31 }32 >33 {rows.length === 0 ? (34 <EmptyState title="Trial intelligence not yet computed">The per-cancer trial measures are derived from ClinicalTrials.gov records after each ingest. Nothing is shown until that computation has run.</EmptyState>35 ) : (36 <>37 <div className="ci-table-wrap">38 <table className="ci-table">39 <thead>40 <tr>41 <th className="num">#</th>42 <th>Cancer</th>43 <th className="num" title="Recruiting interventional studies with PHASE3 among their phases">44 Ph III recruiting45 </th>46 <th className="num" title="Active interventional studies (recruiting, not yet recruiting, enrolling by invitation, active not recruiting)">47 Active48 </th>49 <th className="num" title="(studies first posted in the last 12 months − preceding 12 months) / preceding; null under 20 studies in the preceding window">50 Growth YoY51 </th>52 <th className="num" title="Share of active studies led by an industry sponsor">53 Industry54 </th>55 </tr>56 </thead>57 <tbody>58 {rows.map((r, i) => (59 <tr key={r.cancer_id}>60 <td className="num">{i + 1}</td>61 <td>62 <Link className="ci-link" href={`/cancer/${r.cancer_slug}/trials`}>63 {r.cancer_name}64 </Link>65 </td>66 <td className="num font-medium">{fmtInt(r.phase3_recruiting)}</td>67 <td className="num">{fmtInt(r.active_trials)}</td>68 <td className={`num ${r.trial_growth_yoy != null && r.trial_growth_yoy > 0 ? 'text-ok' : r.trial_growth_yoy != null && r.trial_growth_yoy < 0 ? 'text-danger' : ''}`} title={`${fmtInt(r.new_trials_12m)} first posted in the last 12 months vs ${fmtInt(r.new_trials_prior_12m)} in the preceding 12 months`}>69 {fmtGrowth(r.trial_growth_yoy)}70 </td>71 <td className="num">{fmtPct(r.industry_share, 0)}</td>72 </tr>73 ))}74 </tbody>75 </table>76 </div>77 <p className="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11.5px] text-ink-3">78 <ClaimBadge kind="computed" />79 <span>80 formula <span className="ci-mono">{rows[0]?.formula_version}</span> · interventional studies · descendants included81 </span>82 <span>source: clinicaltrials</span>83 <Link className="ci-link" href="/trials/terminated">84 Terminated studies85 </Link>86 </p>87 <Freshness dataUpdatedAt={computedAt} extra="computed by CancerIndex from ClinicalTrials.gov records" />88 </>89 )}90 </Section>91 );92}93