spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1"use client";23import type { HistoryPoint } from "@dci/core";4import { useMemo } from "react";5import { LineChart, type Series } from "@/components/ui/charts";6import { SourceBadge } from "@/components/ui/source-badge";7import { fieldLabel } from "@/lib/field-labels";8import { fmtDate, fmtMw } from "@/lib/format";9import { Evidence } from "./evidence-context";1011const COLORS: Record<string, string> = {12 itCapacityMw: "var(--ink)",13 totalPowerMw: "var(--st-planned)",14 plannedPowerMw: "var(--accent)",15 plannedMw: "var(--accent)",16 utilityCapacityMw: "var(--st-permitting)",17 gridConnectionMw: "var(--st-permitting)",18 ultimateCampusMw: "var(--st-construction)",19};2021/**22 * Capacity history: step chart of every observed / changed MW value per field (values hold until the next23 * observation). With fewer than two dated points it degrades to a dated list so nothing is drawn that the data24 * cannot support.25 */26export function CapacityHistoryChart({ points, height = 180 }: { points: HistoryPoint[]; height?: number }) {27 const mw = useMemo(() => points.filter((p) => /Mw$|_mw$/.test(p.field) && typeof p.value === "number").sort((a, b) => (a.date < b.date ? -1 : 1)), [points]);28 const series = useMemo<Series[]>(() => {29 const by = new Map<string, HistoryPoint[]>();30 for (const p of mw) {31 const f = p.field.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());32 if (!by.has(f)) by.set(f, []);33 by.get(f)!.push(p);34 }35 return Array.from(by.entries()).map(([field, pts]) => ({ name: fieldLabel(field), color: COLORS[field] ?? "var(--ink-3)", step: true, dashed: /planned/i.test(field), data: pts.map((p) => ({ x: p.date.slice(0, 10), y: p.value as number })) }));36 }, [mw]);37 const dates = new Set(mw.map((p) => p.date.slice(0, 10)));3839 if (!mw.length) return <p className="rounded-sm border border-dashed border-hair-2 px-3 py-2.5 text-[12.5px] text-ink-3">No MW figure has been observed for this record — no history to chart.</p>;4041 return (42 <div className="flex flex-col gap-3">43 {dates.size >= 2 ? (44 <LineChart series={series} height={height} format={(v) => fmtMw(v)} ariaLabel="Capacity history — observed MW values by date" />45 ) : (46 <p className="text-[12px] text-ink-3">One observation date so far — the step chart appears once a second dated value exists.</p>47 )}48 <ul className="divide-y divide-hair text-[12.5px]">49 {[...mw].reverse().slice(0, 12).map((p, i) => (50 <li key={`${p.date}-${p.field}-${i}`} className="grid grid-cols-[minmax(0,1fr)_auto] items-baseline gap-x-3 py-1.5">51 <div className="min-w-0 flex flex-wrap items-center gap-x-2 gap-y-0.5">52 <time className="figure text-ink-3" dateTime={p.date}>53 {fmtDate(p.date)}54 </time>55 <span className="text-ink-2">{fieldLabel(p.field)}</span>56 {p.sourceKind && <SourceBadge kind={p.sourceKind} name={p.sourceName} url={p.url} />}57 <span className="text-[11px] uppercase tracking-[0.06em] text-ink-4">{p.kind}</span>58 </div>59 <Evidence field={p.field} history>60 <span className="figure font-medium text-ink">61 {typeof p.oldValue === "number" && <span className="mr-1 text-ink-3 line-through decoration-ink-4">{fmtMw(p.oldValue)}</span>}62 {fmtMw(p.value as number)}63 </span>64 </Evidence>65 </li>66 ))}67 </ul>68 </div>69 );70}71