import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useAppPath } from '@core/hooks/useAppPath' import { apiFetch } from '@core/lib/api' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@core/components/ui/tabs' import { Badge } from '@core/components/ui/badge' import { Button } from '@core/components/ui/button' import { Skeleton } from '@core/components/ui/skeleton' import { ScrollArea } from '@core/components/ui/scroll-area' import { ClipboardList, AlertCircle, ThumbsDown, BookOpen } from 'lucide-react' interface Item { id: string title: string status?: string created_at: string updated_at?: string data?: Record } function ageLabel(dateStr: string) { const diff = Date.now() - new Date(dateStr).getTime() const hours = Math.floor(diff / 3600000) if (hours < 1) return 'just now' if (hours < 24) return `${hours}h ago` return `${Math.floor(hours / 24)}d ago` } function daysAgo(dateStr: string) { const diff = Date.now() - new Date(dateStr).getTime() return Math.floor(diff / 86400000) } // --- KB Candidates Tab --- function KBCandidatesTab() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { apiFetch('/api/admin-data?entity=items&type_slug=kb_article&status=draft&limit=50') .then(r => r.json()) .then(j => { const all: Item[] = Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [] setItems(all.filter(item => item.data?.kb_type === 'postmortem')) }) .catch(() => setItems([])) .finally(() => setLoading(false)) }, []) const handleApprove = async (item: Item) => { await apiFetch(`/api/admin-data?entity=items&id=${item.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'published' }), }) setItems(prev => prev.filter(i => i.id !== item.id)) } if (loading) { return (
{Array.from({ length: 5 }).map((_, i) => )}
) } if (items.length === 0) { return (

No postmortem KB drafts awaiting review.

) } return (
{items.map(item => (

{item.title}

{ageLabel(item.created_at)} {item.data?.source_ticket_id && ( Ticket: {String(item.data.source_ticket_id).slice(0, 8)}… )} {item.data?.automation_potential && ( {item.data.automation_potential} automation )}
))}
) } // --- Escalations Tab --- function EscalationsTab() { const navigate = useNavigate() const appPath = useAppPath() const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { apiFetch('/api/admin-data?entity=items&type_slug=support_ticket&status=human_assigned&limit=50') .then(r => r.json()) .then(j => setItems(Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [])) .catch(() => setItems([])) .finally(() => setLoading(false)) }, []) if (loading) { return (
{Array.from({ length: 5 }).map((_, i) => )}
) } if (items.length === 0) { return (

No escalated tickets right now.

) } const PRIORITY_COLORS: Record = { low: 'bg-muted text-muted-foreground', medium: 'bg-blue-100 text-blue-700', high: 'bg-orange-100 text-orange-700', urgent: 'bg-red-100 text-red-700', } return (
{items.map(item => (

{item.title}

{ageLabel(item.created_at)} {item.data?.priority && ( {item.data.priority} )} {item.data?.aim_escalation_reason && ( {item.data.aim_escalation_reason.replace(/_/g, ' ')} )} {item.data?.aim_confidence_at_response != null && ( AI conf: {Math.round(item.data.aim_confidence_at_response * 100)}% )}
))}
) } // --- Thumbs Down Tab --- function ThumbsDownTab() { const [count, setCount] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { apiFetch('/api/admin-data?entity=items&type_slug=support_ticket&limit=500') .then(r => r.json()) .then(j => { const all: Item[] = Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [] setCount(all.filter(t => t.data?.aim_escalation_reason === 'thumbs_down').length) }) .catch(() => setCount(0)) .finally(() => setLoading(false)) }, []) return (

Feedback data is captured on individual tickets. View ticket detail for feedback context.

Tickets with thumbs-down escalation reason

{loading ? ( ) : (

{count ?? 0}

)}
) } // --- Stale KB Tab --- function StaleKBTab() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { apiFetch('/api/admin-data?entity=items&type_slug=kb_article&status=published&limit=100') .then(r => r.json()) .then(j => { const all: Item[] = Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [] setItems(all.filter(item => daysAgo(item.updated_at || item.created_at) >= 90)) }) .catch(() => setItems([])) .finally(() => setLoading(false)) }, []) const handleMarkReviewed = async (item: Item) => { await apiFetch(`/api/admin-data?entity=items&id=${item.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ updated_at: new Date().toISOString() }), }) setItems(prev => prev.filter(i => i.id !== item.id)) } const handleArchive = async (item: Item) => { await apiFetch(`/api/admin-data?entity=items&id=${item.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'archived' }), }) setItems(prev => prev.filter(i => i.id !== item.id)) } if (loading) { return (
{Array.from({ length: 5 }).map((_, i) => )}
) } if (items.length === 0) { return (

No published articles are 90+ days stale.

) } return (
{items.map(item => (

{item.title}

Last updated {daysAgo(item.updated_at || item.created_at)} days ago

))}
) } // --- Main Page --- export default function ReviewQueuePage() { return (

Review Queue

Items requiring human review and action

KB Candidates Escalations Thumbs Down Stale KB
) }