first commit
This commit is contained in:
157
frontend/src/components/ReviewView.tsx
Normal file
157
frontend/src/components/ReviewView.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { CheckCircle, XCircle } from 'lucide-react';
|
||||
import { Card, Badge } from './common/Card';
|
||||
import { matchApi } from '../services/api';
|
||||
import type { MatchResult, ReviewAction } from '../types';
|
||||
|
||||
interface ReviewViewProps {
|
||||
onReviewComplete: () => void;
|
||||
}
|
||||
|
||||
export const ReviewView: React.FC<ReviewViewProps> = ({ onReviewComplete }) => {
|
||||
const [reviews, setReviews] = useState<MatchResult[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadReviews();
|
||||
}, []);
|
||||
|
||||
const loadReviews = async () => {
|
||||
try {
|
||||
const results = await matchApi.getResults();
|
||||
setReviews(results.filter(r => r.status === 'pending'));
|
||||
} catch (error) {
|
||||
console.error('Error loading reviews:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReviewAction = async (id: number, action: ReviewAction) => {
|
||||
try {
|
||||
await matchApi.review(id, action);
|
||||
setReviews(prev => prev.filter(r => r.id !== id));
|
||||
|
||||
if (reviews.length === 1) {
|
||||
onReviewComplete();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error submitting review:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in zoom-in-95 duration-300">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-800 flex items-center gap-2">
|
||||
模糊比對審核工作台
|
||||
<span className="text-sm bg-indigo-100 text-indigo-700 px-2 py-1 rounded-full font-normal">
|
||||
待審核: {reviews.length}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-slate-500 mt-1">系統發現以下案件名稱相似,請人工確認關聯性。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{reviews.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 bg-white rounded-lg border border-dashed border-slate-300">
|
||||
<div className="w-16 h-16 bg-emerald-100 text-emerald-600 rounded-full flex items-center justify-center mb-4">
|
||||
<CheckCircle size={32} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-slate-800">所有案件已審核完畢!</h3>
|
||||
<p className="text-slate-500 mt-2">您的資料比對已完成,請查看分析儀表板。</p>
|
||||
<button
|
||||
onClick={onReviewComplete}
|
||||
className="mt-6 px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
|
||||
>
|
||||
前往儀表板
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{reviews.map(item => (
|
||||
<Card key={item.id} className="p-0 overflow-hidden group hover:shadow-md transition-all border-l-4 border-l-amber-400">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
{/* Left: DIT */}
|
||||
<div className="flex-1 p-5 border-b md:border-b-0 md:border-r border-slate-100 bg-slate-50/50">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge type="info">DIT (設計導入)</Badge>
|
||||
<span className="text-xs text-slate-400">OP編號: {item.dit?.op_id}</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs text-slate-400 uppercase">Customer Name</div>
|
||||
<div className="font-bold text-slate-800 text-lg">{item.dit?.customer}</div>
|
||||
<div className="text-xs text-slate-400 uppercase mt-2">Part Number</div>
|
||||
<div className="font-mono text-slate-700 bg-white border border-slate-200 px-2 py-1 rounded inline-block text-sm">
|
||||
{item.dit?.pn}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Middle: Score & Reason */}
|
||||
<div className="w-full md:w-48 p-4 flex flex-col items-center justify-center bg-white z-10">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl font-bold text-amber-500 mb-1">{item.score}%</div>
|
||||
<div className="text-[10px] font-medium text-amber-600 bg-amber-50 px-2 py-0.5 rounded-full mb-2">
|
||||
相似度
|
||||
</div>
|
||||
<div className="text-xs text-slate-400 text-center px-2 leading-tight">
|
||||
{item.reason}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Target (Sample/Order) */}
|
||||
<div className="flex-1 p-5 border-t md:border-t-0 md:border-l border-slate-100 bg-indigo-50/30">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Badge type={item.target_type === 'ORDER' ? 'warning' : 'success'}>
|
||||
{item.target_type === 'ORDER' ? 'Order (訂單)' : 'Sample (樣品)'}
|
||||
</Badge>
|
||||
<span className="text-xs text-slate-400">
|
||||
來源單號: {(item.target as { order_no?: string })?.order_no || 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs text-slate-400 uppercase">Customer Name</div>
|
||||
<div className="font-bold text-slate-800 text-lg">
|
||||
{(item.target as { customer?: string })?.customer}
|
||||
</div>
|
||||
<div className="text-xs text-slate-400 uppercase mt-2">Part Number</div>
|
||||
<div className="font-mono text-slate-700 bg-white border border-slate-200 px-2 py-1 rounded inline-block text-sm">
|
||||
{(item.target as { pn?: string })?.pn}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="w-full md:w-40 p-4 bg-slate-50 flex flex-row md:flex-col gap-3 justify-center items-center border-t md:border-t-0 md:border-l border-slate-200">
|
||||
<button
|
||||
onClick={() => handleReviewAction(item.id, 'accept')}
|
||||
className="flex-1 md:flex-none w-full py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-sm font-medium flex items-center justify-center gap-2 transition-colors shadow-sm"
|
||||
>
|
||||
<CheckCircle size={16} /> 確認關聯
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleReviewAction(item.id, 'reject')}
|
||||
className="flex-1 md:flex-none w-full py-2 bg-white border border-slate-300 text-slate-600 hover:bg-slate-50 rounded-lg text-sm font-medium flex items-center justify-center gap-2 transition-colors"
|
||||
>
|
||||
<XCircle size={16} /> 駁回
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user