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.4 KB · 77 lines tsx
Raw Blame History
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 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 choropleth 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>37          <div className="min-w-0">38            <div className="ci-table-wrap">39              <table className="ci-table" id="trial-map-top">40                <thead>41                  <tr>42                    <th className="num">#</th>43                    <th>Country</th>44                    <th className="num">Recruiting sites</th>45                    <th className="num">Trials</th>46                  </tr>47                </thead>48                <tbody>49                  {rows.slice(0, topN).map((r, i) => (50                    <tr key={r.country}>51                      <td className="num">{i + 1}</td>52                      <td>53                        <Link href={data[i]!.href} className="ci-link">54                          {r.country}55                        </Link>56                      </td>57                      <td className="num">{fmtInt(r.sites)}</td>58                      <td className="num">{fmtInt(r.trials)}</td>59                    </tr>60                  ))}61                </tbody>62              </table>63            </div>64            <p className="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11.5px] text-ink-3">65              <ClaimBadge kind="computed" />66              <span>67                {fmtInt(totalSites)} recruiting sites · {fmtInt(rows.length)} countries · <span className="ci-mono">{rows[0]?.formula_version}</span> · source: clinicaltrials68              </span>69            </p>70            <Freshness dataUpdatedAt={rows[0]?.computed_at ?? null} extra="registrant-entered locations" />71          </div>72        </div>73      )}74    </Section>75  );76}77