import type { FacilityStatus, ProjectSummary } from "@dci/core"; import { partialDateSortKey } from "@dci/core"; import Link from "next/link"; import { AiMark, EstimateMark, PrecisionBadge, ScopeBadge, StatusBadge } from "@/components/ui/badge"; import { cn } from "@/components/ui/cn"; import { CountryFlag } from "@/components/ui/country-flag"; import { EmptyState } from "@/components/ui/states"; import { StatusDot } from "@/components/ui/status-dot"; import { fmtInt, fmtMw, fmtUsd, placeLine } from "@/lib/format"; import { fmtPartialDateSafe } from "@/lib/format-project"; import { STATUS_SHORT, isSiteScopeLabel } from "@/lib/labels"; import { routes } from "@/lib/routes"; /* ------------------------------------------------------------------ shared dense card */ function mwCell(p: ProjectSummary) { if (p.plannedMw === null) return —; const site = p.capacityScope === null || p.capacityScope === undefined || isSiteScopeLabel(p.capacityScope); if (!site) return — ; return ( {fmtMw(p.plannedMw)} ); } export function ProjectCard({ p, showStatus = true, className }: { p: ProjectSummary; showStatus?: boolean; className?: string }) { return (
{p.name} {mwCell(p)}
{p.operator ? {p.operator.name} : Operator unknown} · {p.metro?.name ?? placeLine(p)}
{showStatus && } {p.isAi && } {p.geoPrecision && p.geoPrecision !== "unknown" && p.geoPrecision !== "exact" && } {p.expectedOpening ? fmtPartialDateSafe(p.expectedOpening) : "TBD"}
); } /* ------------------------------------------------------------------ TIMELINE */ /** Projects grouped by expected opening year (undated last), each year with its published-MW sum. */ export function ProjectsTimeline({ items }: { items: ProjectSummary[] }) { if (!items.length) return ; const groups = new Map(); for (const p of items) { const k = p.expectedOpening ? p.expectedOpening.slice(0, 4) : "TBD"; if (!groups.has(k)) groups.set(k, []); groups.get(k)!.push(p); } const keys = Array.from(groups.keys()).sort((a, b) => (a === "TBD" ? 1 : b === "TBD" ? -1 : a.localeCompare(b))); return (
    {keys.map((k) => { const list = groups.get(k)!.sort((a, b) => partialDateSortKey((a.expectedOpening ?? "9999").slice(0, 10)) - partialDateSortKey((b.expectedOpening ?? "9999").slice(0, 10))); const mw = list.filter((p) => p.plannedMw !== null && (p.capacityScope == null || isSiteScopeLabel(p.capacityScope))).reduce((a, p) => a + (p.plannedMw ?? 0), 0); const withMw = list.filter((p) => p.plannedMw !== null).length; return (
  1. {k === "TBD" ? "Expected opening not published" : k}

    {fmtInt(list.length)} {list.length === 1 ? "project" : "projects"} {mw > 0 && <> · ≈ {fmtMw(mw)} published ({fmtInt(withMw)} of {fmtInt(list.length)} with a figure)}
    {list.map((p) => ( ))}
  2. ); })}
); } /* ------------------------------------------------------------------ KANBAN */ const KANBAN: FacilityStatus[] = ["rumored", "proposed", "announced", "permitting", "approved", "under_construction", "partially_operational", "delayed", "cancelled"]; export function ProjectsKanban({ items, counts }: { items: ProjectSummary[]; counts?: Partial> }) { if (!items.length) return ; const cols = KANBAN.map((s) => ({ s, list: items.filter((p) => p.status === s || (s === "announced" && p.status === "expansion")) })).filter((c) => c.list.length || (counts?.[c.s] ?? 0) > 0); return (
    {cols.map((c) => { const mw = c.list.filter((p) => p.plannedMw !== null && (p.capacityScope == null || isSiteScopeLabel(p.capacityScope))).reduce((a, p) => a + (p.plannedMw ?? 0), 0); return (
  1. {STATUS_SHORT[c.s]}

    {fmtInt(counts?.[c.s] ?? c.list.length)} {mw > 0 && ` · ≈ ${fmtMw(mw)}`}
    {c.list.length ? c.list.map((p) => ) :

    None on this page — {fmtInt(counts?.[c.s] ?? 0)} in total.

    }
  2. ); })}
); } /* ------------------------------------------------------------------ helper used by the map view list */ export function investmentCell(p: ProjectSummary) { if (p.investmentUsd === null) return "—"; const site = p.investmentScope == null || isSiteScopeLabel(p.investmentScope); return site ? fmtUsd(p.investmentUsd) : "—"; }