"use client"; import { useEffect, useState, useCallback } from "react"; import { toast } from "sonner"; import { fetchMaintenanceWindows, createMaintenanceWindow, deleteMaintenanceWindow, type MaintenanceWindow, } 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 { Button } from "@/components/ui/button"; import { CalendarClock, Plus, Trash2, CheckCircle2, Clock, AlertTriangle, BellOff, RefreshCw, X, } from "lucide-react"; import { cn } from "@/lib/utils"; const SITE_ID = "sg-01"; const TARGET_GROUPS = [ { label: "Site", targets: [{ value: "all", label: "Entire Site" }], }, { label: "Halls", targets: [ { value: "hall-a", label: "Hall A" }, { value: "hall-b", label: "Hall B" }, ], }, { label: "Racks — Hall A", targets: [ { value: "rack-A01", label: "Rack A01" }, { value: "rack-A02", label: "Rack A02" }, { value: "rack-A03", label: "Rack A03" }, { value: "rack-A04", label: "Rack A04" }, { value: "rack-A05", label: "Rack A05" }, ], }, { label: "Racks — Hall B", targets: [ { value: "rack-B01", label: "Rack B01" }, { value: "rack-B02", label: "Rack B02" }, { value: "rack-B03", label: "Rack B03" }, { value: "rack-B04", label: "Rack B04" }, { value: "rack-B05", label: "Rack B05" }, ], }, { label: "CRAC Units", targets: [ { value: "crac-01", label: "CRAC-01" }, { value: "crac-02", label: "CRAC-02" }, ], }, { label: "UPS", targets: [ { value: "ups-01", label: "UPS-01" }, { value: "ups-02", label: "UPS-02" }, ], }, { label: "Generator", targets: [ { value: "gen-01", label: "Generator GEN-01" }, ], }, ]; // Flat list for looking up labels const TARGETS_FLAT = TARGET_GROUPS.flatMap(g => g.targets); const statusCfg = { active: { label: "Active", cls: "bg-green-500/10 text-green-400 border-green-500/20", icon: CheckCircle2 }, scheduled: { label: "Scheduled", cls: "bg-blue-500/10 text-blue-400 border-blue-500/20", icon: Clock }, expired: { label: "Expired", cls: "bg-muted/50 text-muted-foreground border-border", icon: AlertTriangle }, }; function StatusChip({ status }: { status: MaintenanceWindow["status"] }) { const cfg = statusCfg[status]; const Icon = cfg.icon; return ( {cfg.label} ); } function formatDt(iso: string): string { return new Date(iso).toLocaleString([], { dateStyle: "short", timeStyle: "short" }); } // 7-day timeline strip const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; function TimelineStrip({ windows }: { windows: MaintenanceWindow[] }) { const relevant = windows.filter(w => w.status === "active" || w.status === "scheduled"); if (relevant.length === 0) return null; const today = new Date(); today.setHours(0, 0, 0, 0); const totalMs = 7 * 24 * 3600_000; // Day labels const days = Array.from({ length: 7 }, (_, i) => { const d = new Date(today.getTime() + i * 24 * 3600_000); return DAY_LABELS[d.getDay()]; }); return (

7-Day Maintenance Timeline

{/* Day column labels */}
{days.map((day, i) => (
{day}
))}
{/* Grid lines */}
{Array.from({ length: 8 }, (_, i) => (
))}
{/* Window bars */}
{relevant.map(w => { const startMs = Math.max(0, new Date(w.start_dt).getTime() - today.getTime()); const endMs = Math.min(totalMs, new Date(w.end_dt).getTime() - today.getTime()); if (endMs <= 0 || startMs >= totalMs) return null; const leftPct = (startMs / totalMs) * 100; const widthPct = ((endMs - startMs) / totalMs) * 100; const barCls = w.status === "active" ? "bg-green-500/30 border-green-500/50 text-green-300" : "bg-blue-500/20 border-blue-500/40 text-blue-300"; return (
{w.title}
); })}
); } // Defaults for new window form: now → +2h function defaultStart() { const d = new Date(); d.setSeconds(0, 0); return d.toISOString().slice(0, 16); } function defaultEnd() { const d = new Date(Date.now() + 2 * 3600_000); d.setSeconds(0, 0); return d.toISOString().slice(0, 16); } export default function MaintenancePage() { const [windows, setWindows] = useState([]); const [loading, setLoading] = useState(true); const [showForm, setShowForm] = useState(false); const [submitting, setSubmitting] = useState(false); const [deleting, setDeleting] = useState(null); // Form state const [title, setTitle] = useState(""); const [target, setTarget] = useState("all"); const [startDt, setStartDt] = useState(defaultStart); const [endDt, setEndDt] = useState(defaultEnd); const [suppress, setSuppress] = useState(true); const [notes, setNotes] = useState(""); const load = useCallback(async () => { try { const data = await fetchMaintenanceWindows(SITE_ID); setWindows(data); } catch { toast.error("Failed to load maintenance windows"); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); async function handleCreate(e: React.FormEvent) { e.preventDefault(); if (!title.trim()) return; setSubmitting(true); try { const targetLabel = TARGETS_FLAT.find(t => t.value === target)?.label ?? target; await createMaintenanceWindow({ site_id: SITE_ID, title: title.trim(), target, target_label: targetLabel, start_dt: new Date(startDt).toISOString(), end_dt: new Date(endDt).toISOString(), suppress_alarms: suppress, notes: notes.trim(), }); await load(); toast.success("Maintenance window created"); setShowForm(false); setTitle(""); setNotes(""); setStartDt(defaultStart()); setEndDt(defaultEnd()); } catch { toast.error("Failed to create maintenance window"); } finally { setSubmitting(false); } } async function handleDelete(id: string) { setDeleting(id); try { await deleteMaintenanceWindow(id); toast.success("Maintenance window deleted"); await load(); } catch { toast.error("Failed to delete maintenance window"); } finally { setDeleting(null); } } const active = windows.filter(w => w.status === "active").length; const scheduled = windows.filter(w => w.status === "scheduled").length; return (

Maintenance Windows

Singapore DC01 — planned outages & alarm suppression

{/* Summary */} {!loading && (
{active > 0 && ( {active} active )} {scheduled > 0 && ( {scheduled} scheduled )} {active === 0 && scheduled === 0 && ( No active or scheduled maintenance )}
)} {/* Create form */} {showForm && (
New Maintenance Window
setTitle(e.target.value)} placeholder="e.g. UPS-01 firmware update" className="w-full h-9 rounded-md border border-border bg-muted/30 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary" />
setStartDt(e.target.value)} className="w-full h-9 rounded-md border border-border bg-muted/30 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary" />
setEndDt(e.target.value)} className="w-full h-9 rounded-md border border-border bg-muted/30 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary" />