spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { FacilityFilters, FacilitySummary } from "@dci/core";2import Link from "next/link";3import { ConfidenceBadge, EstimateMark, FlagBadges, StatusBadge, TypeBadge } from "@/components/ui/badge";4import { CountryFlag } from "@/components/ui/country-flag";5import { EmptyState } from "@/components/ui/states";6import { RowLink, TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table";7import { bestMw, fmtMw } from "@/lib/format";8import { filtersToQuery } from "@/lib/query";9import { routes } from "@/lib/routes";1011export interface FacilityTableProps {12 items: FacilitySummary[];13 /** current filters → sort links */14 filters?: FacilityFilters;15 basePath?: string;16 /** hide columns for compact embeds */17 columns?: Array<"operator" | "market" | "country" | "status" | "mw" | "type" | "confidence">;18 emptyTitle?: string;19 emptyDescription?: string;20}2122const ALL: NonNullable<FacilityTableProps["columns"]> = ["operator", "market", "country", "status", "mw", "type", "confidence"];2324/**25 * Facility index table. Sortable headers link to ?sort=&order= (server-side). Under 640 px, rows26 * become stacked cards (data-label). Reused by /datacenters, /search and the entity pages.27 */28export function FacilityTable({ items, filters = {}, basePath = routes.datacenters(), columns = ALL, emptyTitle = "No facilities match these filters", emptyDescription = "Loosen a filter or clear them all." }: FacilityTableProps) {29 const has = (c: (typeof ALL)[number]) => columns.includes(c);30 const sortHref = (key: NonNullable<FacilityFilters["sort"]>) => {31 const dir = filters.sort === key ? (filters.order === "asc" ? "desc" : "asc") : key === "name" ? "asc" : "desc";32 const q = filtersToQuery({ ...filters, sort: key, order: dir, page: undefined });33 return `${basePath}${q ? `?${q}` : ""}`;34 };35 const sortState = (key: NonNullable<FacilityFilters["sort"]>) => (filters.sort === key ? (filters.order ?? "desc") : null);3637 if (!items.length) return <EmptyState title={emptyTitle} description={emptyDescription} compact />;3839 return (40 <Table mobileCollapse={{ total: items.length, label: "facilities" }}>41 <THead>42 <tr>43 <Th sort={sortState("name")} sortHref={sortHref("name")}>44 Facility45 </Th>46 {has("operator") && <Th>Operator</Th>}47 {has("market") && <Th className="hidden lg:table-cell">Market</Th>}48 {has("country") && <Th>Country</Th>}49 {has("status") && <Th>Status</Th>}50 {has("mw") && (51 <Th numeric sort={sortState("mw")} sortHref={sortHref("mw")}>52 Capacity53 </Th>54 )}55 {has("type") && <Th className="hidden md:table-cell">Type</Th>}56 {has("confidence") && (57 <Th className="hidden md:table-cell" sort={sortState("completeness")} sortHref={sortHref("completeness")}>58 Confidence59 </Th>60 )}61 </tr>62 </THead>63 <TBody>64 {items.map((f) => {65 const { mw, kind } = bestMw(f);66 return (67 <Tr key={f.id}>68 <Td primary>69 <div className="flex items-center gap-2">70 <RowLink href={routes.facility(f.slug)}>{f.name}</RowLink>71 <FlagBadges isAi={f.isAi} isHyperscale={f.isHyperscale} />72 </div>73 <div className="text-[11.5px] text-ink-3 sm:hidden">74 {f.operator?.name ?? "Operator unknown"}75 {f.city ? ` · ${f.city}` : ""}76 </div>77 </Td>78 {has("operator") && (79 <Td label="Operator" className="hidden sm:table-cell">80 {f.operator ? (81 <Link href={routes.operator(f.operator.slug)} className="text-ink-2 hover:text-ink hover:underline">82 {f.operator.name}83 </Link>84 ) : (85 <span className="text-ink-4">—</span>86 )}87 </Td>88 )}89 {has("market") && (90 <Td label="Market" className="hidden lg:table-cell" muted>91 {f.metro ? (92 <Link href={routes.market(f.metro.slug)} className="hover:text-ink hover:underline">93 {f.metro.name}94 </Link>95 ) : (96 f.city ?? "—"97 )}98 </Td>99 )}100 {has("country") && (101 <Td label="Country">102 <CountryFlag iso2={f.countryIso2} withName className="max-w-[160px] text-ink-2" />103 </Td>104 )}105 {has("status") && (106 <Td label="Status">107 <StatusBadge status={f.status} short size="xs" />108 </Td>109 )}110 {has("mw") && (111 <Td label="Capacity" numeric lead>112 {mw === null ? (113 <span className="text-ink-4">—</span>114 ) : (115 <span title={kind === "it" ? "IT capacity" : kind === "total" ? "Total power" : "Planned power"}>116 {kind === "planned" && <EstimateMark className="mr-0.5" />}117 {fmtMw(mw)}118 </span>119 )}120 </Td>121 )}122 {has("type") && (123 <Td label="Type" className="hidden md:table-cell">124 <TypeBadge type={f.facilityType} size="xs" />125 </Td>126 )}127 {has("confidence") && (128 <Td label="Confidence" className="hidden md:table-cell">129 <ConfidenceBadge level={f.confidence} size="xs" />130 </Td>131 )}132 </Tr>133 );134 })}135 </TBody>136 </Table>137 );138}139