"use client"; import { useEffect, useState, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Zap, Thermometer, Wind, AlertTriangle, Wifi, WifiOff, Fuel, Droplets } from "lucide-react"; import { KpiCard } from "@/components/dashboard/kpi-card"; import { PowerTrendChart } from "@/components/dashboard/power-trend-chart"; import { TemperatureTrendChart } from "@/components/dashboard/temperature-trend-chart"; import { AlarmFeed } from "@/components/dashboard/alarm-feed"; import { MiniFloorMap } from "@/components/dashboard/mini-floor-map"; import { RackDetailSheet } from "@/components/dashboard/rack-detail-sheet"; import { fetchKpis, fetchPowerHistory, fetchTempHistory, fetchAlarms, fetchGeneratorStatus, fetchLeakStatus, fetchCapacitySummary, fetchFloorLayout, type KpiData, type PowerBucket, type TempBucket, type Alarm, type GeneratorStatus, type LeakSensorStatus, type RackCapacity, } from "@/lib/api"; import { TimeRangePicker } from "@/components/ui/time-range-picker"; import Link from "next/link"; import { PageShell } from "@/components/layout/page-shell"; const SITE_ID = "sg-01"; const KPI_INTERVAL = 15_000; const CHART_INTERVAL = 30_000; // Fallback static data shown when the API is unreachable const FALLBACK_KPIS: KpiData = { total_power_kw: 0, pue: 0, avg_temperature: 0, active_alarms: 0, }; export default function DashboardPage() { const router = useRouter(); const [kpis, setKpis] = useState(FALLBACK_KPIS); const [prevKpis, setPrevKpis] = useState(null); const [powerHistory, setPowerHistory] = useState([]); const [tempHistory, setTempHistory] = useState([]); const [alarms, setAlarms] = useState([]); const [generators, setGenerators] = useState([]); const [leakSensors, setLeakSensors] = useState([]); const [mapRacks, setMapRacks] = useState([]); const [mapLayout, setMapLayout] = useState | null>(null); const [chartHours, setChartHours] = useState(1); const [loading, setLoading] = useState(true); const [liveError, setLiveError] = useState(false); const [lastUpdated, setLastUpdated] = useState(null); const [selectedRack, setSelectedRack] = useState(null); const refreshKpis = useCallback(async () => { try { const [k, a, g, l, cap] = await Promise.all([ fetchKpis(SITE_ID), fetchAlarms(SITE_ID), fetchGeneratorStatus(SITE_ID).catch(() => []), fetchLeakStatus(SITE_ID).catch(() => []), fetchCapacitySummary(SITE_ID).catch(() => null), ]); setKpis((current) => { if (current !== FALLBACK_KPIS) setPrevKpis(current); return k; }); setAlarms(a); setGenerators(g); setLeakSensors(l); if (cap) setMapRacks(cap.racks); setLiveError(false); setLastUpdated(new Date()); } catch { setLiveError(true); } }, []); const refreshCharts = useCallback(async () => { try { const [p, t] = await Promise.all([ fetchPowerHistory(SITE_ID, chartHours), fetchTempHistory(SITE_ID, chartHours), ]); setPowerHistory(p); setTempHistory(t); } catch { // keep previous chart data on failure } }, []); // Initial load useEffect(() => { Promise.all([refreshKpis(), refreshCharts()]).finally(() => setLoading(false)); fetchFloorLayout(SITE_ID) .then(l => setMapLayout(l as typeof mapLayout)) .catch(() => {}); }, [refreshKpis, refreshCharts]); // Re-fetch charts when time range changes useEffect(() => { refreshCharts(); }, [chartHours, refreshCharts]); // Polling useEffect(() => { const kpiTimer = setInterval(refreshKpis, KPI_INTERVAL); const chartTimer = setInterval(refreshCharts, CHART_INTERVAL); return () => { clearInterval(kpiTimer); clearInterval(chartTimer); }; }, [refreshKpis, refreshCharts]); function handleAlarmClick(alarm: Alarm) { if (alarm.rack_id) { setSelectedRack(alarm.rack_id); } else if (alarm.room_id) { router.push("/environmental"); } else { router.push("/alarms"); } } // Derived KPI display values const alarmStatus = kpis.active_alarms === 0 ? "ok" : kpis.active_alarms <= 2 ? "warning" : "critical"; const tempStatus = kpis.avg_temperature === 0 ? "ok" : kpis.avg_temperature >= 28 ? "critical" : kpis.avg_temperature >= 25 ? "warning" : "ok"; // Trends vs previous poll const powerTrend = prevKpis ? Math.round((kpis.total_power_kw - prevKpis.total_power_kw) * 10) / 10 : null; const tempTrend = prevKpis ? Math.round((kpis.avg_temperature - prevKpis.avg_temperature) * 10) / 10 : null; const alarmTrend = prevKpis ? kpis.active_alarms - prevKpis.active_alarms : null; // Generator derived const gen = generators[0] ?? null; const genFuel = gen?.fuel_pct ?? null; const genState = gen?.state ?? "unknown"; const genStatus: "ok" | "warning" | "critical" = genState === "fault" ? "critical" : genState === "running" ? "warning" : genFuel !== null && genFuel < 25 ? "warning" : "ok"; // Leak derived const activeLeaks = leakSensors.filter(s => s.state === "detected").length; const leakStatus: "ok" | "warning" | "critical" = activeLeaks > 0 ? "critical" : "ok"; return ( setSelectedRack(null)} /> {/* Live status bar */}
{liveError ? ( <> Live data unavailable ) : ( <> Live · updates every 15s )}
{lastUpdated && ( Last updated {lastUpdated.toLocaleTimeString()} )}
{/* Unified KPI grid — 3×2 on desktop */}
0 ? "+" : ""}${powerTrend} kW` : undefined} href="/power" /> 0 ? "+" : ""}${tempTrend}°C` : undefined} trendInvert href="/environmental" /> 0 ? "+" : ""}${alarmTrend}` : undefined} trendInvert href="/alarms" /> 0 ? `${activeLeaks} active` : "All clear"} hint={activeLeaks > 0 ? "Water detected — investigate immediately" : `${leakSensors.length} sensors monitoring`} icon={Droplets} iconColor={leakStatus === "critical" ? "text-destructive" : "text-blue-400"} status={loading ? "ok" : leakStatus} loading={loading} href="/environmental" />
{/* Charts row */}

Trends

{/* Bottom row — 50/50 */}
); }