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 { Badge, ClaimBadge, StatusBadge } from '@/components/ui/badge';6import { SourceBadge } from '@/components/ui/source-badge';7import { recentApprovals } from '@/lib/queries/approvals';8import { fmtDate, toDate } from '@/lib/format';910/**11 * Home module "New oncology approvals": the last dated approval records across every ingested12 * authority (one record = one authority's decision for one application/DIN — never a bare13 * "approved"). Regulatory claim label; source badge per row because authorities differ.14 */15export async function ApprovalsModule({ limit = 8 }: { limit?: number }) {16 const rows = await recentApprovals(limit);17 return (18 <Section19 id="approvals"20 kicker="Regulatory"21 title="New oncology approvals"22 description="Latest dated approval records by authority and jurisdiction. A record is one decision for one application or DIN; Health Canada records do not state indications."23 actions={24 <Link href="/approvals" className="ci-link">25 Approvals feed →26 </Link>27 }28 >29 {rows.length === 0 ? (30 <EmptyState knows={[{ label: 'Drugs', href: '/drugs' }, { label: 'Sources', href: '/sources' }]}>No regulatory approval record has been ingested yet.</EmptyState>31 ) : (32 <>33 <div className="ci-table-wrap">34 <table className="ci-table">35 <thead>36 <tr>37 <th>Date</th>38 <th>Drug</th>39 <th>Authority</th>40 <th>Cancer</th>41 <th>Status</th>42 <th>Source</th>43 </tr>44 </thead>45 <tbody>46 {rows.map((a) => (47 <tr key={a.id}>48 <td className="whitespace-nowrap">{fmtDate(a.approval_date)}</td>49 <td>50 <Link className="ci-link font-medium" href={`/drug/${a.drug_slug}#approvals`}>51 {a.drug_name}52 </Link>53 {a.approval_type ? <span className="ml-1.5 ci-mono text-[11px] text-ink-3">{a.approval_type}</span> : null}54 </td>55 <td className="whitespace-nowrap">56 <span className="text-ink-2">{a.authority}</span> <Badge mono tone="outline">{a.jurisdiction}</Badge>57 </td>58 <td className="text-[12.5px]">59 {a.tumor_agnostic ? <Badge tone="accent">Tumor-agnostic</Badge> : null}60 {a.cancer_slug ? (61 <Link className="ci-link" href={`/cancer/${a.cancer_slug}`}>62 {a.cancer_name}63 </Link>64 ) : !a.tumor_agnostic ? (65 <span className="text-ink-3">not stated in this record</span>66 ) : null}67 </td>68 <td>69 <StatusBadge status={a.status} />70 </td>71 <td>72 <SourceBadge compact p={{ sourceSlug: a.source_slug, sourceName: a.source_name }} />73 </td>74 </tr>75 ))}76 </tbody>77 </table>78 </div>79 <div className="mt-2 flex flex-wrap items-center gap-1.5 text-[12px] text-ink-3">80 <ClaimBadge kind="regulatory" />81 <span>Jurisdiction-specific and time-bound; not a treatment recommendation.</span>82 </div>83 <Freshness dataUpdatedAt={rows.map((r) => toDate(r.updated_at)).filter((d): d is Date => !!d).sort((a, b) => b.getTime() - a.getTime())[0] ?? null} sourceUpdatedAt={rows[0]?.approval_date ?? null} />84 </>85 )}86 </Section>87 );88}89