"use client"; import { useEffect, useState, useCallback } from "react"; import { toast } from "sonner"; import { fetchGeneratorStatus, fetchAtsStatus, fetchPhaseBreakdown, type GeneratorStatus, type AtsStatus, type RoomPhase, } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Fuel, Zap, Activity, RefreshCw, CheckCircle2, AlertTriangle, ArrowLeftRight, Gauge, Thermometer, Battery, } from "lucide-react"; import { cn } from "@/lib/utils"; import { GeneratorDetailSheet } from "@/components/dashboard/generator-detail-sheet"; const SITE_ID = "sg-01"; const STATE_COLOR: Record = { running: "bg-green-500/10 text-green-400", standby: "bg-blue-500/10 text-blue-400", test: "bg-amber-500/10 text-amber-400", fault: "bg-destructive/10 text-destructive", unknown: "bg-muted/30 text-muted-foreground", }; const ATS_FEED_COLOR: Record = { "utility-a": "bg-blue-500/10 text-blue-400", "utility-b": "bg-sky-500/10 text-sky-400", "generator": "bg-amber-500/10 text-amber-400", }; function FillBar({ value, max, color = "#22c55e", warn, crit, }: { value: number | null; max: number; color?: string; warn?: number; crit?: number; }) { const pct = value != null ? Math.min(100, (value / max) * 100) : 0; const bg = crit && value != null && value >= crit ? "#ef4444" : warn && value != null && value >= warn ? "#f59e0b" : color; return (
); } function StatRow({ label, value, warn }: { label: string; value: string; warn?: boolean }) { return (
{label} {value}
); } function GeneratorCard({ gen, onClick }: { gen: GeneratorStatus; onClick: () => void }) { const fuelLow = (gen.fuel_pct ?? 100) < 25; const fuelCrit = (gen.fuel_pct ?? 100) < 10; const isFault = gen.state === "fault"; const isRun = gen.state === "running" || gen.state === "test"; return (
{gen.gen_id.toUpperCase()}
{gen.state}
{/* Fuel level */}
Fuel Level {gen.fuel_pct != null ? `${gen.fuel_pct.toFixed(1)}%` : "—"}
{gen.fuel_litres != null && (

{gen.fuel_litres.toFixed(0)} L remaining

)} {gen.fuel_litres != null && gen.load_kw != null && gen.load_kw > 0 && (() => { const runtimeH = gen.fuel_litres / (gen.load_kw * 0.27); const hours = Math.floor(runtimeH); const mins = Math.round((runtimeH - hours) * 60); const cls = runtimeH < 4 ? "text-destructive" : runtimeH < 12 ? "text-amber-400" : "text-green-400"; return (

Est. runtime: {hours}h {mins}m

); })()}
{/* Load */} {gen.load_kw != null && (
Load {gen.load_kw.toFixed(1)} kW {gen.load_pct != null && ( ({gen.load_pct.toFixed(0)}%) )}
)} {/* Engine stats */}

Engine

{gen.voltage_v != null && } {gen.frequency_hz != null && 0.5} />} {gen.run_hours != null && } {gen.oil_pressure_bar != null && } {gen.coolant_temp_c != null && (
Coolant temp 95 ? "text-destructive" : gen.coolant_temp_c > 85 ? "text-amber-400" : "")}> {gen.coolant_temp_c.toFixed(1)}°C
)} {gen.battery_v != null && (
Battery {gen.battery_v.toFixed(1)} V
)}
); } function AtsCard({ ats }: { ats: AtsStatus }) { const feedColor = ATS_FEED_COLOR[ats.active_feed] ?? "bg-muted/30 text-muted-foreground"; const isGen = ats.active_feed === "generator"; return ( {ats.ats_id.toUpperCase()} — ATS Transfer Switch
Active feed {ats.active_feed} {isGen && Running on generator power}
{[ { label: "Utility A", v: ats.utility_a_v }, { label: "Utility B", v: ats.utility_b_v }, { label: "Generator", v: ats.generator_v }, ].map(({ label, v }) => (

{label}

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

))}
{ats.transfer_count != null && (

Transfers (total)

{ats.transfer_count}

)} {ats.last_transfer_ms != null && (

Last transfer time

{ats.last_transfer_ms} ms

)}
); } function PhaseImbalancePanel({ rooms }: { rooms: RoomPhase[] }) { const allRacks = rooms.flatMap((r) => r.racks); const flagged = allRacks .filter((r) => (r.imbalance_pct ?? 0) >= 5) .sort((a, b) => (b.imbalance_pct ?? 0) - (a.imbalance_pct ?? 0)); if (flagged.length === 0) return (
No PDU phase imbalance detected across all racks
); return ( PDU Phase Imbalance
{flagged.map((rack) => { const crit = (rack.imbalance_pct ?? 0) >= 15; return (
{rack.rack_id.toUpperCase()} {rack.imbalance_pct?.toFixed(1)}% imbalance A: {rack.phase_a_kw?.toFixed(2) ?? "—"} kW B: {rack.phase_b_kw?.toFixed(2) ?? "—"} kW C: {rack.phase_c_kw?.toFixed(2) ?? "—"} kW
); })}
); } export default function GeneratorPage() { const [generators, setGenerators] = useState([]); const [atsUnits, setAtsUnits] = useState([]); const [phases, setPhases] = useState([]); const [loading, setLoading] = useState(true); const [selectedGen, setSelectedGen] = useState(null); const load = useCallback(async () => { try { const [g, a, p] = await Promise.all([ fetchGeneratorStatus(SITE_ID), fetchAtsStatus(SITE_ID).catch(() => [] as AtsStatus[]), fetchPhaseBreakdown(SITE_ID).catch(() => [] as RoomPhase[]), ]); setGenerators(g); setAtsUnits(a); setPhases(p); } catch { toast.error("Failed to load generator data"); } finally { setLoading(false); } }, []); useEffect(() => { load(); const id = setInterval(load, 15_000); return () => clearInterval(id); }, [load]); const anyFault = generators.some((g) => g.state === "fault"); const anyRun = generators.some((g) => g.state === "running" || g.state === "test"); const onGen = atsUnits.some((a) => a.active_feed === "generator"); return (
{/* Header */}

Generator & Power Path

Singapore DC01 — backup power systems · refreshes every 15s

{!loading && ( {anyFault ? <> Generator fault : onGen ? <> Running on generator : anyRun ? <> Generator running (test) : <> Utility power — all standby} )}
{/* Site power status bar */} {!loading && atsUnits.length > 0 && (
Power path: {onGen ? "Generator (utility lost)" : "Utility mains"}
Generators: {generators.length} total ({generators.filter((g) => g.state === "standby").length} standby,{" "} {generators.filter((g) => g.state === "running").length} running,{" "} {generators.filter((g) => g.state === "fault").length} fault)
)} {/* Generators */}

Diesel Generators

{loading ? (
) : generators.length === 0 ? (
No generator data available
) : (
{generators.map((g) => ( setSelectedGen(g.gen_id)} /> ))}
)} {/* ATS */}

Automatic Transfer Switches

{loading ? ( ) : atsUnits.length === 0 ? (
No ATS data available
) : (
{atsUnits.map((a) => )}
)} setSelectedGen(null)} /> {/* Phase imbalance */}

PDU Phase Balance

{loading ? ( ) : ( )}
); }