"use client";
import { useEffect, useState } from "react";
import { fetchCracStatus, fetchCracHistory, type CracStatus, type CracHistoryPoint } from "@/lib/api";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Skeleton } from "@/components/ui/skeleton";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { TimeRangePicker } from "@/components/ui/time-range-picker";
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip,
ResponsiveContainer, ReferenceLine,
} from "recharts";
import { Thermometer, Wind, Zap, Gauge, Settings2, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
interface Props {
siteId: string;
cracId: string | null;
onClose: () => void;
}
function fmt(v: number | null | undefined, dec = 1, unit = "") {
if (v == null) return "—";
return `${v.toFixed(dec)}${unit}`;
}
function formatTime(iso: string) {
try { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }
catch { return iso; }
}
function FillBar({
value, max, color, 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 barColor =
crit && value != null && value >= crit ? "#ef4444" :
warn && value != null && value >= warn ? "#f59e0b" :
color;
return (
);
}
function SectionLabel({ icon: Icon, title }: { icon: React.ElementType; title: string }) {
return (
{title}
);
}
function StatRow({ label, value, highlight }: { label: string; value: string; highlight?: boolean }) {
return (
{label}
{value}
);
}
function RefrigerantRow({ label, value, status }: { label: string; value: string; status: "ok" | "warn" | "crit" }) {
return (
{label}
{value}
{status === "ok" ? "normal" : status}
);
}
function MiniChart({
data, dataKey, color, label, unit, refLine,
}: {
data: CracHistoryPoint[];
dataKey: keyof CracHistoryPoint;
color: string;
label: string;
unit: string;
refLine?: number;
}) {
const vals = data.map(d => d[dataKey] as number | null).filter(v => v != null) as number[];
const last = vals[vals.length - 1];
return (
{label}
{last != null ? `${last.toFixed(1)}${unit}` : "—"}
[`${(v as number).toFixed(1)}${unit}`, label]}
labelFormatter={(l: unknown) => formatTime(String(l))}
contentStyle={{ fontSize: 11, background: "#1e1e2e", border: "1px solid #333" }}
/>
{refLine != null && (
)}
);
}
// ── Overview tab ──────────────────────────────────────────────────────────────
function OverviewTab({ status }: { status: CracStatus }) {
const deltaWarn = (status.delta ?? 0) > 11;
const deltaCrit = (status.delta ?? 0) > 14;
const capWarn = (status.cooling_capacity_pct ?? 0) > 75;
const capCrit = (status.cooling_capacity_pct ?? 0) > 90;
const copWarn = (status.cop ?? 99) < 1.5;
const filterWarn = (status.filter_dp_pa ?? 0) > 80;
const filterCrit = (status.filter_dp_pa ?? 0) > 120;
const compWarn = (status.compressor_load_pct ?? 0) > 95;
return (
{/* ── Thermal hero ──────────────────────────────────────────── */}
Supply
{fmt(status.supply_temp, 1)}°C
ΔT {fmt(status.delta, 1)}°C
Return
{fmt(status.return_temp, 1)}°C
Filter ΔP
{fmt(status.filter_dp_pa, 0)} Pa
{!filterWarn && ✓}
{/* ── Cooling capacity ──────────────────────────────────────── */}
{fmt(status.cooling_capacity_kw, 1)} kW
of {status.rated_capacity_kw} kW rated
{fmt(status.cooling_capacity_pct, 1)}% utilised
{/* ── Compressor ────────────────────────────────────────────── */}
Load
{status.compressor_state === 1 ? "● Running" : "○ Off"}
{fmt(status.compressor_load_pct, 1)}%
{/* ── Fan ───────────────────────────────────────────────────── */}
Speed
{fmt(status.fan_pct, 1)}%
{status.fan_rpm != null ? ` · ${Math.round(status.fan_rpm).toLocaleString()} rpm` : ""}
{/* ── Electrical ────────────────────────────────────────────── */}
);
}
// ── Refrigerant tab ───────────────────────────────────────────────────────────
function RefrigerantTab({ status }: { status: CracStatus }) {
const hiP = status.high_pressure_bar ?? 0;
const loP = status.low_pressure_bar ?? 99;
const sh = status.discharge_superheat_c ?? 0;
const sc = status.liquid_subcooling_c ?? 0;
const load = status.compressor_load_pct ?? 0;
// Normal ranges for R410A-style DX unit
const hiPStatus: "ok" | "warn" | "crit" = hiP > 22 ? "crit" : hiP > 20 ? "warn" : "ok";
const loPStatus: "ok" | "warn" | "crit" = loP < 3 ? "crit" : loP < 4 ? "warn" : "ok";
const shStatus: "ok" | "warn" | "crit" = sh > 16 ? "warn" : sh < 4 ? "warn" : "ok";
const scStatus: "ok" | "warn" | "crit" = sc < 2 ? "warn" : "ok";
const ldStatus: "ok" | "warn" | "crit" = load > 95 ? "warn" : "ok";
const compRunning = status.compressor_state === 1;
return (
Refrigerant circuit data for the DX cooling system. Pressures assume an R410A charge.
Values outside normal range are flagged automatically.
Compressor State
{compRunning ? "● Running" : "○ Off"}
Compressor Power
{fmt(status.compressor_power_kw, 2, " kW")}
Normal Ranges (R410A)
High side pressure: 15 – 20 bar
Low side pressure: 4 – 6 bar
Discharge superheat: 5 – 15°C
Liquid subcooling: 3 – 8°C
);
}
// ── Trends tab ────────────────────────────────────────────────────────────────
function TrendsTab({ history }: { history: CracHistoryPoint[] }) {
if (history.length < 2) {
return (
Not enough history yet — data accumulates every 5 minutes.
);
}
return (
);
}
// ── Sheet ─────────────────────────────────────────────────────────────────────
export function CracDetailSheet({ siteId, cracId, onClose }: Props) {
const [hours, setHours] = useState(6);
const [status, setStatus] = useState(null);
const [history, setHistory] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!cracId) return;
setLoading(true);
Promise.all([
fetchCracStatus(siteId),
fetchCracHistory(siteId, cracId, hours),
]).then(([statuses, hist]) => {
setStatus(statuses.find(c => c.crac_id === cracId) ?? null);
setHistory(hist);
}).finally(() => setLoading(false));
}, [siteId, cracId, hours]);
return (
{ if (!open) onClose(); }}>
{cracId?.toUpperCase() ?? "CRAC Unit"}
{status && (
{status.state}
)}
{status && (
{status.room_id ? `Room: ${status.room_id}` : ""}
{status.state === "online" ? " · Mode: Cooling · Setpoint 22°C" : ""}
)}
{loading && !status ? (
{Array.from({ length: 8 }).map((_, i) => (
))}
) : status ? (
Overview
Refrigerant
Trends
) : (
No data available for {cracId}.
)}
);
}