// File: components/migration/review-queue-table.tsx // Change Log: // - 2026-05-22: Initial creation of ReviewQueueTable component for US2 (T024) // - 2026-05-22: Integrated hybrid identifiers and Radix Sheet panel with zero blank lines inside function bodies (T024) import React, { useState } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetFooter, } from '@/components/ui/sheet'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useCommitMigrationReview, useRejectMigrationReview } from '@/hooks/use-migration-review'; import { useProjects, useOrganizations } from '@/hooks/use-master-data'; import { MigrationReviewQueueItem, MigrationReviewStatus } from '@/types/migration'; import { Loader2, Calendar, Tag, AlertCircle, Edit, Check, X, Plus } from 'lucide-react'; interface ReviewTag { name?: string; tagName?: string; is_new?: boolean; isNew?: boolean; } interface ProjectOption { publicId: string; projectName: string; projectCode?: string; } interface OrganizationOption { publicId: string; organizationName: string; } const getStringField = (value: Record, key: string): string | undefined => typeof value[key] === 'string' ? value[key] : undefined; const toReviewTag = (value: Record): ReviewTag => ({ name: getStringField(value, 'name'), tagName: getStringField(value, 'tagName'), is_new: typeof value.is_new === 'boolean' ? value.is_new : undefined, isNew: typeof value.isNew === 'boolean' ? value.isNew : undefined, }); const getTagLabel = (tag: Record): string => getStringField(tag, 'name') ?? getStringField(tag, 'tagName') ?? ''; const getIssueText = (issue: Record): string => getStringField(issue, 'description') ?? getStringField(issue, 'message') ?? ''; interface ReviewQueueTableProps { items: MigrationReviewQueueItem[]; isLoading: boolean; } export function ReviewQueueTable({ items, isLoading }: ReviewQueueTableProps) { const [selectedItem, setSelectedItem] = useState(null); const [isSheetOpen, setIsSheetOpen] = useState(false); const [editSubject, setEditSubject] = useState(''); const [editCategory, setEditCategory] = useState(''); const [editProjectId, setEditProjectId] = useState(''); const [editSenderId, setEditSenderId] = useState(''); const [editReceiverId, setEditReceiverId] = useState(''); const [editIssuedDate, setEditIssuedDate] = useState(''); const [editReceivedDate, setEditReceivedDate] = useState(''); const [editBody, setEditBody] = useState(''); const [editTags, setEditTags] = useState([]); const [newTagInput, setNewTagInput] = useState(''); const commitMutation = useCommitMigrationReview(); const rejectMutation = useRejectMigrationReview(); const { data: projects = [] } = useProjects(); const { data: organizations = [] } = useOrganizations(); const projectOptions = projects as ProjectOption[]; const organizationOptions = organizations as OrganizationOption[]; const handleOpenReview = (item: MigrationReviewQueueItem) => { setSelectedItem(item); setEditSubject(item.subject || item.title || ''); setEditCategory(item.aiSuggestedCategory || 'Correspondence'); setEditProjectId(String(item.projectId || '')); setEditSenderId(String(item.senderOrganizationId || '')); setEditReceiverId(String(item.receiverOrganizationId || '')); setEditIssuedDate(item.issuedDate ? item.issuedDate.substring(0, 10) : ''); setEditReceivedDate(item.receivedDate ? item.receivedDate.substring(0, 10) : ''); setEditBody(item.body || ''); const tags = Array.isArray(item.extractedTags) ? item.extractedTags.map((tag) => getTagLabel(tag)).filter(Boolean) : []; setEditTags(tags); setNewTagInput(''); setIsSheetOpen(true); }; const handleAddTag = () => { if (newTagInput.trim() && !editTags.includes(newTagInput.trim())) { setEditTags([...editTags, newTagInput.trim()]); setNewTagInput(''); } }; const handleRemoveTag = (tagToRemove: string) => { setEditTags(editTags.filter((t) => t !== tagToRemove)); }; const handleCommit = async () => { if (!selectedItem) return; try { const idempotencyKey = `migration_review_${selectedItem.publicId}_${Date.now()}`; await commitMutation.mutateAsync({ publicId: selectedItem.publicId, idempotencyKey, subject: editSubject, category: editCategory, projectId: editProjectId || undefined, senderId: editSenderId || undefined, receiverId: editReceiverId || undefined, issuedDate: editIssuedDate || undefined, receivedDate: editReceivedDate || undefined, tags: editTags, body: editBody || undefined, }); setIsSheetOpen(false); setSelectedItem(null); } catch { return; } }; const handleReject = async () => { if (!selectedItem) return; if (window.confirm('คุณแน่ใจหรือไม่ว่าต้องการปฏิเสธเอกสารนี้?')) { try { const queueIntId = selectedItem.id || 0; await rejectMutation.mutateAsync(queueIntId); setIsSheetOpen(false); setSelectedItem(null); } catch { return; } } }; const getStatusBadge = (status: MigrationReviewStatus) => { const configs: Record = { [MigrationReviewStatus.PENDING]: { label: 'รอตรวจสอบ', className: 'bg-yellow-500/20 text-yellow-500 border-yellow-500/30', }, [MigrationReviewStatus.APPROVED]: { label: 'อนุมัติแล้ว', className: 'bg-blue-500/20 text-blue-500 border-blue-500/30', }, [MigrationReviewStatus.REJECTED]: { label: 'ปฏิเสธ', className: 'bg-red-500/20 text-red-500 border-red-500/30', }, [MigrationReviewStatus.IMPORTED]: { label: 'นำเข้าแล้ว', className: 'bg-green-500/20 text-green-500 border-green-500/30', }, }; const config = configs[status] || { label: status, className: '' }; return {config.label}; }; return (
เลขที่เอกสาร หัวข้อเอกสาร (Subject) หมวดหมู่ AI ความมั่นใจ AI สถานะ การกระทำ {isLoading ? (
กำลังโหลดรายการรอรีวิว...
) : items.length === 0 ? ( ไม่พบรายการที่รอตรวจสอบในคิวขณะนี้ ) : ( items.map((item) => ( {item.documentNumber} {item.subject || item.title || 'ไม่มีหัวข้อ'} {item.aiSuggestedCategory || 'Correspondence'} {item.aiConfidence ? `${(Number(item.aiConfidence) * 100).toFixed(1)}%` : '-'} {getStatusBadge(item.status)} )) )}
รีวิวการย้ายข้อมูลเอกสาร {selectedItem?.documentNumber} ตรวจสอบ แก้ไขข้อมูล Metadata และยืนยันความถูกต้องเพื่อนำข้อมูลเข้าสู่ระบบจดหมายโต้ตอบจริง {selectedItem && (
{selectedItem.aiIssues && selectedItem.aiIssues.length > 0 && (
ข้อควรระวังจากการตรวจสอบของ AI:
    {selectedItem.aiIssues.map((issue, idx: number) => (
  • {getIssueText(issue)}
  • ))}
)}
setEditSubject(e.target.value)} placeholder="ป้อนหัวข้อเรื่องภาษาไทยหรืออังกฤษ" className="w-full border-input" /> {selectedItem.originalSubject && selectedItem.originalSubject !== editSubject && (

หัวข้อเดิมที่ AI ดึงได้: {selectedItem.originalSubject}

)}
setEditIssuedDate(e.target.value)} />
setEditReceivedDate(e.target.value)} />