SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
6 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.8 KB · 44 lines tsx
Raw Blame History
1import type { MarketMomentum } from "@dci/core";2import Link from "next/link";3import { CounterGrid, type CounterItem } from "@/components/intelligence/shared";4import { fmtInt } from "@/lib/format";5import { routes } from "@/lib/routes";67/**8 * MARKET MOMENTUM — each component shown on its own, never collapsed into a score. Counts come from9 * events / projects detected in the window; MW are published figures only ("not published" otherwise).10 */11export function MarketMomentumPanel({ m, metroSlug, countryIso2 }: { m: MarketMomentum; metroSlug: string; countryIso2: string }) {12  const items: CounterItem[] = [13    { key: "pa", value: m.projectsAnnounced, label: "projects announced", href: routes.projects(`metro=${metroSlug}`) },14    { key: "pc", value: m.projectsEnteredConstruction, label: "projects entered construction", href: routes.projects(`metro=${metroSlug}&status=under_construction`) },15    { key: "cmw", value: m.constructionMw, label: "of construction MW (published, ≈)", format: "mw" },16    { key: "amw", value: m.announcedMw, label: "of announced MW (published, ≈)", format: "mw" },17    { key: "ne", value: m.newEntrants.length, label: m.newEntrants.length === 1 ? "operator entered the market" : "operators entered the market", note: "first facility observed in the window" },18    { key: "fo", value: m.facilitiesOpened, label: "facilities opened", href: routes.live(`metro=${metroSlug}&type=facility_opened`) },19    { key: "cr", value: m.cloudRegionsAdded, label: "cloud regions added", href: routes.live(`metro=${metroSlug}&type=cloud_region_announced,cloud_region_launched`) },20    { key: "ge", value: m.gridEvents, label: "grid / power events", href: routes.live(`metro=${metroSlug}&type=grid_constraint,grid_connection,utility_event,power_agreement`) },21    { key: "et", value: m.eventsTotal, label: "changes detected in total", href: routes.live(`country=${countryIso2}`) },22  ];23  return (24    <div className="flex flex-col gap-3">25      <CounterGrid items={items} suffix="· last 12 months" />26      {m.newEntrants.length > 0 && (27        <div>28          <div className="label-caps mb-1.5">New entrants ({fmtInt(m.newEntrants.length)})</div>29          <ul className="flex flex-wrap gap-1.5">30            {m.newEntrants.map((o) => (31              <li key={o.id}>32                <Link href={routes.operator(o.slug)} className="inline-flex h-10 items-center rounded-sm border border-hair bg-surface px-2 text-[12px] text-ink hover:border-hair-2 hover:text-accent sm:h-8">33                  {o.name}34                </Link>35              </li>36            ))}37          </ul>38          <p className="mt-1.5 text-[11px] text-ink-4">“New entrant” = an operator whose first facility in this market was first observed by the index within the window; for a recently connected source this reflects indexing, not market entry.</p>39        </div>40      )}41    </div>42  );43}44