"use client"; import { useEffect, useState } from "react"; import { fetchGeneratorStatus, fetchGeneratorHistory, type GeneratorStatus, type GeneratorHistoryPoint, } from "@/lib/api"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; 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 { Fuel, Zap, Gauge, Thermometer, Wind, Activity, Battery, Settings2, } from "lucide-react"; import { cn } from "@/lib/utils"; interface Props { siteId: string; genId: 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; } } const STATE_BADGE: Record = { running: "bg-green-500/15 text-green-400 border-green-500/30", standby: "bg-blue-500/15 text-blue-400 border-blue-500/30", test: "bg-amber-500/15 text-amber-400 border-amber-500/30", fault: "bg-destructive/15 text-destructive border-destructive/30", unknown: "bg-muted/30 text-muted-foreground border-border", }; function FillBar({ value, max, color, warn, crit, invert = false, }: { value: number | null; max: number; color: string; warn?: number; crit?: number; invert?: boolean; }) { const pct = value != null ? Math.min(100, (value / max) * 100) : 0; const v = value ?? 0; const barColor = crit && (invert ? v <= crit : v >= crit) ? "#ef4444" : warn && (invert ? v <= warn : v >= warn) ? "#f59e0b" : color; return (
); } function SectionLabel({ icon: Icon, title }: { icon: React.ElementType; title: string }) { return (
{title}
); } function StatRow({ label, value, highlight, status, }: { label: string; value: string; highlight?: boolean; status?: "ok" | "warn" | "crit"; }) { return (
{label}
{value} {status && ( {status === "ok" ? "normal" : status} )}
); } function MiniChart({ data, dataKey, color, label, unit, refLine, }: { data: GeneratorHistoryPoint[]; dataKey: keyof GeneratorHistoryPoint; 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({ gen }: { gen: GeneratorStatus }) { const isRunning = gen.state === "running" || gen.state === "test"; const fuelLow = (gen.fuel_pct ?? 100) < 25; const fuelCrit = (gen.fuel_pct ?? 100) < 10; const loadWarn = (gen.load_pct ?? 0) > 75; const loadCrit = (gen.load_pct ?? 0) > 90; // Estimated runtime from fuel and consumption rate const runtimeH = gen.fuel_rate_lph && gen.fuel_rate_lph > 0 && gen.fuel_litres ? gen.fuel_litres / gen.fuel_rate_lph : gen.fuel_litres && gen.load_kw && gen.load_kw > 0 ? gen.fuel_litres / (gen.load_kw * 0.27) : null; // Computed electrical values const outputKva = gen.load_kw && gen.power_factor && gen.power_factor > 0 ? gen.load_kw / gen.power_factor : null; const outputCurrent = gen.load_kw && gen.voltage_v && gen.voltage_v > 0 && gen.power_factor ? (gen.load_kw * 1000) / (gen.voltage_v * 1.732 * gen.power_factor) : null; return (
{/* Fuel */}
Tank Level {fmt(gen.fuel_pct, 1)}%
{gen.fuel_litres != null ? `${gen.fuel_litres.toFixed(0)} L remaining` : "—"} {gen.fuel_rate_lph != null && gen.fuel_rate_lph > 0 && ( {fmt(gen.fuel_rate_lph, 1)} L/hr consumption )}
{runtimeH != null && (
Est. runtime: {Math.floor(runtimeH)}h {Math.round((runtimeH % 1) * 60)}m
)}
{/* Load */}
Active Load {fmt(gen.load_kw, 1)} kW
{fmt(gen.load_pct, 1)}% of 500 kW rated {outputKva != null && ( {outputKva.toFixed(1)} kVA apparent )}
{/* Quick stats */}
); } // ── Engine tab ──────────────────────────────────────────────────────────────── function EngineTab({ gen }: { gen: GeneratorStatus }) { const coolantWarn = (gen.coolant_temp_c ?? 0) > 85; const coolantCrit = (gen.coolant_temp_c ?? 0) > 95; const exhaustWarn = (gen.exhaust_temp_c ?? 0) > 420; const exhaustCrit = (gen.exhaust_temp_c ?? 0) > 480; const altTempWarn = (gen.alternator_temp_c ?? 0) > 70; const altTempCrit = (gen.alternator_temp_c ?? 0) > 85; const oilLow = (gen.oil_pressure_bar ?? 99) < 2.0; const oilWarn = (gen.oil_pressure_bar ?? 99) < 3.0; const battLow = (gen.battery_v ?? 99) < 23.0; const battWarn = (gen.battery_v ?? 99) < 24.0; const rpmWarn = gen.engine_rpm != null && gen.engine_rpm > 0 && Math.abs(gen.engine_rpm - 1500) > 20; return (

Mechanical health of the diesel engine. Normal operating range assumes a 50 Hz, 4-pole synchronous generator at 1500 RPM.

{/* Temperature gauges */}
Coolant {fmt(gen.coolant_temp_c, 1)}°C

Normal: 70–90°C

Exhaust Stack {fmt(gen.exhaust_temp_c, 0)}°C

Normal: 200–420°C at load

Alternator Windings {fmt(gen.alternator_temp_c, 1)}°C

Normal: 40–70°C at load

{/* Engine mechanical */}
0 ? `${gen.engine_rpm.toFixed(0)} RPM` : "— (stopped)"} status={rpmWarn ? "warn" : gen.engine_rpm && gen.engine_rpm > 0 ? "ok" : undefined} /> 0 ? `${gen.oil_pressure_bar.toFixed(2)} bar` : "— (stopped)"} status={oilLow ? "crit" : oilWarn ? "warn" : gen.oil_pressure_bar && gen.oil_pressure_bar > 0 ? "ok" : undefined} />
{/* Starter battery */}

