'use client'; import { useEffect, useState, useCallback } from 'react'; import { migrationService } from '@/lib/services/migration.service'; import { MigrationReviewQueueItem, MigrationReviewStatus } from '@/types/migration'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Checkbox } from '@/components/ui/checkbox'; import { Badge } from '@/components/ui/badge'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { format } from 'date-fns'; import { EyeIcon, FileXIcon, CheckSquareIcon } from 'lucide-react'; import Link from 'next/link'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { getApiErrorMessage } from '@/types/api-error'; export default function MigrationReviewQueuePage() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [statusFilter, setStatusFilter] = useState('PENDING'); const [selectedIds, setSelectedIds] = useState([]); const [errorMessage, setErrorMessage] = useState(null); const fetchData = useCallback(async () => { try { setLoading(true); setErrorMessage(null); const res = await migrationService.getReviewQueue({ status: statusFilter === 'ALL' ? undefined : (statusFilter as MigrationReviewStatus), limit: 50, }); setItems(Array.isArray(res.items) ? res.items : []); setSelectedIds([]); // reset selection on fetch } catch (error: unknown) { setItems([]); setErrorMessage(getApiErrorMessage(error, 'Failed to load queue')); } finally { setLoading(false); } }, [statusFilter]); useEffect(() => { fetchData(); }, [fetchData]); const handleToggleSelectAll = () => { if (selectedIds.length === items.length) { setSelectedIds([]); } else { setSelectedIds(items.map((i) => i.id)); } }; const handleToggleSelect = (id: number) => { setSelectedIds((prev) => (prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id])); }; const handleBatchApprove = async () => { if (selectedIds.length === 0) return; try { setSubmitting(true); const batchItems = items .filter((i) => selectedIds.includes(i.id)) .map((item) => ({ queueId: item.id, dto: { document_number: item.documentNumber, subject: item.title || item.originalTitle || 'Untitled', category: item.aiSuggestedCategory || 'Correspondence', project_id: item.projectId || 1, migrated_by: 'SYSTEM_IMPORT', temp_attachment_id: item.tempAttachmentId, ai_confidence: item.aiConfidence, ai_issues: item.aiIssues, issued_date: item.issuedDate, received_date: item.receivedDate, sender_id: item.senderOrganizationId, receiver_id: item.receiverOrganizationId, details: { tags: item.extractedTags, }, }, })); const batchId = `BATCH_UI_${Date.now()}`; await migrationService.commitBatch({ items: batchItems, batchId }, batchId); fetchData(); } catch (_error) { toast.error('Batch commit failed.'); } finally { setSubmitting(false); } }; return (

Migration Review Queue

Review and correct documents that AI flagged as low confidence.

{selectedIds.length > 0 && ( )}
Queue Items - {statusFilter} {errorMessage && (
{errorMessage}
)} {loading ? (
Loading queue...
) : items.length === 0 ? (
No items in the queue.
) : (
0 && selectedIds.length === items.length} onCheckedChange={handleToggleSelectAll} aria-label="Select all" /> Document No. Suggested Category Confidence Status Created At Action {items.map((item) => ( handleToggleSelect(item.id)} aria-label={`Select item ${item.id}`} /> {item.documentNumber} {item.aiSuggestedCategory || 'Unknown'} 0.8 ? 'default' : item.aiConfidence > 0.5 ? 'secondary' : 'destructive' } > {item.aiConfidence ? (item.aiConfidence * 100).toFixed(1) + '%' : 'N/A'} {item.status} {format(new Date(item.createdAt), 'dd MMM yyyy, HH:mm')} ))}
)}
); }