"use client"; import { useEffect, useState, useMemo } from "react"; import { toast } from "sonner"; import { fetchAssets, fetchAllDevices, fetchPduReadings, type AssetsData, type RackAsset, type CracAsset, type UpsAsset, type Device, type PduReading, } from "@/lib/api"; import { RackDetailSheet } from "@/components/dashboard/rack-detail-sheet"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Thermometer, Zap, Wind, Battery, AlertTriangle, CheckCircle2, HelpCircle, LayoutGrid, List, Download, } from "lucide-react"; import { cn } from "@/lib/utils"; const SITE_ID = "sg-01"; const roomLabels: Record = { "hall-a": "Hall A", "hall-b": "Hall B" }; // ── Status helpers ──────────────────────────────────────────────────────────── const statusStyles: Record = { ok: { dot: "bg-green-500", border: "border-green-500/20" }, warning: { dot: "bg-amber-500", border: "border-amber-500/30" }, critical: { dot: "bg-destructive", border: "border-destructive/30" }, unknown: { dot: "bg-muted", border: "border-border" }, }; const TYPE_STYLES: Record = { server: { dot: "bg-blue-400", label: "Server" }, switch: { dot: "bg-green-400", label: "Switch" }, patch_panel: { dot: "bg-slate-400", label: "Patch Panel" }, pdu: { dot: "bg-amber-400", label: "PDU" }, storage: { dot: "bg-purple-400", label: "Storage" }, firewall: { dot: "bg-red-400", label: "Firewall" }, kvm: { dot: "bg-teal-400", label: "KVM" }, }; // ── Compact CRAC row ────────────────────────────────────────────────────────── function CracRow({ crac }: { crac: CracAsset }) { const online = crac.state === "online"; const fault = crac.state === "fault"; return (
{crac.crac_id.toUpperCase()} {fault ? : online ? : } {fault ? "Fault" : online ? "Online" : "Unk"} Supply: {crac.supply_temp !== null ? `${crac.supply_temp}°C` : "—"} Return: {crac.return_temp !== null ? `${crac.return_temp}°C` : "—"} Fan: {crac.fan_pct !== null ? `${crac.fan_pct}%` : "—"}
); } // ── Compact UPS row ─────────────────────────────────────────────────────────── function UpsRow({ ups }: { ups: UpsAsset }) { const onBattery = ups.state === "battery"; return (
{ups.ups_id.toUpperCase()} {onBattery ? : } {onBattery ? "Battery" : ups.state === "online" ? "Mains" : "Unk"} Charge: {ups.charge_pct !== null ? `${ups.charge_pct}%` : "—"} Load: {ups.load_pct !== null ? `${ups.load_pct}%` : "—"}
); } // ── Rack sortable table ─────────────────────────────────────────────────────── type RackSortCol = "rack_id" | "temp" | "power_kw" | "power_pct" | "alarm_count" | "status"; type SortDir = "asc" | "desc"; function RackTable({ racks, roomId, statusFilter, onRackClick, }: { racks: RackAsset[]; roomId: string; statusFilter: "all" | "warning" | "critical"; onRackClick: (id: string) => void; }) { const [sortCol, setSortCol] = useState("rack_id"); const [sortDir, setSortDir] = useState("asc"); function toggleSort(col: RackSortCol) { if (sortCol === col) setSortDir(d => d === "asc" ? "desc" : "asc"); else { setSortCol(col); setSortDir("asc"); } } function SortIcon({ col }: { col: RackSortCol }) { if (sortCol !== col) return ; return {sortDir === "asc" ? "↑" : "↓"}; } const filtered = useMemo(() => { const base = statusFilter === "all" ? racks : racks.filter(r => r.status === statusFilter); return [...base].sort((a, b) => { let cmp = 0; if (sortCol === "temp" || sortCol === "power_kw" || sortCol === "alarm_count") { cmp = ((a[sortCol] ?? 0) as number) - ((b[sortCol] ?? 0) as number); } else if (sortCol === "power_pct") { const aP = a.power_kw !== null ? a.power_kw / 10 * 100 : 0; const bP = b.power_kw !== null ? b.power_kw / 10 * 100 : 0; cmp = aP - bP; } else { cmp = String(a[sortCol]).localeCompare(String(b[sortCol])); } return sortDir === "asc" ? cmp : -cmp; }); }, [racks, statusFilter, sortCol, sortDir]); type ColDef = { col: RackSortCol; label: string }; const cols: ColDef[] = [ { col: "rack_id", label: "Rack ID" }, { col: "temp", label: "Temp (°C)" }, { col: "power_kw", label: "Power (kW)" }, { col: "power_pct", label: "Power%" }, { col: "alarm_count", label: "Alarms" }, { col: "status", label: "Status" }, ]; return (
{cols.map(({ col, label }) => ( ))} {/* Room column header */} {filtered.length === 0 ? ( ) : ( filtered.map(rack => { const powerPct = rack.power_kw !== null ? (rack.power_kw / 10) * 100 : null; const tempCls = rack.temp !== null ? rack.temp >= 30 ? "text-destructive" : rack.temp >= 28 ? "text-amber-400" : "" : ""; const pctCls = powerPct !== null ? powerPct >= 85 ? "text-destructive" : powerPct >= 75 ? "text-amber-400" : "" : ""; const s = statusStyles[rack.status] ?? statusStyles.unknown; return ( onRackClick(rack.rack_id)} className="border-b border-border/40 last:border-0 hover:bg-muted/20 transition-colors cursor-pointer" > ); }) )}
Room
No racks matching this filter
{rack.rack_id.toUpperCase()} {rack.temp !== null ? rack.temp : "—"} {rack.power_kw !== null ? rack.power_kw : "—"} {powerPct !== null ? `${powerPct.toFixed(0)}%` : "—"} {rack.alarm_count > 0 ? {rack.alarm_count} : 0}
{rack.status}
{roomLabels[roomId] ?? roomId}
); } // ── Inventory table ─────────────────────────────────────────────────────────── type SortCol = "name" | "type" | "rack_id" | "room_id" | "u_start" | "power_draw_w"; function InventoryTable({ siteId, onRackClick }: { siteId: string; onRackClick: (rackId: string) => void }) { const [devices, setDevices] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState("all"); const [roomFilter, setRoomFilter] = useState("all"); const [sortCol, setSortCol] = useState("name"); const [sortDir, setSortDir] = useState("asc"); useEffect(() => { fetchAllDevices(siteId) .then(setDevices) .catch(() => {}) .finally(() => setLoading(false)); }, [siteId]); function toggleSort(col: SortCol) { if (sortCol === col) setSortDir(d => d === "asc" ? "desc" : "asc"); else { setSortCol(col); setSortDir("asc"); } } function SortIcon({ col }: { col: SortCol }) { if (sortCol !== col) return ; return {sortDir === "asc" ? "↑" : "↓"}; } const filtered = useMemo(() => { const q = search.toLowerCase(); const base = devices.filter(d => { if (typeFilter !== "all" && d.type !== typeFilter) return false; if (roomFilter !== "all" && d.room_id !== roomFilter) return false; if (q && !d.name.toLowerCase().includes(q) && !d.rack_id.includes(q) && !d.ip.includes(q) && !d.serial.toLowerCase().includes(q)) return false; return true; }); return [...base].sort((a, b) => { let cmp = 0; if (sortCol === "power_draw_w" || sortCol === "u_start") { cmp = (a[sortCol] ?? 0) - (b[sortCol] ?? 0); } else { cmp = String(a[sortCol]).localeCompare(String(b[sortCol])); } return sortDir === "asc" ? cmp : -cmp; }); }, [devices, search, typeFilter, roomFilter, sortCol, sortDir]); const totalPower = filtered.reduce((s, d) => s + d.power_draw_w, 0); const types = Array.from(new Set(devices.map(d => d.type))).sort(); function downloadCsv() { const headers = ["Device", "Type", "Rack", "Room", "U Start", "U Height", "IP", "Serial", "Power (W)", "Status"]; const rows = filtered.map((d) => [ d.name, TYPE_STYLES[d.type]?.label ?? d.type, d.rack_id.toUpperCase(), roomLabels[d.room_id] ?? d.room_id, d.u_start, d.u_height, d.ip !== "-" ? d.ip : "", d.serial, d.power_draw_w, d.status, ]); const csv = [headers, ...rows] .map((r) => r.map((v) => `"${String(v ?? "").replace(/"/g, '""')}"`).join(",")) .join("\n"); const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = Object.assign(document.createElement("a"), { href: url, download: `bms-inventory-${new Date().toISOString().slice(0, 10)}.csv`, }); document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("Export downloaded"); } if (loading) { return (
{Array.from({ length: 8 }).map((_, i) => )}
); } return (
{/* Device type legend */}
{Object.entries(TYPE_STYLES).map(([key, { dot, label }]) => (
{label}
))}
{/* Filters */}
setSearch(e.target.value)} className="flex-1 min-w-48 h-8 rounded-md border border-border bg-muted/30 px-3 text-xs focus:outline-none focus:ring-1 focus:ring-primary" /> {filtered.length} devices · {(totalPower / 1000).toFixed(1)} kW
{/* Table */}
{([ { col: "name" as SortCol, label: "Device", cls: "text-left px-3 py-2" }, { col: "type" as SortCol, label: "Type", cls: "text-left px-3 py-2" }, { col: "rack_id" as SortCol, label: "Rack", cls: "text-left px-3 py-2" }, { col: "room_id" as SortCol, label: "Room", cls: "text-left px-3 py-2 hidden sm:table-cell" }, { col: "u_start" as SortCol, label: "U", cls: "text-left px-3 py-2 hidden md:table-cell" }, ]).map(({ col, label, cls }) => ( ))} {filtered.length === 0 ? ( ) : ( filtered.map(d => { const ts = TYPE_STYLES[d.type]; return ( onRackClick(d.rack_id)}> ); }) )}
IP Status Lifecycle
No devices match your filters.
{d.name}
{d.serial}
{ts?.label ?? d.type}
{d.rack_id.toUpperCase()} {roomLabels[d.room_id] ?? d.room_id} U{d.u_start}{d.u_height > 1 ? `–U${d.u_start + d.u_height - 1}` : ""} {d.ip !== "-" ? d.ip : "—"} {d.power_draw_w} W ● online {d.status === "online" ? "Active" : d.status === "offline" ? "Offline" : "Unknown"}
); } // ── PDU Monitoring ──────────────────────────────────────────────────────────── function PduMonitoringSection({ siteId }: { siteId: string }) { const [pdus, setPdus] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchPduReadings(siteId) .then(setPdus) .catch(() => {}) .finally(() => setLoading(false)); const id = setInterval(() => fetchPduReadings(siteId).then(setPdus).catch(() => {}), 30_000); return () => clearInterval(id); }, [siteId]); const critical = pdus.filter(p => p.status === "critical").length; const warning = pdus.filter(p => p.status === "warning").length; return (
PDU Phase Monitoring
{critical > 0 && {critical} critical} {warning > 0 && {warning} warning} {critical === 0 && warning === 0 && !loading && ( All balanced )}
{loading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : (
{pdus.map(p => ( ))}
Rack Room Total kW Ph-A kW Ph-B kW Ph-C kW Ph-A A Ph-B A Ph-C A Imbalance
{p.rack_id.toUpperCase().replace("RACK-", "")} {roomLabels[p.room_id] ?? p.room_id} {p.total_kw !== null ? p.total_kw.toFixed(2) : "—"} {p.phase_a_kw !== null ? p.phase_a_kw.toFixed(2) : "—"} {p.phase_b_kw !== null ? p.phase_b_kw.toFixed(2) : "—"} {p.phase_c_kw !== null ? p.phase_c_kw.toFixed(2) : "—"} {p.phase_a_a !== null ? p.phase_a_a.toFixed(1) : "—"} {p.phase_b_a !== null ? p.phase_b_a.toFixed(1) : "—"} {p.phase_c_a !== null ? p.phase_c_a.toFixed(1) : "—"} {p.imbalance_pct !== null ? ( {p.imbalance_pct.toFixed(1)}% ) : "—"}
)}
); } // ── Page ────────────────────────────────────────────────────────────────────── export default function AssetsPage() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [selectedRack, setSelectedRack] = useState(null); const [statusFilter, setStatusFilter] = useState<"all" | "warning" | "critical">("all"); const [view, setView] = useState<"grid" | "inventory">("grid"); async function load() { try { const d = await fetchAssets(SITE_ID); setData(d); setError(false); } catch { setError(true); toast.error("Failed to load asset data"); } finally { setLoading(false); } } useEffect(() => { load(); const id = setInterval(load, 30_000); return () => clearInterval(id); }, []); if (loading) { return (
{Array.from({ length: 10 }).map((_, i) => )}
); } if (error || !data) { return (
Unable to load asset data.
); } const defaultTab = data.rooms[0]?.room_id ?? ""; const totalRacks = data.rooms.reduce((s, r) => s + r.racks.length, 0); const critCount = data.rooms.flatMap(r => r.racks).filter(r => r.status === "critical").length; const warnCount = data.rooms.flatMap(r => r.racks).filter(r => r.status === "warning").length; return (
{/* Header */}

Asset Registry

Singapore DC01 · {totalRacks} racks {critCount > 0 && · {critCount} critical} {warnCount > 0 && · {warnCount} warning}

{/* View toggle */}
setSelectedRack(null)} /> {view === "inventory" ? ( ) : ( <> {/* Compact UPS + CRAC rows */}
{data.ups_units.map(ups => )} {data.rooms.map(room => )}
{/* PDU phase monitoring */} {/* Per-room rack table */} {data.rooms.map(room => ( {roomLabels[room.room_id] ?? room.room_id} ))} {data.rooms.map(room => { const rWarn = room.racks.filter(r => r.status === "warning").length; const rCrit = room.racks.filter(r => r.status === "critical").length; return (

Racks — {roomLabels[room.room_id] ?? room.room_id}

{(["all", "warning", "critical"] as const).map(f => ( ))}
); })}
)}
); }