import type { FacilityFilters, SearchHit, SearchResponse } from "@dci/core"; import { Map as MapIcon, Search } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; import { FacilityTable } from "@/components/facility-table"; import { HeroSearch } from "@/components/home/hero"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Page, PageHeader, Section } from "@/components/ui/card"; import { EmptyState, OfflineNotice } from "@/components/ui/states"; import { api } from "@/lib/api"; import { fmtInt, fmtMw } from "@/lib/format"; import { SEARCH_GROUP_LABEL, STATUS_LABEL, TYPE_LABEL } from "@/lib/labels"; import { filtersToQuery } from "@/lib/query"; import { routes } from "@/lib/routes"; export const revalidate = 120; export async function generateMetadata({ searchParams }: { searchParams: Promise> }): Promise { const sp = await searchParams; const q = typeof sp.q === "string" ? sp.q : ""; return { title: q ? `“${q}” — search` : "Search", robots: { index: false, follow: true }, alternates: { canonical: "/search" } }; } const GROUPS: SearchHit["type"][] = ["facility", "operator", "country", "metro", "city", "cloud_region", "project", "ixp"]; export default async function SearchPage({ searchParams }: { searchParams: Promise> }) { const sp = await searchParams; const q = (typeof sp.q === "string" ? sp.q : "").trim(); if (!q) { return ( ); } const r = await api.search(q); const data: SearchResponse | null = r.ok ? r.data : null; const hits = data?.hits ?? []; const facilities = data?.facilities ?? null; const interp = data?.interpreted; const interpFilters: FacilityFilters = {}; if (interp?.countryIso2) interpFilters.country = interp.countryIso2; if (interp?.operator) interpFilters.operator = interp.operator; if (interp?.status) interpFilters.status = interp.status; if (interp?.minMw !== undefined) interpFilters.min_mw = interp.minMw; if (interp?.facilityType) interpFilters.type = interp.facilityType; if (interp?.text) interpFilters.q = interp.text; const mapQs = filtersToQuery(Object.keys(interpFilters).length ? interpFilters : { q }); const grouped = new Map(); for (const h of hits.filter((h) => h.type !== "facility" || !facilities)) { if (!grouped.has(h.type)) grouped.set(h.type, []); grouped.get(h.type)!.push(h); } const nothing = r.ok && hits.length === 0 && (!facilities || facilities.total === 0); return ( Results for “{q}” } description={r.ok ? `${fmtInt(hits.length + (facilities?.total ?? 0))} matches across the index.` : undefined} aside={ } /> {!r.ok && } {interp && Object.keys(interp).length > 0 && (
Interpreted as {interp.text && text: {interp.text}} {interp.countryIso2 && country: {interp.countryIso2}} {interp.operator && operator: {interp.operator}} {interp.status && status: {STATUS_LABEL[interp.status]}} {interp.facilityType && type: {TYPE_LABEL[interp.facilityType]}} {interp.minMw !== undefined && ≥ {fmtMw(interp.minMw)}} Apply as filters
)} {nothing && } title={`Nothing matched “${q}”`} description="Try a shorter query, an operator name, a city or a country code." action={} />}
{(facilities || (!r.ok && true)) && (
{facilities && facilities.total > facilities.items.length && (
Showing {fmtInt(facilities.items.length)} of {fmtInt(facilities.total)} —{" "} see all in the index
)}
)}
{GROUPS.filter((g) => grouped.has(g)).map((g) => (
{grouped.get(g)!.length}}>
    {grouped.get(g)!.map((h) => (
  • {h.title} {h.subtitle && {h.subtitle}} {h.meta && typeof h.meta.mw === "number" && {fmtMw(h.meta.mw)}} {h.meta && typeof h.meta.facilityCount === "number" && {fmtInt(h.meta.facilityCount)} DC}
  • ))}
))} {r.ok && grouped.size === 0 && !nothing && }
); }