Files
SalesPipeline/frontend/src/components/ReviewView.tsx
2026-01-16 18:16:33 +08:00

165 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 flex-col gap-1 mb-3">
<div className="flex items-center gap-2">
<Badge type="info">DIT ()</Badge>
<span className="text-xs text-slate-500 font-mono">OP NO: {item.dit?.op_id}</span>
</div>
{item.dit?.op_name && (
<div className="text-sm font-medium text-indigo-900 line-clamp-2" title={item.dit.op_name}>
{item.dit.op_name}
</div>
)}
</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>
);
};