"use client"; import React from "react"; import { useEffect, useState, useCallback } from "react"; import { fetchKpis, fetchRackBreakdown, fetchRoomPowerHistory, fetchUpsStatus, fetchCapacitySummary, fetchGeneratorStatus, fetchAtsStatus, fetchPowerRedundancy, fetchPhaseBreakdown, type KpiData, type RoomPowerBreakdown, type PowerHistoryBucket, type UpsAsset, type CapacitySummary, type GeneratorStatus, type AtsStatus, type PowerRedundancy, type RoomPhase, } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, AreaChart, Area, Cell, } from "recharts"; import { Zap, Battery, AlertTriangle, CheckCircle2, Activity, Fuel, ArrowLeftRight, ShieldCheck, Server, Thermometer, Gauge } from "lucide-react"; import { UpsDetailSheet } from "@/components/dashboard/ups-detail-sheet"; import { cn } from "@/lib/utils"; const SITE_ID = "sg-01"; const ROOM_COLORS: Record = { "hall-a": "oklch(0.62 0.17 212)", "hall-b": "oklch(0.7 0.15 162)", }; const roomLabels: Record = { "hall-a": "Hall A", "hall-b": "Hall B", }; function formatTime(iso: string) { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } // ── Site capacity bar ───────────────────────────────────────────────────────── function SiteCapacityBar({ usedKw, capacityKw }: { usedKw: number; capacityKw: number }) { const pct = capacityKw > 0 ? Math.min(100, (usedKw / capacityKw) * 100) : 0; const barColor = pct >= 85 ? "bg-destructive" : pct >= 70 ? "bg-amber-500" : "bg-primary"; const textColor = pct >= 85 ? "text-destructive" : pct >= 70 ? "text-amber-400" : "text-green-400"; return (
Site IT Load vs Rated Capacity
{pct.toFixed(1)}% utilised
{usedKw.toFixed(1)} kW in use {(capacityKw - usedKw).toFixed(1)} kW headroom {capacityKw.toFixed(0)} kW rated
); } // ── KPI card ────────────────────────────────────────────────────────────────── function KpiCard({ label, value, sub, icon: Icon, accent }: { label: string; value: string; sub?: string; icon: React.ElementType; accent?: boolean }) { return (

{value}

{label}

{sub &&

{sub}

}
); } // ── UPS card ────────────────────────────────────────────────────────────────── function UpsCard({ ups, onClick }: { ups: UpsAsset; onClick: () => void }) { const onBattery = ups.state === "battery"; const overload = ups.state === "overload"; const abnormal = onBattery || overload; const charge = ups.charge_pct ?? 0; const runtime = ups.runtime_min ?? null; return (
{ups.ups_id.toUpperCase()} {abnormal ? : } {overload ? "Overloaded" : onBattery ? "On Battery" : "Mains"}
{/* Battery charge */}
Battery charge {ups.charge_pct !== null ? `${ups.charge_pct}%` : "—"}

Load

= 95 ? "text-destructive" : (ups.load_pct ?? 0) >= 85 ? "text-amber-400" : "", )}> {ups.load_pct !== null ? `${ups.load_pct}%` : "—"}

Runtime

{runtime !== null ? `${Math.round(runtime)} min` : "—"}

Voltage

250) ? "text-amber-400" : "", )}> {ups.voltage_v !== null ? `${ups.voltage_v} V` : "—"}

{/* Runtime bar */} {runtime !== null && (
Est. runtime remaining {runtime < 5 ? "Critical" : runtime < 15 ? "Low" : "OK"}
)}

Click for details

); } // ── Rack bar chart ──────────────────────────────────────────────────────────── function RackPowerChart({ rooms }: { rooms: RoomPowerBreakdown[] }) { const [activeRoom, setActiveRoom] = useState(rooms[0]?.room_id ?? ""); const room = rooms.find((r) => r.room_id === activeRoom); if (!room) return null; const maxKw = Math.max(...room.racks.map((r) => r.power_kw ?? 0), 1); return (
Per-Rack Power (kW) {rooms.map((r) => ( {roomLabels[r.room_id] ?? r.room_id} ))}
{[ { color: "oklch(0.62 0.17 212)", label: "Normal" }, { color: "oklch(0.68 0.14 162)", label: "Moderate" }, { color: "oklch(0.65 0.20 45)", label: "High (≥7.5 kW)" }, { color: "oklch(0.55 0.22 25)", label: "Critical (≥9.5 kW)" }, ].map(({ color, label }) => ( {label} ))}
v.replace("rack-", "")} /> [`${v} kW`, "Power"]} labelFormatter={(l) => l} /> {room.racks.map((r) => ( = 9.5 ? "oklch(0.55 0.22 25)" : (r.power_kw ?? 0) >= 7.5 ? "oklch(0.65 0.20 45)" : (r.power_kw ?? 0) >= 4.0 ? "oklch(0.68 0.14 162)" : ROOM_COLORS[room.room_id] ?? "oklch(0.62 0.17 212)" } /> ))}
); } // ── Room power history chart ────────────────────────────────────────────────── function RoomPowerHistoryChart({ data }: { data: PowerHistoryBucket[] }) { type Row = { time: string; [room: string]: string | number }; const bucketMap = new Map(); for (const row of data) { const time = formatTime(row.bucket); if (!bucketMap.has(time)) bucketMap.set(time, { time }); bucketMap.get(time)![row.room_id] = row.total_kw; } const chartData = Array.from(bucketMap.values()); const roomIds = [...new Set(data.map((d) => d.room_id))].sort(); return (
Power by Room
{roomIds.map((id) => ( {roomLabels[id] ?? id} ))}
{chartData.length === 0 ? (
Waiting for data...
) : ( {roomIds.map((id) => ( ))} [`${v} kW`, roomLabels[String(name)] ?? String(name)]} /> {roomIds.map((id) => ( ))} )}
); } // ── Generator card ──────────────────────────────────────────────── function GeneratorCard({ gen }: { gen: GeneratorStatus }) { const faulted = gen.state === "fault"; const running = gen.state === "running" || gen.state === "test"; const fuel = gen.fuel_pct ?? 0; const stateLabel = { standby: "Standby", running: "Running", test: "Test Run", fault: "FAULT", unknown: "Unknown" }[gen.state] ?? gen.state; return (
{gen.gen_id.toUpperCase()} {faulted ? : } {stateLabel}
Fuel level {gen.fuel_pct != null ? `${gen.fuel_pct.toFixed(1)}%` : "—"} {gen.fuel_litres != null ? ` (${gen.fuel_litres.toFixed(0)} L)` : ""}

