SPB Git

spb/cancerindex

Public
3.9 KB · 80 lines tsx
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 { WorldMap, type MapCountryDatum } from '@/components/charts/world-map';7import { countryCounts } from '@/lib/queries/trial-sites';8import { fmtInt } from '@/lib/format';910/**11 * Home module: compact choropleth of RECRUITING trial sites per country, all cancers, any phase12 * (precomputed by `pnpm cix intel`). Links to the full map with filters and its data table. The13 * country list under the map keeps the module readable without colour or hover.14 */15export async function TrialMapModule({ topN = 8 }: { topN?: number }) {16  const rows = await countryCounts({ cancerId: null, phase: null, recruitingOnly: true });17  const totalSites = rows.reduce((s, r) => s + r.sites, 0);18  const data: MapCountryDatum[] = rows.map((r) => ({ country: r.country, iso3: r.iso3, sites: r.sites, trials: r.trials, href: `/trials?country=${encodeURIComponent(r.country)}&status=RECRUITING` }));19  return (20    <Section21      id="trial-map"22      kicker="Where trials recruit"23      title="Recruiting trial sites by country"24      description="Study locations with status RECRUITING (or in a recruiting study when the site has no status), all cancers, any phase. A site is one registrant-entered location; multi-site studies weigh by their number of sites."25      actions={26        <Link href="/trials/map?recruiting=1" className="ci-link">27          Full map and filters →28        </Link>29      }30    >31      {rows.length === 0 ? (32        <EmptyState title="Trial map not yet computed" compact>33          Country aggregates appear after <code className="ci-mono">pnpm cix intel</code> has run on ingested ClinicalTrials.gov locations.34        </EmptyState>35      ) : (36        <div className="grid gap-4 lg:grid-cols-[3fr_2fr]">37          <div className="min-w-0">38            <WorldMap data={data} metric="sites" compact ariaLabel={`World map of recruiting clinical trial sites per country, all cancers: ${fmtInt(totalSites)} sites in ${fmtInt(rows.length)} countries. The list beside the map gives the leading countries; the full table is on the trial map page.`} describedBy="trial-map-top" />39          </div>40          <div className="min-w-0">41            <div className="ci-table-wrap">42              <table className="ci-table" id="trial-map-top">43                <thead>44                  <tr>45                    <th className="num">#</th>46                    <th>Country</th>47                    <th className="num">Recruiting sites</th>48                    <th className="num">Trials</th>49                  </tr>50                </thead>51                <tbody>52                  {rows.slice(0, topN).map((r, i) => (53                    <tr key={r.country}>54                      <td className="num">{i + 1}</td>55                      <td>56                        <Link href={data[i]!.href} className="ci-link">57                          {r.country}58                        </Link>59                      </td>60                      <td className="num">{fmtInt(r.sites)}</td>61                      <td className="num">{fmtInt(r.trials)}</td>62                    </tr>63                  ))}64                </tbody>65              </table>66            </div>67            <p className="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11.5px] text-ink-3">68              <ClaimBadge kind="computed" />69              <span>70                {fmtInt(totalSites)} recruiting sites · {fmtInt(rows.length)} countries · <span className="ci-mono">{rows[0]?.formula_version}</span> · source: clinicaltrials71              </span>72            </p>73            <Freshness dataUpdatedAt={rows[0]?.computed_at ?? null} extra="registrant-entered locations" />74          </div>75        </div>76      )}77    </Section>78  );79}80
Files · 33aae9564b414c377acdc2de5ccf643cd1f18285
Menu

Theme