spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1"use client";23import type { ExploreQuery, FacilityFilters } from "@dci/core";4import dynamic from "next/dynamic";5import Link from "next/link";6import { Skeleton } from "@/components/ui/skeleton";7import { fmtInt } from "@/lib/format";8import { routes } from "@/lib/routes";910const MapCanvas = dynamic(() => import("@/components/map/map-canvas").then((m) => m.MapCanvas), { ssr: false, loading: () => <Skeleton className="h-full w-full rounded-none" /> });1112/** Facility filters the /map endpoint understands, derived from the explore query. */13export function mapFiltersFor(q: ExploreQuery): FacilityFilters {14 const f: FacilityFilters = {};15 if (q.country) f.country = q.country;16 if (q.operator) f.operator = q.operator;17 if (q.metro) f.metro = q.metro;18 if (q.status) f.status = q.status;19 if (q.type) f.type = q.type;20 if (q.min_mw !== undefined) f.min_mw = q.min_mw;21 if (q.max_mw !== undefined) f.max_mw = q.max_mw;22 if (q.hyperscale) f.hyperscale = true;23 if (q.has_mw) f.has_mw = true;24 if (q.ai && q.ai !== "associated") f.ai = true;25 if (q.q) f.q = q.q;26 if (q.opened_from !== undefined) f.opened_from = q.opened_from;27 if (q.opened_to !== undefined) f.opened_to = q.opened_to;28 return f;29}3031/**32 * Map view of the explore result. The canvas fetches /map with the same filters (server-side clustering), so what it33 * draws is the filtered set, not the current table page. Projects use the projects layer.34 */35export function ExploreMap({ query, total, degraded }: { query: ExploreQuery; total: number; degraded: boolean }) {36 const projects = query.entity === "projects";37 const filters = mapFiltersFor(query);38 const extra: Record<string, string | number | boolean | undefined> = projects ? { project_status: query.status, expected_from: query.expected_after, expected_to: query.expected_before, ai_evidence: query.ai && query.ai !== "any" ? query.ai : undefined } : { location_precision: query.location_precision, ai_evidence: query.ai && query.ai !== "any" ? query.ai : undefined };39 const sp = new URLSearchParams();40 for (const [k, v] of Object.entries({ ...filters, ...extra, layer: projects ? "projects" : undefined })) if (v !== undefined && v !== "" && v !== false) sp.set(k, v === true ? "1" : String(v));41 const href = routes.map(sp.toString() || undefined);42 return (43 <div>44 <div className="relative h-[360px] w-full overflow-hidden rounded-sm border border-hair bg-surface-2 sm:h-[480px]">45 <MapCanvas className="h-full w-full" filters={filters} layer={projects ? "projects" : "facilities"} extraQuery={extra} initialView={{ lng: 10, lat: 25, zoom: 1.4 }} />46 </div>47 <p className="mt-2 text-[11.5px] text-ink-3">48 <span className="figure">{fmtInt(total)}</span> {projects ? "project" : "facility"} points carry coordinates in this result{degraded ? " — the point cap (5 000) was hit, so the map shows clusters" : ""}. Hollow rings are city-level locations; {projects ? "projects are geocoded from their metro or city, never to a parcel" : "records without coordinates are listed in the table but not drawn"}.{" "}49 <Link href={href} className="underline decoration-hair-2 hover:text-ink">50 Open in the full map →51 </Link>52 </p>53 </div>54 );55}56