Load

= 95 ? "text-destructive" : (gen.load_pct ?? 0) >= 85 ? "text-amber-400" : "" )}> {gen.load_kw != null ? `${gen.load_kw} kW (${gen.load_pct}%)` : "—"}

Run hours

{gen.run_hours != null ? `${gen.run_hours.toFixed(0)} h` : "—"}

Output voltage

{gen.voltage_v != null && gen.voltage_v > 0 ? `${gen.voltage_v} V` : "—"}

Frequency

{gen.frequency_hz != null && gen.frequency_hz > 0 ? `${gen.frequency_hz} Hz` : "—"}

Coolant

= 105 ? "text-destructive" : (gen.coolant_temp_c ?? 0) >= 95 ? "text-amber-400" : "" )}> {gen.coolant_temp_c != null ? `${gen.coolant_temp_c}°C` : "—"}

Exhaust

{gen.exhaust_temp_c != null && gen.exhaust_temp_c > 0 ? `${gen.exhaust_temp_c}°C` : "—"}

Oil pressure

{gen.oil_pressure_bar != null && gen.oil_pressure_bar > 0 ? `${gen.oil_pressure_bar} bar` : "—"}

Battery

{gen.battery_v != null ? `${gen.battery_v} V` : "—"}

); } // ── ATS card ────────────────────────────────────────────────────── function AtsCard({ ats }: { ats: AtsStatus }) { const onGenerator = ats.active_feed === "generator"; const transferring = ats.state === "transferring"; const feedLabel: Record = { "utility-a": "Utility A", "utility-b": "Utility B", "generator": "Generator", }; return (
{ats.ats_id.toUpperCase()} {transferring ? "Transferring" : onGenerator ? "Generator feed" : "Stable"}

Active Feed

{feedLabel[ats.active_feed] ?? ats.active_feed}

{[ { label: "Utility A", v: ats.utility_a_v, active: ats.active_feed === "utility-a" }, { label: "Utility B", v: ats.utility_b_v, active: ats.active_feed === "utility-b" }, { label: "Generator", v: ats.generator_v, active: ats.active_feed === "generator" }, ].map(({ label, v, active }) => (

{label}

{v != null && v > 0 ? `${v.toFixed(0)} V` : "—"}

))}
Transfers: {ats.transfer_count} {ats.last_transfer_ms != null && ( Last xfer: {ats.last_transfer_ms} ms )}
); } // ── Redundancy banner ───────────────────────────────────────────── function RedundancyBanner({ r }: { r: PowerRedundancy }) { const color = r.level === "2N" ? "border-green-500/30 bg-green-500/5 text-green-400" : r.level === "N+1" ? "border-amber-500/30 bg-amber-500/5 text-amber-400" : "border-destructive/30 bg-destructive/5 text-destructive"; return (

Power Redundancy: {r.level}

{r.notes}

UPS online: {r.ups_online}/{r.ups_total}

Generator: {r.generator_ok ? "available" : "unavailable"}

