SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
5 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
3.7 KB · 67 lines tsx
Raw Blame History
1"use client";23import type { ExploreResponse, FacilityStatus } from "@dci/core";4import { BarList, ColumnChart } from "@/components/ui/charts";5import { HonestEmpty } from "@/components/intelligence/shared";6import { fmtInt, fmtMw } from "@/lib/format";7import { STATUS_SHORT } from "@/lib/labels";89/** byStatus / byCountry / byYear charts over the whole filtered set (not the current page). */10export function ExploreCharts({ charts, entity, countryHrefBase }: { charts: ExploreResponse["charts"]; entity: "facilities" | "projects"; /** href prefix the iso2 is appended to (function props cannot cross the RSC boundary) */ countryHrefBase: string }) {11  const countryHref = (iso2: string) => `${countryHrefBase}${encodeURIComponent(iso2)}`;12  const byStatus = charts.byStatus.map((s) => ({ label: STATUS_SHORT[s.key as FacilityStatus] ?? s.key, value: s.count, secondary: s.mw }));13  const byYear = charts.byYear.filter((y) => y.year >= 1990 && y.year <= 2100).map((y) => ({ label: String(y.year), value: y.count, secondary: y.mw }));14  const byCountry = charts.byCountry.slice(0, 15);15  const mwTotal = charts.byStatus.reduce((a, s) => a + (s.mw ?? 0), 0);16  return (17    <div className="grid gap-8 lg:grid-cols-2">18      <div>19        <div className="mb-2 flex items-baseline justify-between">20          <h2 className="text-[13.5px] font-semibold text-ink">By status</h2>21          <span className="figure text-[11.5px] text-ink-3">records{mwTotal > 0 ? ` · ${fmtMw(mwTotal)} published` : ""}</span>22        </div>23        {byStatus.length ? (24          <div className="pt-8">25            <ColumnChart data={byStatus} format={(v) => fmtInt(v)} height={160} ariaLabel="Records by lifecycle status" />26          </div>27        ) : (28          <HonestEmpty title="No status distribution" />29        )}30        {byStatus.length > 0 && (31          <ul className="mt-2 grid grid-cols-2 gap-x-4 gap-y-0.5 text-[11.5px] text-ink-3 sm:grid-cols-3">32            {charts.byStatus.map((s) => (33              <li key={s.key} className="flex justify-between gap-2">34                <span>{STATUS_SHORT[s.key as FacilityStatus] ?? s.key}</span>35                <span className="figure">36                  {fmtInt(s.count)} · {s.mw === null ? "MW —" : fmtMw(s.mw)}37                </span>38              </li>39            ))}40          </ul>41        )}42      </div>43      <div>44        <div className="mb-2 flex items-baseline justify-between">45          <h2 className="text-[13.5px] font-semibold text-ink">{entity === "projects" ? "By expected opening year" : "By opening year"}</h2>46          <span className="figure text-[11.5px] text-ink-3">records</span>47        </div>48        {byYear.length ? (49          <div className="pt-8">50            <ColumnChart data={byYear} format={(v) => fmtInt(v)} height={160} color="var(--ink-2)" ariaLabel="Records by year" />51          </div>52        ) : (53          <HonestEmpty title="No dated records" detail={entity === "projects" ? "no expected opening published" : "no opening date published in this set"} />54        )}55        <p className="mt-1.5 text-[11px] text-ink-4">Records without a published date are not charted.</p>56      </div>57      <div className="lg:col-span-2">58        <div className="mb-2 flex items-baseline justify-between">59          <h2 className="text-[13.5px] font-semibold text-ink">By country</h2>60          <span className="figure text-[11.5px] text-ink-3">top {byCountry.length} · records · published MW</span>61        </div>62        {byCountry.length ? <BarList items={byCountry.map((c) => ({ label: c.name, value: c.count, href: countryHref(c.key), secondary: c.mw === null ? "MW —" : fmtMw(c.mw) }))} format={(v) => fmtInt(v)} /> : <HonestEmpty title="No country distribution" />}63      </div>64    </div>65  );66}67