"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { AppShell } from "@/components/AppShell"; import { Badge, Panel, } from "@/components/ui/Primitives"; import { ProposalDiffCard, ReviewDecisionBar, ReviewNoteField, ReviewQueueList, ReviewTimeline, ReviewWorkflowNav, } from "@/components/reviews/ReviewWorkflow"; import { approveReviewItem, batchReviewItems, listAgentTimeline, listApprovalQueue, rejectReviewItem } from "@/lib/review-client"; import { toast } from "@/lib/toast"; import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types"; export default function ReviewsPage() { const [approvalQueue, setApprovalQueue] = useState([]); const [timeline, setTimeline] = useState([]); const [selectedApprovalId, setSelectedApprovalId] = useState(null); const [selectedBatchIds, setSelectedBatchIds] = useState>(new Set()); const [reviewNote, setReviewNote] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); useEffect(() => { void (async () => { try { const [nextQueue, nextTimeline] = await Promise.all([ listApprovalQueue(), listAgentTimeline(), ]); setApprovalQueue(nextQueue); setSelectedApprovalId((current) => current && nextQueue.some((item) => item.id === current) ? current : nextQueue[0]?.id ?? null, ); setTimeline(nextTimeline); } catch (err) { setError(err instanceof Error ? err.message : "Unable to load review queue"); } })(); }, []); const featuredProposal = useMemo( () => approvalQueue.find((item) => item.id === selectedApprovalId) ?? approvalQueue[0] ?? null, [approvalQueue, selectedApprovalId], ); const batchMode = selectedBatchIds.size > 0; const toggleBatchItem = useCallback((id: string) => { setSelectedBatchIds((current) => { const next = new Set(current); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }, []); const selectAllForBatch = useCallback(() => { setSelectedBatchIds(new Set(approvalQueue.map((item) => item.id))); }, [approvalQueue]); const clearBatch = useCallback(() => { setSelectedBatchIds(new Set()); }, []); const operatorWorkflows = [ { id: "workflow-approvals", name: "Approval triage", owner: "Operator", queueCount: approvalQueue.length, sla: "< 4h", status: approvalQueue.length > 3 ? "at_risk" : "healthy", }, { id: "workflow-agent-activity", name: "Agent activity review", owner: "Knowledge Ops", queueCount: timeline.length, sla: "< 1d", status: timeline.length > 6 ? "at_risk" : "healthy", }, ] as const; async function handleDecision(decision: "approved" | "rejected") { if (!featuredProposal) { return; } setIsSubmitting(true); const note = reviewNote.trim() || undefined; try { const updated = decision === "approved" ? await approveReviewItem(featuredProposal, note) : await rejectReviewItem(featuredProposal, note); setApprovalQueue((current) => { const nextQueue = current.filter((item) => item.id !== featuredProposal.id); setSelectedApprovalId((selected) => selected === featuredProposal.id ? nextQueue[0]?.id ?? null : selected, ); return nextQueue; }); setTimeline((current) => [ { id: updated.id, actor: updated.owner, action: `human ${decision} proposal`, timestamp: new Date().toISOString(), status: updated.status, summary: updated.after ?? updated.title, }, ...current, ]); setReviewNote(""); toast.success(`Proposal ${decision}`); } catch (err) { const msg = err instanceof Error ? err.message : "Unable to update review state"; setError(msg); toast.error(msg); } finally { setIsSubmitting(false); } } async function handleBatchDecision(decision: "approved" | "rejected") { const batchItems = approvalQueue.filter((item) => selectedBatchIds.has(item.id)); if (batchItems.length === 0) return; setIsSubmitting(true); const note = reviewNote.trim() || undefined; try { await batchReviewItems(batchItems, decision, note); setApprovalQueue((current) => { const nextQueue = current.filter((item) => !selectedBatchIds.has(item.id)); setSelectedApprovalId(nextQueue[0]?.id ?? null); return nextQueue; }); setTimeline((current) => [ { id: `batch-${Date.now()}`, actor: "reviewer", action: `batch ${decision} ${batchItems.length} proposals`, timestamp: new Date().toISOString(), status: decision, summary: note ?? `${batchItems.length} items ${decision}`, }, ...current, ]); setSelectedBatchIds(new Set()); setReviewNote(""); toast.success(`Batch ${decision}: ${batchItems.length} items`); } catch (err) { const msg = err instanceof Error ? err.message : "Batch review failed"; setError(msg); toast.error(msg); } finally { setIsSubmitting(false); } } return ( Operator workflow shell} >
void handleBatchDecision(decision)} /> {error ?
{error}
: null} batchMode ? toggleBatchItem(id) : setSelectedApprovalId(id)} />
{featuredProposal && !batchMode ? ( void handleDecision("approved")} onReject={() => void handleDecision("rejected")} isSubmitting={isSubmitting} /> ) : null}
); }