spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1"use client";23import type { MapPoint, MapResponse, ProjectSummary } from "@dci/core";4import dynamic from "next/dynamic";5import Link from "next/link";6import { useCallback, useState } from "react";7import { StatusBadge } from "@/components/ui/badge";8import { Button } from "@/components/ui/button";9import { Skeleton } from "@/components/ui/skeleton";10import { fmtInt, fmtMw } from "@/lib/format";11import { PRECISION_LABEL } from "@/lib/labels";12import { routes } from "@/lib/routes";1314const MapCanvas = dynamic(() => import("@/components/map/map-canvas").then((m) => m.MapCanvas), { ssr: false, loading: () => <Skeleton className="h-full w-full rounded-none" /> });1516/**17 * Projects MAP view: the shared canvas in `layer=projects` mode (square stage-coloured markers, hollow ring for18 * city-level points). Projects without published coordinates cannot be drawn — the caption says how many are missing19 * instead of guessing a location.20 */21export function ProjectsMapView({ items, total, projectStatus }: { items: ProjectSummary[]; total: number; projectStatus?: string }) {22 const [data, setData] = useState<MapResponse | null>(null);23 const [sel, setSel] = useState<MapPoint | null>(null);24 const onData = useCallback((r: MapResponse | null) => setData(r), []);25 const located = items.filter((p) => p.lat !== null && p.lng !== null).length;26 return (27 <div className="flex flex-col gap-2">28 <div className="relative h-[52vh] min-h-[320px] w-full overflow-hidden rounded-sm border border-hair bg-surface-2">29 <MapCanvas layer="projects" extraQuery={{ project_status: projectStatus }} onData={onData} onSelect={setSel} className="h-full w-full" />30 {sel && (31 <div className="absolute bottom-3 left-3 right-3 z-[5] flex flex-wrap items-center gap-2 rounded-sm border border-hair bg-surface/95 px-3 py-2 text-[12.5px] backdrop-blur sm:right-auto sm:max-w-[420px]">32 <StatusBadge status={sel.s} short size="xs" />33 <span className="min-w-0 flex-1 truncate font-medium text-ink">{sel.n}</span>34 <span className="figure text-ink-2">{sel.mw != null ? `≈ ${fmtMw(sel.mw)}` : "—"}</span>35 <span className="text-[11px] text-ink-3">{PRECISION_LABEL[sel.p]}</span>36 <Button href={sel.k === "project" ? routes.project(sel.slug) : routes.facility(sel.slug)} size="xs" variant="primary">37 Open38 </Button>39 </div>40 )}41 </div>42 <p className="text-[11.5px] text-ink-3">43 {data ? <>{fmtInt(data.total)} located {data.total === 1 ? "project" : "projects"} drawn</> : "Loading…"} · {fmtInt(located)} of the {fmtInt(items.length)} projects on this page have published coordinates{total > items.length ? ` (${fmtInt(total)} match in total)` : ""}. Square markers are coloured by stage; a hollow ring means a city-level location, never a site.{" "}44 <Link href={routes.map("layer=projects")} className="underline decoration-hair-2 hover:text-ink">45 Open in the full map46 </Link>47 </p>48 </div>49 );50}51