← back to Omega Watches 2
src/app/(dashboard)/quarantine/page.tsx
112 lines
"use client";
import { useEffect, useState } from "react";
import { api } from "@/lib/api-client";
import { formatDateTime } from "@/lib/utils";
export default function QuarantineQueuePage() {
const [data, setData] = useState<any>({ records: [], severity_counts: [] });
const [error, setError] = useState("");
const [resolving, setResolving] = useState<string | null>(null);
const [notes, setNotes] = useState("");
useEffect(() => {
api.getQuarantine({ resolved: false }).then(setData).catch((e) => setError(e.message));
}, []);
async function handleResolve(id: string, action: "release" | "reject") {
if (!notes.trim()) return;
setResolving(id);
try {
await api.resolveQuarantine(id, notes, action);
setData((prev: any) => ({
...prev,
records: prev.records.filter((r: any) => r.id !== id),
}));
setNotes("");
} catch (e: any) {
setError(e.message);
}
setResolving(null);
}
const severityColor: Record<string, string> = {
critical: "text-red-400 bg-red-900/20",
high: "text-orange-400 bg-orange-900/20",
medium: "text-yellow-400 bg-yellow-900/20",
low: "text-blue-400 bg-blue-900/20",
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold text-gray-100">Quarantine Review Queue</h1>
<div className="flex gap-2">
{data.severity_counts.map((sc: any) => (
<span key={sc.severity} className={`badge ${severityColor[sc.severity] || ""}`}>
{sc.severity}: {sc.count}
</span>
))}
</div>
</div>
{error && <div className="rounded bg-red-900/30 p-3 text-red-300">{error}</div>}
{data.records.length === 0 ? (
<div className="card border-navy-700 p-8 text-center">
<div className="text-4xl">✓</div>
<div className="mt-2 text-lg font-medium text-green-400">Queue Clear</div>
<div className="text-sm text-gray-500">No quarantined records requiring review.</div>
</div>
) : (
<div className="space-y-3">
{data.records.map((r: any) => (
<div key={r.id} className="card border-navy-700">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<span className={`badge ${severityColor[r.severity] || "badge-info"}`}>{r.severity}</span>
<span className="text-sm font-medium text-gray-200">{r.reason}</span>
</div>
<span className="text-xs text-gray-500">{formatDateTime(r.created_at)}</span>
</div>
{r.source_name && (
<div className="mt-2 text-xs text-gray-400">
Source: {r.source_name} | Ref: {r.reference_number_raw || "N/A"} | Price: ${r.price_usd || "N/A"} | Type: {r.event_type}
</div>
)}
{r.details && (
<pre className="mt-2 rounded bg-navy-950 p-2 text-xs text-gray-500">
{JSON.stringify(r.details, null, 2)}
</pre>
)}
<div className="mt-3 flex items-center gap-2">
<input
type="text"
placeholder="Resolution notes..."
className="input flex-1 text-xs"
value={resolving === r.id ? notes : ""}
onChange={(e) => { setResolving(r.id); setNotes(e.target.value); }}
/>
<button
onClick={() => handleResolve(r.id, "release")}
disabled={resolving === r.id && !notes.trim()}
className="btn-primary text-xs"
>
Release
</button>
<button
onClick={() => handleResolve(r.id, "reject")}
disabled={resolving === r.id && !notes.trim()}
className="btn-secondary text-xs"
>
Reject
</button>
</div>
</div>
))}
</div>
)}
</div>
);
}