"use client"; import { useEffect, useState, useCallback } from "react"; import { toast } from "sonner"; import { fetchReportSummary, fetchKpis, fetchAlarmStats, fetchEnergyReport, reportExportUrl, type ReportSummary, type KpiData, type AlarmStats, type EnergyReport } from "@/lib/api"; import { PageShell } from "@/components/layout/page-shell"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Zap, Thermometer, Bell, AlertTriangle, CheckCircle2, Clock, Download, Wind, Battery, RefreshCw, Activity, DollarSign, } from "lucide-react"; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from "recharts"; import { cn } from "@/lib/utils"; const SITE_ID = "sg-01"; function UptimeBar({ pct }: { pct: number }) { const color = pct >= 99 ? "bg-green-500" : pct >= 95 ? "bg-amber-500" : "bg-destructive"; return (
= 99 ? "text-green-400" : pct >= 95 ? "text-amber-400" : "text-destructive" )}> {pct.toFixed(1)}%
); } function ExportCard({ hours, setHours }: { hours: number; setHours: (h: number) => void }) { const exports: { label: string; type: "power" | "temperature" | "alarms"; icon: React.ElementType }[] = [ { label: "Power History", type: "power", icon: Zap }, { label: "Temperature History", type: "temperature", icon: Thermometer }, { label: "Alarm Log", type: "alarms", icon: Bell }, ]; return (
Export Data
{([24, 48, 168] as const).map((h) => ( ))}
{exports.map(({ label, type, icon: Icon }) => (
{label} {type !== "alarms" && ( (last {hours === 168 ? "7 days" : `${hours}h`}) )}
))}

CSV format — 5-minute bucketed averages

); } const RANGE_HOURS: Record<"24h" | "7d" | "30d", number> = { "24h": 24, "7d": 168, "30d": 720 }; const RANGE_DAYS: Record<"24h" | "7d" | "30d", number> = { "24h": 1, "7d": 7, "30d": 30 }; export default function ReportsPage() { const [summary, setSummary] = useState(null); const [kpis, setKpis] = useState(null); const [alarmStats, setAlarmStats] = useState(null); const [energy, setEnergy] = useState(null); const [loading, setLoading] = useState(true); const [exportHours, setExportHours] = useState(720); const [dateRange, setDateRange] = useState<"24h" | "7d" | "30d">("30d"); const load = useCallback(async () => { try { const [s, k, a, e] = await Promise.all([ fetchReportSummary(SITE_ID), fetchKpis(SITE_ID), fetchAlarmStats(SITE_ID), fetchEnergyReport(SITE_ID, RANGE_DAYS[dateRange]).catch(() => null), ]); setSummary(s); setKpis(k); setAlarmStats(a); setEnergy(e); } catch { toast.error("Failed to load report data"); } finally { setLoading(false); } }, [dateRange]); useEffect(() => { load(); }, [load]); function handleRangeChange(r: "24h" | "7d" | "30d") { setDateRange(r); setExportHours(RANGE_HOURS[r]); } return (

Reports

Singapore DC01 — site summary & data exports

{/* Date range picker */}
{(["24h", "7d", "30d"] as const).map((r) => ( ))}
{/* Export always visible at top */} {loading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : !summary ? (
Unable to load report data.
) : ( <>

Generated {new Date(summary.generated_at).toLocaleString()} · Showing last {dateRange}

{/* KPI snapshot — expanded */} Site KPI Snapshot

Total Power

{summary.kpis.total_power_kw} kW

PUE

1.5 ? "text-amber-400" : "" )}> {kpis?.pue.toFixed(2) ?? "—"} target <1.4

Avg Temp

= 28 ? "text-destructive" : summary.kpis.avg_temperature >= 25 ? "text-amber-400" : "" )}> {summary.kpis.avg_temperature}°C

Active Alarms

0 ? "text-destructive" : "")}> {alarmStats?.active ?? "—"}

Critical

