"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 (
);
})}
);
}
// 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
)}
{/* Windows list */}
{loading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : windows.length === 0 ? (
No maintenance windows
Click "New Window" to schedule planned downtime
) : (
<>
{/* 7-day timeline strip */}
{[...windows].sort((a, b) => {
const order = { active: 0, scheduled: 1, expired: 2 };
return (order[a.status] ?? 9) - (order[b.status] ?? 9) || a.start_dt.localeCompare(b.start_dt);
}).map(w => (
{w.title}
{w.suppress_alarms && (
Alarms suppressed
)}
Target: {w.target_label}
{formatDt(w.start_dt)} → {formatDt(w.end_dt)}
{w.notes && (
{w.notes}
)}
))}
>
)}
);
}