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%
3.5 KB · 80 lines tsx
Raw Blame History
1import Link from 'next/link';2import type { ReactNode } from 'react';3import { Badge, ClaimBadge, StatusBadge } from '@/components/ui/badge';4import { SourceBadge, TableProvenance, type ProvenanceInfo } from '@/components/ui/source-badge';5import type { TrialListRow } from '@/lib/queries/trials';6import { fmtDate, fmtInt, phaseLabel } from '@/lib/format';78const CT: ProvenanceInfo = { sourceSlug: 'clinicaltrials', sourceName: 'ClinicalTrials.gov', dataset: 'ClinicalTrials.gov API v2 studies', layer: 'normalized' };910/**11 * Dense trial table. All rows come from one registry, so the provenance popover is rendered once in12 * the caption (with the latest retrieval date of the page) and each row carries a compact badge.13 */14export function TrialTable({ rows, summary }: { rows: TrialListRow[]; summary?: ReactNode }) {15  const latest = rows.reduce<Date | string | null>((m, t) => (m == null || String(t.updated_at) > String(m) ? t.updated_at : m), null);16  return (17    <div>18      <TableProvenance p={{ ...CT, retrievedAt: latest }} claim={<ClaimBadge kind="published" />}>19        {summary ?? `${fmtInt(rows.length)} stud${rows.length === 1 ? 'y' : 'ies'}`} · status and phase as posted by the registrant; each row links to the study record.20      </TableProvenance>21      <div className="ci-table-wrap">22        <table className="ci-table">23          <thead>24            <tr>25              <th scope="col">NCT</th>26              <th scope="col">Title</th>27              <th scope="col">Status</th>28              <th scope="col">Phase</th>29              <th scope="col" className="num">30                Enrollment (n)31              </th>32              <th scope="col">Sponsor</th>33              <th scope="col" className="num">34                Countries35              </th>36              <th scope="col">Last update</th>37              <th scope="col">Source</th>38            </tr>39          </thead>40          <tbody>41            {rows.map((t) => (42              <tr key={t.id}>43                <td>44                  <Link href={`/trial/${t.nct_id}`} className="ci-mono ci-link">45                    {t.nct_id}46                  </Link>47                </td>48                <td className="min-w-[280px] max-w-[520px]">49                  <Link href={`/trial/${t.nct_id}`} className="text-ink no-underline hover:text-accent">50                    {t.brief_title}51                  </Link>52                  {t.acronym ? <span className="ml-1 text-[12px] text-ink-3">({t.acronym})</span> : null}53                </td>54                <td>55                  <StatusBadge status={t.overall_status} />56                </td>57                <td className="whitespace-nowrap">{t.phases.length ? t.phases.map(phaseLabel).join(' / ') : <span className="text-ink-4">—</span>}</td>58                <td className="num">{t.enrollment_count == null ? '—' : fmtInt(t.enrollment_count)}</td>59                <td className="max-w-[220px] truncate text-[12.5px]" title={t.lead_sponsor ?? ''}>60                  {t.lead_sponsor ?? '—'}61                  {t.lead_sponsor_class ? (62                    <Badge tone="outline" className="ml-1">63                      {t.lead_sponsor_class}64                    </Badge>65                  ) : null}66                </td>67                <td className="num">{t.countries.length}</td>68                <td className="whitespace-nowrap text-[12.5px]">{fmtDate(t.last_update_posted_date)}</td>69                <td>70                  <SourceBadge compact title={null} p={CT} />71                </td>72              </tr>73            ))}74          </tbody>75        </table>76      </div>77    </div>78  );79}80