"use client";
import type { NearbyInfrastructure } from "@dci/core";
import Link from "next/link";
import { useEffect, useState } from "react";
import { EstimateMark, FlagBadges, PrecisionBadge, StatusBadge } from "@/components/ui/badge";
import { cn } from "@/components/ui/cn";
import { CountryFlag } from "@/components/ui/country-flag";
import { SegmentedControl } from "@/components/ui/segmented";
import { Skeleton } from "@/components/ui/skeleton";
import { apiGet, clientApiBase } from "@/lib/api";
import { bestMw, fmtInt, fmtMw } from "@/lib/format";
import { isImprecise } from "@/lib/labels";
import { routes } from "@/lib/routes";
const RADII = [5, 10, 25, 50, 100] as const;
type Radius = (typeof RADII)[number];
function km(d: number): string {
return d < 1 ? `${Math.round(d * 1000)} m` : `${d.toFixed(d < 10 ? 1 : 0)} km`;
}
function Group({ title, count, children, note }: { title: string; count: number; children?: React.ReactNode; note?: string }) {
return (
{title}
{fmtInt(count)}
{count === 0 ?
{note ?? "None within this radius."}
: children}
);
}
/**
* NEARBY INFRASTRUCTURE (§23): facilities, projects, IXPs, cloud regions and metros within a radius, from GET /nearby.
* Wording is always "nearby" — proximity is a great-circle distance between published coordinates and never implies a
* connection. Layers whose connectors do not exist yet (landing stations, substations, power plants) show the API note.
*/
export function NearbyInfrastructurePanel({ initial, center, excludeId }: { initial: NearbyInfrastructure | null; center: { lat: number; lng: number }; excludeId?: string }) {
const [radius, setRadius] = useState((initial?.radiusKm as Radius | undefined) && RADII.includes(initial!.radiusKm as Radius) ? (initial!.radiusKm as Radius) : 25);
// results cached per radius; the server-provided payload seeds its own radius
const [fetched, setFetched] = useState>>({});
const [error, setError] = useState(null);
const data: NearbyInfrastructure | null = initial && radius === initial.radiusKm ? initial : (fetched[radius] ?? null);
const loading = !data && !error;
useEffect(() => {
if (data) return;
const ctrl = new AbortController();
apiGet("/nearby", { lat: center.lat, lng: center.lng, radius_km: radius }, { base: clientApiBase(), signal: ctrl.signal, timeoutMs: 10000 }).then((r) => {
if (ctrl.signal.aborted) return;
if (r.ok) {
setFetched((f) => ({ ...f, [radius]: r.data }));
setError(null);
} else setError(r.error);
});
return () => ctrl.abort();
}, [radius, center.lat, center.lng, data]);
const facilities = (data?.facilities ?? []).filter((f) => f.id !== excludeId);
const empty = ["landingStations", "substations", "powerPlants"] as const;
const emptyLabels: Record<(typeof empty)[number], string> = { landingStations: "Cable landing stations", substations: "Substations", powerPlants: "Power plants" };
return (
{ setError(null); setRadius(Number(v) as Radius); }} options={RADII.map((r) => ({ value: String(r), label: `${r} km` }))} size="xs" />
{loading ? "Loading…" : error ? `Unavailable (${error})` : data ? `${fmtInt(facilities.length)} facilities · ${fmtInt(data.ixps.length)} IXPs · ${fmtInt(data.cloudRegions.length)} cloud regions · ${fmtInt(data.projects.length)} projects` : ""}
Open on map →
{loading && !data ? (
) : !data ? (
Nearby infrastructure is unavailable right now.
) : (
{facilities.length > 12 && Showing 12 of {fmtInt(facilities.length)}.
}
{data.ixps.slice(0, 8).map((x) => (
-
{x.name}
{x.networkCount !== null ? `${fmtInt(x.networkCount)} nets · ` : ""}
{km(x.distanceKm)}
))}
{data.cloudRegions.slice(0, 8).map((c) => (
-
{c.provider.name} {c.code}
{km(c.distanceKm)}
))}
{data.projects.slice(0, 8).map((p) => (
-
{p.name}
{p.plannedMw !== null ? `≈ ${fmtMw(p.plannedMw)} · ` : ""}
{km(p.distanceKm)}
))}
{data.metros.length > 0 && (
{data.metros.map((m) => (
-
{m.name} {km(m.distanceKm)}
))}
)}
)}
{data && (
{empty.map((k) => (
{emptyLabels[k]}
{fmtInt(data[k].length)}
{data[k].length === 0 &&
Empty until a licensed connector for this layer exists — never inferred.
}
))}
{data.note}
)}
);
}