import type { FacilityStatus, FacilitySummary, ProjectSummary } from "@dci/core"; import type { Metadata } from "next"; import Link from "next/link"; import { Breadcrumbs } from "@/components/entity/breadcrumbs"; import { SourcesFooter } from "@/components/entity/sources-footer"; import { FacilityTable } from "@/components/facility-table"; import { ExploreCharts } from "@/components/intelligence/explore/explore-charts"; import { ExploreMap } from "@/components/intelligence/explore/explore-map"; import { QueryBuilder } from "@/components/intelligence/explore/query-builder"; import { ResultBar } from "@/components/intelligence/explore/result-bar"; import { ApiNote } from "@/components/intelligence/shared"; import { ProjectList } from "@/components/project-list"; import { Page, PageHeader } from "@/components/ui/card"; import { FilterChips, type Chip } from "@/components/ui/filter-chips"; import { Pagination } from "@/components/ui/pagination"; import { SegmentedControl } from "@/components/ui/segmented"; import { EmptyState, OfflineNotice } from "@/components/ui/states"; import { api } from "@/lib/api"; import { EXPLORE_PER_PAGE, exploreHref, parseExploreQuery, toggleCsv } from "@/lib/explore-query"; import { countryName, fmtInt } from "@/lib/format"; import { CONFIDENCE_SHORT, PRECISION_SHORT, STATUS_LABEL, TYPE_LABEL } from "@/lib/labels"; import { routes } from "@/lib/routes"; import { pageMetadata } from "@/lib/seo"; export const revalidate = 120; export const metadata: Metadata = pageMetadata({ title: "Explore", description: "Structured query builder over every indexed facility and project: status, country, market, operator, type, published MW, AI evidence, dates, confidence and location precision — as a table, a map or charts. Every view is a shareable URL.", path: routes.explore(), }); type SP = Record; export default async function ExplorePage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const q = parseExploreQuery(sp); const entity = q.entity ?? "facilities"; const view = q.view ?? "table"; const r = await api.explore(q); const data = r.ok ? r.data : null; const page = q.page ?? 1; const perPage = q.per_page ?? EXPLORE_PER_PAGE; const total = data?.total ?? 0; const methodology = r.ok ? (r.meta?.methodology as string | undefined) : undefined; const chips: Chip[] = []; const drop = (key: string, member?: string) => exploreHref(q, member ? { [key]: toggleCsv(q[key as keyof typeof q] as string | undefined, member) } : { [key]: undefined, ...(key === "min_mw" ? { max_mw: undefined } : {}), ...(key === "opened_from" ? { opened_to: undefined } : {}), ...(key === "expected_after" ? { expected_before: undefined } : {}) }); if (q.q) chips.push({ key: "q", label: "Search", value: q.q, removeHref: drop("q") }); for (const s of q.status?.split(",") ?? []) chips.push({ key: `st:${s}`, label: "Status", value: STATUS_LABEL[s as FacilityStatus] ?? s, removeHref: drop("status", s) }); for (const c of q.country?.split(",") ?? []) chips.push({ key: `c:${c}`, label: "Country", value: countryName(c), removeHref: drop("country", c) }); if (q.metro) chips.push({ key: "metro", label: "Market", value: q.metro, removeHref: drop("metro") }); if (q.operator) chips.push({ key: "op", label: "Operator", value: q.operator, removeHref: drop("operator") }); for (const t of q.type?.split(",") ?? []) chips.push({ key: `t:${t}`, label: "Type", value: TYPE_LABEL[t as keyof typeof TYPE_LABEL] ?? t, removeHref: drop("type", t) }); if (q.min_mw !== undefined || q.max_mw !== undefined) chips.push({ key: "mw", label: "MW", value: q.min_mw !== undefined && q.max_mw !== undefined ? `${q.min_mw}–${q.max_mw}` : q.min_mw !== undefined ? `≥ ${q.min_mw}` : `≤ ${q.max_mw}`, removeHref: drop("min_mw") }); if (q.ai) chips.push({ key: "ai", label: "AI evidence", value: q.ai === "any" ? "any" : `≥ ${q.ai}`, removeHref: drop("ai") }); if (q.hyperscale) chips.push({ key: "hs", label: "Hyperscale", value: "yes", removeHref: drop("hyperscale") }); if (q.has_mw) chips.push({ key: "hasmw", label: "Known MW", value: "yes", removeHref: drop("has_mw") }); if (q.opened_from !== undefined || q.opened_to !== undefined) chips.push({ key: "opened", label: "Opened", value: `${q.opened_from ?? "…"}–${q.opened_to ?? "…"}`, removeHref: drop("opened_from") }); if (q.expected_after !== undefined || q.expected_before !== undefined) chips.push({ key: "expected", label: "Expected", value: `${q.expected_after ?? "…"}–${q.expected_before ?? "…"}`, removeHref: drop("expected_after") }); if (q.announced_since) chips.push({ key: "since", label: "Announced since", value: q.announced_since, removeHref: drop("announced_since") }); for (const c of q.confidence?.split(",") ?? []) chips.push({ key: `cf:${c}`, label: "Confidence", value: CONFIDENCE_SHORT[c as keyof typeof CONFIDENCE_SHORT] ?? c, removeHref: drop("confidence", c) }); for (const p of q.location_precision?.split(",") ?? []) chips.push({ key: `lp:${p}`, label: "Precision", value: PRECISION_SHORT[p as keyof typeof PRECISION_SHORT] ?? p, removeHref: drop("location_precision", p) }); for (const c of q.project_class?.split(",") ?? []) chips.push({ key: `pc:${c}`, label: "Class", value: c.replace(/_/g, " ").toLowerCase(), removeHref: drop("project_class", c) }); const viewHref = (v: "table" | "map" | "charts") => exploreHref(q, { view: v, page: undefined }); const countryHrefBase = entity === "projects" ? routes.projects("country=") : routes.datacenters("country="); const items = data?.items ?? []; const facilities = entity === "facilities" ? (items as FacilitySummary[]) : []; const projects = entity === "projects" ? (items as ProjectSummary[]) : []; return ( {!r.ok && }
{!data ? ( ) : total === 0 ? ( Clear filters} /> ) : view === "map" ? ( ) : view === "charts" ? ( ) : ( <> {entity === "facilities" ? : } exploreHref(q, { page: p })} label={entity === "projects" ? "projects" : "facilities"} className="mt-3" /> )}
{data && (
({ key: f.key, label: STATUS_LABEL[f.key as FacilityStatus] ?? f.key, count: f.count, href: exploreHref(q, { status: toggleCsv(q.status, f.key), page: undefined }), active: (q.status ?? "").split(",").includes(f.key) }))} /> ({ key: f.key, label: f.key, count: f.count, href: f.key === "unknown" ? undefined : exploreHref(q, { ai: f.key as never, page: undefined }), active: q.ai === f.key }))} note="Counts by the strongest evidence level on each record; “unknown” = no signal." /> {data.facets.type && data.facets.type.length > 0 && ({ key: f.key, label: TYPE_LABEL[f.key as keyof typeof TYPE_LABEL] ?? f.key, count: f.count, href: exploreHref(q, { type: toggleCsv(q.type, f.key), page: undefined }), active: (q.type ?? "").split(",").includes(f.key) }))} />} ({ key: f.key, label: f.name, count: f.count, href: exploreHref(q, { operator: q.operator === f.key ? undefined : f.key, page: undefined }), active: q.operator === f.key }))} />
)}
); } function FacetBlock({ title, items, note }: { title: string; items: Array<{ key: string; label: string; count: number; href?: string; active: boolean }>; note?: string }) { if (!items.length) return null; return (

{title}

    {items.map((it) => { const cls = `inline-flex h-10 items-center gap-1.5 rounded-sm border px-2 text-[12px] sm:h-7 ${it.active ? "border-ink bg-ink text-paper" : "border-hair-2 bg-surface text-ink-2 hover:bg-surface-2"}`; return (
  • {it.href ? ( {it.label} {fmtInt(it.count)} ) : ( {it.label} {fmtInt(it.count)} )}
  • ); })}
{note &&

{note}

}
); }