Float charge: 27.2 V · Low threshold: 24 V · Critical: 23 V

); } // ── Electrical tab ──────────────────────────────────────────────────────────── function ElectricalTab({ gen }: { gen: GeneratorStatus }) { const freqWarn = gen.frequency_hz != null && gen.frequency_hz > 0 && Math.abs(gen.frequency_hz - 50) > 0.3; const freqCrit = gen.frequency_hz != null && gen.frequency_hz > 0 && Math.abs(gen.frequency_hz - 50) > 0.5; const voltWarn = gen.voltage_v != null && gen.voltage_v > 0 && Math.abs(gen.voltage_v - 415) > 10; const outputKva = gen.load_kw && gen.power_factor && gen.power_factor > 0 ? gen.load_kw / gen.power_factor : null; const outputKvar = outputKva && gen.load_kw ? Math.sqrt(Math.max(0, outputKva ** 2 - gen.load_kw ** 2)) : null; const outputCurrent = gen.load_kw && gen.voltage_v && gen.voltage_v > 0 && gen.power_factor ? (gen.load_kw * 1000) / (gen.voltage_v * 1.732 * gen.power_factor) : null; const phaseCurrentA = outputCurrent ? outputCurrent / 3 : null; return (

AC output electrical parameters. The generator feeds the ATS which transfers site load during utility failure. Rated 500 kW / 555 kVA at 0.90 PF, 415 V, 50 Hz.

{/* AC output hero */}
{[ { label: "Output Voltage", value: gen.voltage_v && gen.voltage_v > 0 ? `${gen.voltage_v.toFixed(0)} V` : "—", warn: voltWarn }, { label: "Frequency", value: gen.frequency_hz && gen.frequency_hz > 0 ? `${gen.frequency_hz.toFixed(2)} Hz` : "—", warn: freqWarn || freqCrit }, { label: "Power Factor", value: fmt(gen.power_factor, 3), warn: false }, ].map(({ label, value, warn }) => (

{label}

{value}

))}
Output Frequency {gen.frequency_hz && gen.frequency_hz > 0 ? `${gen.frequency_hz.toFixed(2)} Hz` : "—"}

Nominal 50 Hz · Grid tolerance ±0.5 Hz

); } // ── Trends tab ──────────────────────────────────────────────────────────────── function TrendsTab({ history }: { history: GeneratorHistoryPoint[] }) { if (history.length < 2) { return (

Not enough history yet — data accumulates every 5 minutes.

); } return (
); } // ── Sheet ───────────────────────────────────────────────────────────────────── export function GeneratorDetailSheet({ siteId, genId, onClose }: Props) { const [hours, setHours] = useState(6); const [status, setStatus] = useState(null); const [history, setHistory] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { if (!genId) return; setLoading(true); Promise.all([ fetchGeneratorStatus(siteId), fetchGeneratorHistory(siteId, genId, hours), ]).then(([statuses, hist]) => { setStatus(statuses.find(g => g.gen_id === genId) ?? null); setHistory(hist); }).finally(() => setLoading(false)); }, [siteId, genId, hours]); return ( { if (!open) onClose(); }}> {genId?.toUpperCase() ?? "Generator"} {status && ( {status.state} )} {status && (

Diesel generator · 500 kW rated · 2,000 L tank {status.run_hours != null ? ` · ${status.run_hours.toLocaleString()} run hours` : ""}

)}
{loading && !status ? (
{Array.from({ length: 8 }).map((_, i) => )}
) : status ? (
Overview Engine Electrical Trends
) : (

No data available for {genId}.

)}
); } const STATUS_BADGE_CLASS: Record = STATE_BADGE;