"use client"; import { useEffect, useState, useCallback } from "react"; import { toast } from "sonner"; import { fetchCracStatus, fetchChillerStatus, type CracStatus, type ChillerStatus } from "@/lib/api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { CracDetailSheet } from "@/components/dashboard/crac-detail-sheet"; import { Wind, AlertTriangle, CheckCircle2, Zap, ChevronRight, ArrowRight, Waves, Filter, ChevronUp, ChevronDown, } from "lucide-react"; import { cn } from "@/lib/utils"; const SITE_ID = "sg-01"; const roomLabels: Record = { "hall-a": "Hall A", "hall-b": "Hall B" }; function fmt(v: number | null | undefined, dec = 1, unit = "") { if (v == null) return "—"; return `${v.toFixed(dec)}${unit}`; } function FillBar({ value, max, color, warn, crit, height = "h-2", }: { value: number | null; max: number; color: string; warn?: number; crit?: number; height?: string; }) { const pct = value != null ? Math.min(100, (value / max) * 100) : 0; const barColor = crit && value != null && value >= crit ? "#ef4444" : warn && value != null && value >= warn ? "#f59e0b" : color; return (
); } function KpiTile({ label, value, sub, warn }: { label: string; value: string; sub?: string; warn?: boolean; }) { return (

{label}

{value}

{sub &&

{sub}

}
); } function CracCard({ crac, onOpen }: { crac: CracStatus; onOpen: () => void }) { const [showCompressor, setShowCompressor] = useState(false); const online = crac.state === "online"; const deltaWarn = (crac.delta ?? 0) > 11; const deltaCrit = (crac.delta ?? 0) > 14; const capWarn = (crac.cooling_capacity_pct ?? 0) > 75; const capCrit = (crac.cooling_capacity_pct ?? 0) > 90; const copWarn = (crac.cop ?? 99) < 1.5; const filterWarn = (crac.filter_dp_pa ?? 0) > 80; const filterCrit = (crac.filter_dp_pa ?? 0) > 120; const compWarn = (crac.compressor_load_pct ?? 0) > 95; const hiPWarn = (crac.high_pressure_bar ?? 0) > 22; const loPWarn = (crac.low_pressure_bar ?? 99) < 3; return ( {/* ── Header ───────────────────────────────────────────────── */}
{crac.crac_id.toUpperCase()} {crac.room_id && (

{roomLabels[crac.room_id] ?? crac.room_id}

)}
{online && ( {deltaCrit || capCrit ? "Critical" : deltaWarn || capWarn || filterWarn || copWarn ? "Warning" : "Normal"} )} {online ? <> Online : <> Fault}
{!online ? (
Unit offline — cooling capacity in this room is degraded.
) : ( <> {/* ── Thermal hero ─────────────────────────────────────── */}

Supply

{fmt(crac.supply_temp, 1)}°C

ΔT {fmt(crac.delta, 1)}°C

Return

{fmt(crac.return_temp, 1)}°C

{/* ── Cooling capacity ─────────────────────────────────── */}
Cooling Capacity {fmt(crac.cooling_capacity_kw, 1)} / {crac.rated_capacity_kw} kW · COP {fmt(crac.cop, 2)}

{fmt(crac.cooling_capacity_pct, 1)}% utilised

{/* ── Fan + Filter ─────────────────────────────────────── */}
Fan {fmt(crac.fan_pct, 1)}% {crac.fan_rpm != null ? ` · ${Math.round(crac.fan_rpm).toLocaleString()} rpm` : ""}
Filter ΔP {fmt(crac.filter_dp_pa, 0)} Pa {!filterWarn && }
{/* ── Compressor (collapsible) ─────────────────────────── */}
{showCompressor && (
Hi {fmt(crac.high_pressure_bar, 1)} bar Lo {fmt(crac.low_pressure_bar, 2)} bar SH {fmt(crac.discharge_superheat_c, 1)}°C SC {fmt(crac.liquid_subcooling_c, 1)}°C
)}
{/* ── Electrical (one line) ────────────────────────────── */}
{fmt(crac.total_unit_power_kw, 2)} kW · {fmt(crac.input_voltage_v, 0)} V · {fmt(crac.input_current_a, 1)} A · PF {fmt(crac.power_factor, 3)}
{/* ── Status banner ────────────────────────────────────── */}
{deltaCrit || capCrit ? "Heat load is high — check airflow or redistribute rack density." : deltaWarn || capWarn ? "Heat load is elevated — monitor for further rises." : filterWarn ? "Filter requires attention — airflow may be restricted." : copWarn ? "Running inefficiently — check refrigerant charge." : "Operating efficiently within normal parameters."}
)} ); } // ── Filter replacement estimate ──────────────────────────────────── // Assumes ~1.2 Pa/day rate of rise — replace at 120 Pa threshold const FILTER_REPLACE_PA = 120; const FILTER_RATE_PA_DAY = 1.2; function FilterEstimate({ cracs }: { cracs: CracStatus[] }) { const units = cracs .filter((c) => c.state === "online" && c.filter_dp_pa != null) .map((c) => { const dp = c.filter_dp_pa!; const days = Math.max(0, Math.round((FILTER_REPLACE_PA - dp) / FILTER_RATE_PA_DAY)); const urgent = dp >= 120; const warn = dp >= 80; return { crac_id: c.crac_id, dp, days, urgent, warn }; }) .sort((a, b) => a.days - b.days); if (units.length === 0) return null; const anyUrgent = units.some((u) => u.urgent); const anyWarn = units.some((u) => u.warn); return (
Predictive Filter Replacement {anyUrgent ? "Overdue" : anyWarn ? "Attention needed" : "All filters OK"}
{units.map((u) => (
{u.crac_id.toUpperCase()}
{u.dp} Pa {u.urgent ? "Replace now" : `~${u.days}d`}
))}