Feed: {r.ats_active_feed ?? "—"}

); } // ── Phase imbalance table ────────────────────────────────────────── function PhaseImbalanceTable({ rooms }: { rooms: RoomPhase[] }) { const allRacks = rooms.flatMap(r => r.racks).filter(r => (r.imbalance_pct ?? 0) > 5); if (allRacks.length === 0) return null; return ( Phase Imbalance — {allRacks.length} rack{allRacks.length !== 1 ? "s" : ""} flagged
{allRacks.map(rack => { const imb = rack.imbalance_pct ?? 0; const crit = imb >= 15; return ( ); })}
Rack Phase A kW Phase B kW Phase C kW Imbalance
{rack.rack_id} {rack.phase_a_kw?.toFixed(2) ?? "—"} {rack.phase_b_kw?.toFixed(2) ?? "—"} {rack.phase_c_kw?.toFixed(2) ?? "—"} {imb.toFixed(1)}%
); } // ── Page ────────────────────────────────────────────────────────────────────── export default function PowerPage() { const [kpis, setKpis] = useState(null); const [racks, setRacks] = useState([]); const [history, setHistory] = useState([]); const [ups, setUps] = useState([]); const [capacity, setCapacity] = useState(null); const [generators, setGenerators] = useState([]); const [atsUnits, setAtsUnits] = useState([]); const [redundancy, setRedundancy] = useState(null); const [phases, setPhases] = useState([]); const [historyHours, setHistoryHours] = useState(6); const [loading, setLoading] = useState(true); const [phaseExpanded, setPhaseExpanded] = useState(false); const [selectedUps, setSelectedUps] = useState(null); const loadHistory = useCallback(async () => { try { const h = await fetchRoomPowerHistory(SITE_ID, historyHours); setHistory(h); } catch { /* keep stale */ } }, [historyHours]); const load = useCallback(async () => { try { const [k, r, h, u, cap, g, a, red, ph] = await Promise.all([ fetchKpis(SITE_ID), fetchRackBreakdown(SITE_ID), fetchRoomPowerHistory(SITE_ID, historyHours), fetchUpsStatus(SITE_ID), fetchCapacitySummary(SITE_ID), fetchGeneratorStatus(SITE_ID).catch(() => []), fetchAtsStatus(SITE_ID).catch(() => []), fetchPowerRedundancy(SITE_ID).catch(() => null), fetchPhaseBreakdown(SITE_ID).catch(() => []), ]); setKpis(k); setRacks(r); setHistory(h); setUps(u); setCapacity(cap); setGenerators(g); setAtsUnits(a); setRedundancy(red); setPhases(ph); } catch { // keep stale data } finally { setLoading(false); } }, [historyHours]); useEffect(() => { load(); const id = setInterval(load, 30_000); return () => clearInterval(id); }, [load]); useEffect(() => { loadHistory(); }, [historyHours, loadHistory]); const totalKw = kpis?.total_power_kw ?? 0; const hallAKw = racks.find((r) => r.room_id === "hall-a")?.racks.reduce((s, r) => s + (r.power_kw ?? 0), 0) ?? 0; const hallBKw = racks.find((r) => r.room_id === "hall-b")?.racks.reduce((s, r) => s + (r.power_kw ?? 0), 0) ?? 0; const siteCapacity = capacity ? capacity.rooms.reduce((s, r) => s + r.power.capacity_kw, 0) : 0; // Phase summary data const allPhaseRacks = phases.flatMap(r => r.racks); const phaseViolations = allPhaseRacks.filter(r => (r.imbalance_pct ?? 0) > 5); return (

Power Management

Singapore DC01 — refreshes every 30s

{/* Internal anchor sub-nav */}
{/* Site capacity bar */}
{!loading && siteCapacity > 0 && ( )}
{/* KPIs */} {loading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : (
)} {/* Power path diagram */} {!loading && redundancy && (

Power Path

{[ { label: "Grid", icon: Zap, ok: redundancy.ats_active_feed !== "generator" }, { label: "ATS", icon: ArrowLeftRight, ok: true }, { label: "UPS", icon: Battery, ok: redundancy.ups_online > 0 }, { label: "Racks", icon: Server, ok: true }, ].map(({ label, icon: Icon, ok }, i, arr) => (
{label}
{i < arr.length - 1 && (
)} ))} {redundancy.ats_active_feed === "generator" && ( Running on generator )}
)} {/* Charts */}

Power History

{loading ? ( <> ) : ( <> {racks.length > 0 && } )}
{/* Redundancy banner */} {!loading && redundancy && } {/* UPS */}

UPS Units

{loading ? (
) : (
{ups.map((u) => ( setSelectedUps(u)} /> ))}
)}
{/* Generator */} {(loading || generators.length > 0) && (

Generators

{loading ? (
) : (
{generators.map((g) => )}
)}
)} {/* ATS */} {(loading || atsUnits.length > 0) && (

Transfer Switches

{loading ? (
) : (
{atsUnits.map((a) => )}
)}
)} {/* UPS detail sheet */} setSelectedUps(null)} /> {/* Phase analysis — always visible summary */}
{!loading && phases.length > 0 && (

Phase Analysis

{phaseViolations.length === 0 ? ( Phase balance OK ) : ( )}
{/* Phase summary row */}
{(["Phase A", "Phase B", "Phase C"] as const).map((phase, idx) => { const phaseKey = (["phase_a_kw", "phase_b_kw", "phase_c_kw"] as const)[idx]; const total = allPhaseRacks.reduce((s, r) => s + (r[phaseKey] ?? 0), 0); return (

{phase}

{total.toFixed(1)} kW

); })}
{/* Expanded violation table */} {phaseExpanded && phaseViolations.length > 0 && ( )}
)}
); }