"use client"; import { useState, useRef, useEffect } from "react"; import Link from "next/link"; import { Alert, AlertSeverity, AlertComment } from "@/lib/types"; // ── Severity theming ───────────────────────────────────────────────────────── const severityAccent: Record = { danger: "border-l-red-500", warning: "border-l-yellow-500", info: "border-l-blue-500", }; const severityText: Record = { danger: "text-red-400", warning: "text-yellow-400", info: "text-blue-400", }; const severityLabel: Record = { danger: "Critical", warning: "Warning", info: "Info", }; // ── Helpers ─────────────────────────────────────────────────────────────────── function timeAgo(iso: string): string { const diff = Date.now() - new Date(iso).getTime(); const mins = Math.floor(diff / 60_000); if (mins < 1) return "just now"; if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; return `${Math.floor(hrs / 24)}d ago`; } function shortDate(iso: string): string { return new Date(iso).toLocaleDateString(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }); } // ── Comment row ─────────────────────────────────────────────────────────────── function CommentRow({ comment }: { comment: AlertComment }) { const isSystem = comment.author === "system"; if (isSystem) { return (
{comment.body} · {timeAgo(comment.createdAt)}
); } return (
User {shortDate(comment.createdAt)}

{comment.body}

); } // ── Main component ──────────────────────────────────────────────────────────── export default function AlertDetail({ initialAlert }: { initialAlert: Alert }) { const [alert, setAlert] = useState(initialAlert); const [actionLoading, setActionLoading] = useState(false); const [commentText, setCommentText] = useState(""); const [commentLoading, setCommentLoading] = useState(false); const [error, setError] = useState(null); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [alert.comments.length]); async function toggleStatus() { setActionLoading(true); setError(null); try { const newStatus = alert.status === "open" ? "closed" : "open"; const res = await fetch(`/api/alerts/${alert.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: newStatus }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); setAlert(await res.json()); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setActionLoading(false); } } async function submitComment(e: React.FormEvent) { e.preventDefault(); if (!commentText.trim()) return; setCommentLoading(true); setError(null); try { const res = await fetch(`/api/alerts/${alert.id}/comments`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ body: commentText.trim() }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const comment = await res.json(); setAlert((prev) => ({ ...prev, comments: [...prev.comments, comment] })); setCommentText(""); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setCommentLoading(false); } } const isOpen = alert.status === "open"; const isResolved = alert.closeReason === "resolved"; const statusTime = isOpen ? alert.firstSeen : (alert.closedAt ?? alert.firstSeen); return (
All Alerts {error && (
{error}
)}
{/* ── Alert detail ─────────────────────────────────────────────── */}
{/* Status row */}
{isOpen && } {isOpen ? "Open" : isResolved ? "Auto-resolved" : "Closed"} · {timeAgo(statusTime)}
{/* Severity + title + description */}
{severityLabel[alert.severity]}

{alert.title}

{alert.description}

{/* ── Comments ─────────────────────────────────────────────────── */}

Comments

{alert.comments.length === 0 && (

No comments yet.

)} {alert.comments.map((c) => ( ))}