Estimated at {FILTER_RATE_PA_DAY} Pa/day increase · replace at {FILTER_REPLACE_PA} Pa threshold

); } // ── Chiller card ────────────────────────────────────────────────── function ChillerCard({ chiller }: { chiller: ChillerStatus }) { const online = chiller.state === "online"; const loadWarn = (chiller.cooling_load_pct ?? 0) > 80; return (
{chiller.chiller_id.toUpperCase()} — Chiller Plant {online ? <> Online : <> Fault}
{!online ? (
Chiller fault — CHW supply lost. CRAC/CRAH units relying on local refrigerant circuits only.
) : ( <> {/* CHW temps */}

CHW Supply

{fmt(chiller.chw_supply_c, 1)}°C

ΔT {fmt(chiller.chw_delta_c, 1)}°C

CHW Return

{fmt(chiller.chw_return_c, 1)}°C

{/* Load */}
Cooling Load {fmt(chiller.cooling_load_kw, 1)} kW · COP {fmt(chiller.cop, 2)}

{fmt(chiller.cooling_load_pct, 1)}% load

{/* Details */}
Flow rate{fmt(chiller.flow_gpm, 0)} GPM
Comp load{fmt(chiller.compressor_load_pct, 1)}%
Cond press{fmt(chiller.condenser_pressure_bar, 2)} bar
Evap press{fmt(chiller.evaporator_pressure_bar, 2)} bar
CW supply{fmt(chiller.cw_supply_c, 1)}°C
CW return{fmt(chiller.cw_return_c, 1)}°C
Run hours: {chiller.run_hours != null ? chiller.run_hours.toFixed(0) : "—"} h
)} ); } // ── Page ────────────────────────────────────────────────────────────────────── export default function CoolingPage() { const [cracs, setCracs] = useState([]); const [chillers, setChillers] = useState([]); const [loading, setLoading] = useState(true); const [selectedCrac, setSelected] = useState(null); const load = useCallback(async () => { try { const [c, ch] = await Promise.all([ fetchCracStatus(SITE_ID), fetchChillerStatus(SITE_ID).catch(() => []), ]); setCracs(c); setChillers(ch); } catch { toast.error("Failed to load cooling data"); } finally { setLoading(false); } }, []); useEffect(() => { load(); const id = setInterval(load, 30_000); return () => clearInterval(id); }, [load]); const online = cracs.filter(c => c.state === "online"); const anyFaulted = cracs.some(c => c.state === "fault"); const totalCoolingKw = online.reduce((s, c) => s + (c.cooling_capacity_kw ?? 0), 0); const totalRatedKw = cracs.reduce((s, c) => s + (c.rated_capacity_kw ?? 0), 0); const copUnits = online.filter(c => c.cop != null); const avgCop = copUnits.length > 0 ? copUnits.reduce((s, c) => s + (c.cop ?? 0), 0) / copUnits.length : null; const totalUnitPower = online.reduce((s, c) => s + (c.total_unit_power_kw ?? 0), 0); const totalAirflowCfm = online.reduce((s, c) => s + (c.airflow_cfm ?? 0), 0); return (
{/* ── Page header ───────────────────────────────────────────── */}

Cooling Systems

Singapore DC01 · click a unit to drill down · refreshes every 30s

{!loading && ( {anyFaulted ? <> Cooling fault detected : <> All {cracs.length} units operational} )}
{/* ── Filter alert banner ───────────────────────────────────── */} {!loading && (() => { const urgent = cracs .filter(c => c.state === "online" && c.filter_dp_pa != null) .map(c => ({ id: c.crac_id, days: Math.max(0, Math.round((120 - c.filter_dp_pa!) / 1.2)) })) .filter(c => c.days < 14) .sort((a, b) => a.days - b.days); if (urgent.length === 0) return null; return (
Filter replacement due:{" "} {urgent.map(u => `${u.id.toUpperCase()} in ${u.days === 0 ? "now" : `~${u.days}d`}`).join(", ")}
); })()} {/* ── Fleet summary KPI cards ───────────────────────────────── */} {loading && (
{Array.from({ length: 5 }).map((_, i) => )}
)} {!loading && cracs.length > 0 && (

Cooling Load

{totalCoolingKw.toFixed(1)} kW

of {totalRatedKw} kW rated

Avg COP

{avgCop != null ? avgCop.toFixed(2) : "—"}

Unit Power Draw

{totalUnitPower.toFixed(1)} kW

total electrical input

Units Online

{online.length} / {cracs.length}

{totalAirflowCfm > 0 && (

Total Airflow

{Math.round(totalAirflowCfm).toLocaleString()}

CFM combined output

)}
)} {/* ── Chiller plant ─────────────────────────────────────────── */} {(loading || chillers.length > 0) && ( <>

Chiller Plant

{loading ? ( ) : (
{chillers.map(ch => )}
)} )} {/* ── Filter health (moved before CRAC cards) ───────────────── */} {!loading && } {/* ── CRAC cards ────────────────────────────────────────────── */}

CRAC / CRAH Units

{loading ? (
) : (
{cracs.map(crac => ( setSelected(crac.crac_id)} /> ))}
)} setSelected(null)} />
); }