0 ? "text-destructive" : "")}> {alarmStats?.critical ?? "—"}

Resolved

{alarmStats?.resolved ?? "—"}

{/* Alarm breakdown */} Alarm Breakdown
{[ { label: "Active", value: summary.alarm_stats.active, icon: AlertTriangle, color: "text-destructive" }, { label: "Acknowledged", value: summary.alarm_stats.acknowledged, icon: Clock, color: "text-amber-400" }, { label: "Resolved", value: summary.alarm_stats.resolved, icon: CheckCircle2, color: "text-green-400" }, ].map(({ label, value, icon: Icon, color }) => (

0 && label === "Active" ? "text-destructive" : "")}>{value}

{label}

))}
By severity: {summary.alarm_stats.critical} critical {summary.alarm_stats.warning} warning
{/* CRAC uptime */} CRAC Uptime (last 24h) {summary.crac_uptime.map((crac) => (
{crac.crac_id.toUpperCase()} {crac.room_id}
))} {summary.crac_uptime.length === 0 && (

No CRAC data available

)}
{/* UPS uptime */} UPS Uptime (last 24h) {summary.ups_uptime.map((ups) => (
{ups.ups_id.toUpperCase()}
))} {summary.ups_uptime.length === 0 && (

No UPS data available

)}
{/* Energy cost section */} {energy && ( Energy Cost — Last {energy.period_days} Days

Total kWh

{energy.kwh_total.toFixed(0)}

{energy.from_date} → {energy.to_date}

Cost ({energy.currency})

${energy.cost_sgd.toLocaleString()}

@ ${energy.tariff_sgd_kwh}/kWh

Est. Annual kWh

{(energy.pue_trend.length > 0 ? energy.kwh_total / energy.period_days * 365 : 0).toFixed(0)}

at current pace

PUE (estimated)

1.5 ? "text-amber-400" : "")}>{energy.pue_estimated.toFixed(2)}

target < 1.4

{(() => { const trend = energy.pue_trend ?? []; const thisWeek = trend.slice(-7); const lastWeek = trend.slice(-14, -7); const thisKwh = thisWeek.reduce((s, d) => s + d.avg_it_kw * 24, 0); const lastKwh = lastWeek.reduce((s, d) => s + d.avg_it_kw * 24, 0); const kwhDelta = lastKwh > 0 ? ((thisKwh - lastKwh) / lastKwh * 100) : 0; const thisAvgKw = thisWeek.length > 0 ? thisWeek.reduce((s, d) => s + d.avg_it_kw, 0) / thisWeek.length : 0; const lastAvgKw = lastWeek.length > 0 ? lastWeek.reduce((s, d) => s + d.avg_it_kw, 0) / lastWeek.length : 0; const kwDelta = lastAvgKw > 0 ? ((thisAvgKw - lastAvgKw) / lastAvgKw * 100) : 0; if (thisWeek.length === 0 || lastWeek.length === 0) return null; return (
This week vs last week:
kWh: {thisKwh.toFixed(0)} 5 ? "text-destructive" : kwhDelta < -5 ? "text-green-400" : "text-muted-foreground" )}> ({kwhDelta > 0 ? "+" : ""}{kwhDelta.toFixed(1)}%)
Avg IT load: {thisAvgKw.toFixed(1)} kW 5 ? "text-amber-400" : kwDelta < -5 ? "text-green-400" : "text-muted-foreground" )}> ({kwDelta > 0 ? "+" : ""}{kwDelta.toFixed(1)}%)
based on last {thisWeek.length + lastWeek.length} days of data
); })()} {energy.pue_trend.length > 0 && ( <>

Daily IT Load (kW)

new Date(v).toLocaleDateString([], { month: "short", day: "numeric" })} /> [`${Number(v).toFixed(1)} kW`, "Avg IT Load"]} labelFormatter={(l) => new Date(l).toLocaleDateString()} /> )}
)} )}
); }