feat(migration): ADR-028 migration architecture refactor
- เพิ่ม POST /api/ai/jobs + GET /api/ai/jobs/:jobId endpoints (FR-001, FR-002) - เพิ่ม BullMQ Worker MigrateDocumentWorker + OCR auto-detect (FR-003, FR-004) - เพิ่ม cleanup-temp-files + expire-pending-reviews workers (FR-005, FR-005a/b) - สร้าง SQL deltas: tags, correspondence_tags, alter migration_review_queue (FR-006, ADR-009) - เพิ่ม MigrationReviewService.commitRecord() + SELECT FOR UPDATE (FR-007, FR-007a) - เพิ่ม CASL permission migration.commit + MigrationReviewController (FR-007) - สร้าง TagsModule + TagsService + TagsController (US3) - สร้าง Migration Review Queue frontend page + ReviewQueueTable (US2) - อัปเดต n8n guide: deterministic Idempotency-Key + token pre-flight (FR-001a, FR-010a/b) - สร้าง spec.md, plan.md, tasks.md, data-model.md, contracts/, quickstart.md - สร้าง ADR-028 document + validation-report.md (PASS 32/32 tasks, 173/173 tests)
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
// File: app/(dashboard)/migration/review/page.tsx
|
||||
// Change Log:
|
||||
// - 2026-05-22: Initial creation of Migration Review page with premium UI, pagination, status tabs, and strictly zero blank lines inside function bodies (T024)
|
||||
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useMigrationReviewQueue } from '@/hooks/use-migration-review';
|
||||
import { MigrationReviewStatus } from '@/types/migration';
|
||||
import { ReviewQueueTable } from '@/components/migration/review-queue-table';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronLeft, ChevronRight, RefreshCw, BarChart2, ShieldAlert } from 'lucide-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
export default function MigrationReviewPage() {
|
||||
const [statusFilter, setStatusFilter] = useState<MigrationReviewStatus | 'ALL'>(MigrationReviewStatus.PENDING);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const itemsPerPage = 10;
|
||||
const { data, isLoading, isFetching, refetch } = useMigrationReviewQueue(
|
||||
statusFilter === 'ALL' ? undefined : statusFilter,
|
||||
currentPage,
|
||||
itemsPerPage
|
||||
);
|
||||
const items = data?.items || [];
|
||||
const totalItems = data?.total || 0;
|
||||
const totalPages = data?.totalPages || 1;
|
||||
const handleTabChange = (value: string) => {
|
||||
setStatusFilter(value as MigrationReviewStatus | 'ALL');
|
||||
setCurrentPage(1);
|
||||
};
|
||||
const handlePrevPage = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
const handleNextPage = () => {
|
||||
if (currentPage < totalPages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex-1 space-y-6 p-8 pt-6">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-3xl font-extrabold tracking-tight bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 bg-clip-text text-transparent">
|
||||
Migration Review Queue
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
จัดการรีวิวเอกสารที่ได้รับการย้ายข้อมูลจากระบบเดิมผ่าน AI Engine และกดยืนยันเพื่อบันทึกเข้าระบบจริง
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => refetch()}
|
||||
disabled={isFetching}
|
||||
className="h-9 hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isFetching ? 'animate-spin' : ''}`} />
|
||||
<span>โหลดใหม่</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card className="bg-gradient-to-br from-yellow-500/10 to-transparent border-yellow-500/20 shadow-sm backdrop-blur-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-semibold text-yellow-500">รอการตรวจสอบ (Pending)</CardTitle>
|
||||
<BarChart2 className="h-4 w-4 text-yellow-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-black text-yellow-500 font-mono">
|
||||
{statusFilter === MigrationReviewStatus.PENDING ? totalItems : '-'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">คิวเอกสารที่ต้องการการอนุมัติแบบมีส่วนร่วม</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-green-500/10 to-transparent border-green-500/20 shadow-sm backdrop-blur-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-semibold text-green-500">นำเข้าเรียบร้อย (Imported)</CardTitle>
|
||||
<BarChart2 className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-black text-green-500 font-mono">
|
||||
{statusFilter === MigrationReviewStatus.IMPORTED ? totalItems : '-'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">เอกสารที่นำเข้าสู่ระบบจัดเก็บถาวรแล้ว</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-red-500/10 to-transparent border-red-500/20 shadow-sm backdrop-blur-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-semibold text-red-500">ปฏิเสธนำเข้า (Rejected)</CardTitle>
|
||||
<ShieldAlert className="h-4 w-4 text-red-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-black text-red-500 font-mono">
|
||||
{statusFilter === MigrationReviewStatus.REJECTED ? totalItems : '-'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">เอกสารที่ปฎิเสธและต้องผ่านการตรวจสอบใหม่</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="bg-gradient-to-br from-indigo-500/10 to-transparent border-indigo-500/20 shadow-sm backdrop-blur-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-semibold text-indigo-500">จำนวนทั้งหมดในระบบ (Total)</CardTitle>
|
||||
<BarChart2 className="h-4 w-4 text-indigo-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-black text-indigo-500 font-mono">
|
||||
{statusFilter === 'ALL' ? totalItems : '-'}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1">จำนวนรวมรายการย้ายข้อมูลในระบบคิว</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<Card className="border-muted bg-card shadow-lg backdrop-blur-md">
|
||||
<CardHeader className="pb-3 border-b flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg font-bold">คิวเอกสารย้ายข้อมูล</CardTitle>
|
||||
<CardDescription className="text-xs">เลือกรายการเอกสารเพื่อตรวจสอบความสัมพันธ์และแท็กของโครงการ</CardDescription>
|
||||
</div>
|
||||
<Tabs value={statusFilter} onValueChange={handleTabChange} className="w-[450px]">
|
||||
<TabsList className="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="PENDING" className="text-xs font-semibold">รอตรวจสอบ</TabsTrigger>
|
||||
<TabsTrigger value="IMPORTED" className="text-xs font-semibold">นำเข้าแล้ว</TabsTrigger>
|
||||
<TabsTrigger value="REJECTED" className="text-xs font-semibold">ปฏิเสธ</TabsTrigger>
|
||||
<TabsTrigger value="ALL" className="text-xs font-semibold">ทั้งหมด</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<ReviewQueueTable items={items} isLoading={isLoading} />
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between space-x-2 pt-6">
|
||||
<div className="text-xs text-muted-foreground font-mono">
|
||||
แสดงหน้า {currentPage} จาก {totalPages} (ทั้งหมด {totalItems} รายการ)
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handlePrevPage}
|
||||
disabled={currentPage === 1 || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-xs font-semibold px-2 font-mono">
|
||||
{currentPage}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleNextPage}
|
||||
disabled={currentPage === totalPages || isLoading}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user