Files
SalesPipeline/SampleOrderAssistant.txt
2026-01-09 19:14:41 +08:00

654 lines
33 KiB
Plaintext
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 {
Upload, FileText, Database, CheckCircle, XCircle,
AlertTriangle, BarChart2, PieChart, Activity,
ArrowRight, Search, Filter, Download, RefreshCw, FileSpreadsheet,
Info
} from 'lucide-react';
import {
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, Legend, ResponsiveContainer,
PieChart as RePieChart, Pie, Cell
} from 'recharts';
// --- 真實模擬資料 (源自使用者上傳檔案) ---
// 1. DIT Report (模擬解析後的結構)
// 來源: DIT report (by Focus Item)_app_樣本.xlsx
const PARSED_DIT_DATA = [
{ id: 'OP0000012868', customer: '光寶科技股份有限公司', pn: 'GBU610', eau: 20000, stage: 'Design Win', date: '2025-02-04' },
{ id: 'OP0000012869', customer: 'LITEON (PMS)', pn: 'PMSM808LL_R2', eau: 55000, stage: 'Design-In', date: '2025-07-28' },
{ id: 'OP0000012870', customer: '台達電子', pn: 'PEC3205ES-AU', eau: 100000, stage: 'Sample Provide', date: '2025-08-15' }, // 故意用簡稱測試模糊比對
{ id: 'OP0000012871', customer: '合美企業', pn: 'PJT7828', eau: 5000, stage: 'Negotiation', date: '2025-08-10' },
{ id: 'OP0000012872', customer: '申浦电子', pn: 'ER1604FCT', eau: 30000, stage: 'Design Win', date: '2025-07-30' },
];
// 2. Sample Data (模擬解析後的結構)
// 來源: 樣品申請紀錄_樣本.xlsx
const PARSED_SAMPLE_DATA = [
{ id: 'S202512490-02', order_no: 'S202512490', customer: '台達電子工業股份有限公司', pn: 'PEC3205ES-AU', qty: 20, date: '2025-12-19' },
{ id: 'S202512490-03', order_no: 'S202512490', customer: '台達電子工業股份有限公司', pn: 'PDZ9.1B-AU', qty: 20, date: '2025-12-19' },
];
// 3. Order Data (模擬解析後的結構)
// 來源: 訂單樣本_20251217.xlsx
const PARSED_ORDER_DATA = [
{ id: '1125025312-1', order_no: '1125025312', customer: '合美企業有限公司', pn: 'PJT7828', qty: 1200, status: 'Shipped', amount: 10800 },
{ id: '1125062105-1', order_no: '1125062105', customer: '申浦电子', pn: 'ER1604FCT', qty: 800, status: 'Backlog', amount: 3200 },
];
// --- 模糊比對模擬結果 ---
const SIMULATED_REVIEWS = [
{
id: 101,
score: 88,
dit: { id: 'OP0000012870', cust: '台達電子', pn: 'PEC3205ES-AU' },
match_target: { id: 'S202512490', cust: '台達電子工業股份有限公司', pn: 'PEC3205ES-AU', type: 'SAMPLE' },
type: 'SAMPLE',
reason: 'Customer Name Partial Match (88%)'
},
{
id: 102,
score: 92,
dit: { id: 'OP0000012871', cust: '合美企業', pn: 'PJT7828' },
match_target: { id: '1125025312', cust: '合美企業有限公司', pn: 'PJT7828', type: 'ORDER' },
type: 'ORDER',
reason: 'Corporate Suffix Mismatch'
}
];
// --- 輔助元件 ---
const Card = ({ children, className = "" }) => (
<div className={`bg-white rounded-lg border border-slate-200 shadow-sm ${className}`}>
{children}
</div>
);
const Badge = ({ children, type = "neutral" }) => {
const styles = {
neutral: "bg-slate-100 text-slate-600",
success: "bg-emerald-100 text-emerald-700",
warning: "bg-amber-100 text-amber-700",
danger: "bg-rose-100 text-rose-700",
info: "bg-blue-100 text-blue-700"
};
return (
<span className={`px-2 py-1 rounded-full text-xs font-medium ${styles[type]}`}>
{children}
</span>
);
};
// --- 主應用程式 ---
export default function App() {
const [activeTab, setActiveTab] = useState('import');
const [isProcessing, setIsProcessing] = useState(false);
const [processingStep, setProcessingStep] = useState('');
const [filesLoaded, setFilesLoaded] = useState(false);
// 狀態管理
const [reviews, setReviews] = useState([]);
const [processedCount, setProcessedCount] = useState(0);
// Dashboard 數據
const [dashboardData, setDashboardData] = useState({
totalDit: 5,
matchedSample: 0,
matchedOrder: 1, // '申浦电子' 是 Exact Match
conversionRate: 20.0
});
// 模擬載入使用者的真實檔案
const loadUserFiles = () => {
setFilesLoaded(true);
};
// 模擬 ETL 執行
const runEtl = () => {
if (!filesLoaded) {
alert("請先載入檔案!");
return;
}
setIsProcessing(true);
// 模擬後端處理步驟
const steps = [
"正在讀取 DIT Report... 偵測到表頭在第 16 行",
"正在讀取 樣品紀錄... 偵測到表頭在第 9 行",
"正在讀取 訂單明細... 編碼識別為 CP950",
"執行資料標準化 (Normalization)...",
"執行模糊比對 (Fuzzy Matching)...",
"運算完成!"
];
let stepIndex = 0;
const interval = setInterval(() => {
if (stepIndex >= steps.length) {
clearInterval(interval);
setIsProcessing(false);
setReviews(SIMULATED_REVIEWS); // 載入模擬的比對結果
setActiveTab('review');
} else {
setProcessingStep(steps[stepIndex]);
stepIndex++;
}
}, 800);
};
// 處理審核動作
const handleReviewAction = (id, action) => {
const reviewItem = reviews.find(r => r.id === id);
setReviews(prev => prev.filter(r => r.id !== id));
if (action === 'accept') {
setProcessedCount(prev => prev + 1);
// 更新 Dashboard 數據 (模擬)
if (reviewItem.type === 'ORDER') {
setDashboardData(prev => ({
...prev,
matchedOrder: prev.matchedOrder + 1,
conversionRate: ((prev.matchedOrder + 1) / prev.totalDit * 100).toFixed(1)
}));
} else if (reviewItem.type === 'SAMPLE') {
setDashboardData(prev => ({
...prev,
matchedSample: prev.matchedSample + 1
}));
}
}
};
// 漏斗圖資料
const funnelData = [
{ name: 'DIT 案件', value: dashboardData.totalDit, fill: '#6366f1' },
{ name: '成功送樣', value: dashboardData.matchedSample, fill: '#8b5cf6' },
{ name: '取得訂單', value: dashboardData.matchedOrder, fill: '#10b981' },
];
return (
<div className="min-h-screen bg-slate-50 font-sans text-slate-800">
{/* Top Navigation */}
<header className="bg-white border-b border-slate-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center text-white font-bold">
S
</div>
<div>
<span className="font-bold text-lg text-slate-800 block leading-tight">SalesPipeline</span>
<span className="text-[10px] text-slate-500 font-medium">On-Premise Simulator</span>
</div>
</div>
<div className="flex gap-1 bg-slate-100 p-1 rounded-lg">
{[
{ id: 'import', icon: Database, label: '資料匯入' },
{ id: 'review', icon: CheckCircle, label: '比對審核', badge: reviews.length },
{ id: 'dashboard', icon: BarChart2, label: '分析儀表板' }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`
flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-all
${activeTab === tab.id
? 'bg-white text-indigo-600 shadow-sm'
: 'text-slate-500 hover:text-slate-700 hover:bg-slate-200/50'}
`}
>
<tab.icon size={16} />
{tab.label}
{tab.badge > 0 && (
<span className="bg-rose-500 text-white text-[10px] px-1.5 py-0.5 rounded-full">
{tab.badge}
</span>
)}
</button>
))}
</div>
<div className="flex items-center gap-2 text-sm text-emerald-600 bg-emerald-50 px-3 py-1 rounded-full border border-emerald-100">
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></div>
系統運作中
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-8">
{/* --- View 1: Import --- */}
{activeTab === 'import' && (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex justify-between items-end">
<div>
<h1 className="text-2xl font-bold text-slate-800">原始資料匯入中心</h1>
<p className="text-slate-500 mt-1">系統將自動偵測 Excel/CSV 檔頭位置並進行智慧欄位對應。</p>
</div>
<div className="flex gap-3">
<button
onClick={loadUserFiles}
disabled={filesLoaded || isProcessing}
className={`flex items-center gap-2 px-4 py-3 rounded-lg font-medium border transition-all ${filesLoaded ? 'bg-slate-100 text-slate-400 border-slate-200' : 'bg-white text-slate-700 border-slate-300 hover:bg-slate-50'}`}
>
<FileSpreadsheet size={18} />
{filesLoaded ? '檔案已就緒' : '載入範本檔案'}
</button>
<button
onClick={runEtl}
disabled={isProcessing || !filesLoaded}
className={`flex items-center gap-2 px-6 py-3 rounded-lg text-white font-bold shadow-lg transition-all ${isProcessing || !filesLoaded ? 'bg-slate-400 cursor-not-allowed shadow-none' : 'bg-indigo-600 hover:bg-indigo-700 hover:scale-105 shadow-indigo-200'}`}
>
{isProcessing ? (
<>
<RefreshCw size={18} className="animate-spin" />
運算中...
</>
) : (
<>
<Activity size={18} />
開始 ETL 運算
</>
)}
</button>
</div>
</div>
{/* Progress Log */}
{isProcessing && (
<div className="bg-slate-800 text-green-400 font-mono p-4 rounded-lg text-sm shadow-inner">
<p className="flex items-center gap-2">
<span className="animate-pulse">▶</span> {processingStep}
</p>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ type: 'dit', title: '1. DIT Report', file: 'DIT report_app_樣本.xlsx', desc: '含 Metadata (前16行)', count: '5 筆 (測試)' },
{ type: 'sample', title: '2. 樣品紀錄', file: '樣品申請紀錄_樣本.xlsx', desc: '含檔頭 (前9行)', count: '2 筆 (測試)' },
{ type: 'order', title: '3. 訂單明細', file: '訂單樣本_20251217.xlsx', desc: '標準格式', count: '2 筆 (測試)' },
].map(file => (
<Card key={file.type} className={`p-6 border-dashed border-2 transition-colors ${filesLoaded ? 'border-emerald-300 bg-emerald-50/30' : 'border-slate-300'}`}>
<div className="flex justify-between items-start mb-4">
<div className={`p-3 rounded-lg ${filesLoaded ? 'bg-emerald-100 text-emerald-600' : 'bg-slate-100 text-slate-500'}`}>
{filesLoaded ? <CheckCircle size={24} /> : <FileText size={24} />}
</div>
{filesLoaded && <Badge type="success">Ready</Badge>}
</div>
<h3 className="text-lg font-bold text-slate-800">{file.title}</h3>
<p className="text-xs text-slate-500 font-mono mt-1 mb-3 truncate">{filesLoaded ? file.file : "等待上傳..."}</p>
<p className="text-sm text-slate-600">{file.desc}</p>
{filesLoaded && (
<div className="mt-4 pt-3 border-t border-slate-200/60 flex justify-between text-sm">
<span className="text-slate-500">預覽筆數</span>
<span className="font-bold font-mono text-slate-700">{file.count}</span>
</div>
)}
</Card>
))}
</div>
{/* Data Preview (Mocked from User Files) */}
{filesLoaded && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 animate-in fade-in slide-in-from-bottom-2 duration-500">
<Card className="overflow-hidden">
<div className="bg-slate-50 px-4 py-3 border-b border-slate-200 flex justify-between items-center">
<h3 className="font-bold text-slate-700 text-sm flex items-center gap-2">
<Database size={16} />
DIT 解析結果 (預覽)
</h3>
<Badge type="info">Auto-Skipped Header</Badge>
</div>
<table className="w-full text-xs text-left">
<thead className="bg-white text-slate-500 border-b border-slate-200">
<tr>
<th className="px-4 py-2">Customer</th>
<th className="px-4 py-2">Part No</th>
<th className="px-4 py-2">Stage</th>
<th className="px-4 py-2 text-right">EAU</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{PARSED_DIT_DATA.map((row, i) => (
<tr key={i} className="hover:bg-slate-50">
<td className="px-4 py-2 font-medium text-slate-700">{row.customer}</td>
<td className="px-4 py-2 font-mono text-slate-500">{row.pn}</td>
<td className="px-4 py-2 text-slate-600">{row.stage}</td>
<td className="px-4 py-2 text-right font-mono">{row.eau.toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</Card>
<Card className="overflow-hidden">
<div className="bg-slate-50 px-4 py-3 border-b border-slate-200 flex justify-between items-center">
<h3 className="font-bold text-slate-700 text-sm flex items-center gap-2">
<Database size={16} />
訂單解析結果 (預覽)
</h3>
<Badge type="info">Detected CP950</Badge>
</div>
<table className="w-full text-xs text-left">
<thead className="bg-white text-slate-500 border-b border-slate-200">
<tr>
<th className="px-4 py-2">Customer</th>
<th className="px-4 py-2">Part No</th>
<th className="px-4 py-2">Status</th>
<th className="px-4 py-2 text-right">Qty</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{PARSED_ORDER_DATA.map((row, i) => (
<tr key={i} className="hover:bg-slate-50">
<td className="px-4 py-2 font-medium text-slate-700">{row.customer}</td>
<td className="px-4 py-2 font-mono text-slate-500">{row.pn}</td>
<td className="px-4 py-2">
<span className={`px-1.5 py-0.5 rounded text-[10px] ${row.status === 'Shipped' ? 'bg-green-100 text-green-700' : 'bg-pink-100 text-pink-700'}`}>
{row.status}
</span>
</td>
<td className="px-4 py-2 text-right font-mono">{row.qty.toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</Card>
</div>
)}
</div>
)}
{/* --- View 2: Review --- */}
{activeTab === 'review' && (
<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={() => setActiveTab('dashboard')}
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.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.cust}</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.type === 'ORDER' ? 'warning' : 'success'}>
{item.type === 'ORDER' ? 'Order (訂單)' : 'Sample (樣品)'}
</Badge>
<span className="text-xs text-slate-400">來源單號: {item.match_target.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.match_target.cust}</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.match_target.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>
)}
{/* --- View 3: Dashboard --- */}
{activeTab === 'dashboard' && (
<div className="space-y-6 animate-in fade-in slide-in-from-right-4 duration-500">
<div className="flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-slate-800">業務轉換率戰情室</h1>
<p className="text-slate-500 mt-1">數據來源DIT Report, 樣品紀錄, 訂單明細 (2025-12-17)</p>
</div>
<button className="flex items-center gap-2 px-4 py-2 border border-slate-300 rounded-lg text-slate-600 hover:bg-slate-50 text-sm font-medium">
<Download size={16} />
匯出報表
</button>
</div>
{/* KPI Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card className="p-5 border-l-4 border-l-indigo-500">
<div className="text-sm text-slate-500 mb-1">DIT 總案件數</div>
<div className="text-3xl font-bold text-slate-800">{dashboardData.totalDit}</div>
<div className="text-xs text-slate-400 mt-2">Raw Data Count</div>
</Card>
<Card className="p-5 border-l-4 border-l-purple-500">
<div className="text-sm text-slate-500 mb-1">成功送樣數</div>
<div className="text-3xl font-bold text-slate-800">{dashboardData.matchedSample}</div>
<div className="text-xs text-emerald-600 mt-2">
{processedCount > 0 ? `(含人工審核 +1)` : ''}
</div>
</Card>
<Card className="p-5 border-l-4 border-l-pink-500">
<div className="text-sm text-slate-500 mb-1">取得訂單 (Backlog)</div>
<div className="text-3xl font-bold text-slate-800">{dashboardData.matchedOrder}</div>
<div className="text-xs text-emerald-600 mt-2">
Match Rate: {dashboardData.conversionRate}%
</div>
</Card>
<Card className="p-5 border-l-4 border-l-emerald-500">
<div className="text-sm text-slate-500 mb-1">預估營收 (Revenue)</div>
<div className="text-3xl font-bold text-emerald-600">$14,000</div>
<div className="text-xs text-slate-400 mt-2">Based on matched orders</div>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Funnel Chart */}
<Card className="lg:col-span-2 p-6">
<h3 className="font-bold text-slate-700 mb-6 flex items-center gap-2">
<Filter size={18} />
DIT 轉換漏斗 (Funnel Analysis)
</h3>
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart
layout="vertical"
data={funnelData}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" horizontal={false} />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={100} />
<RechartsTooltip cursor={{fill: '#f1f5f9'}} />
<Bar dataKey="value" barSize={30} radius={[0, 4, 4, 0]} label={{ position: 'right' }}>
{funnelData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</Card>
{/* Status Pie Chart */}
<Card className="p-6">
<h3 className="font-bold text-slate-700 mb-6 flex items-center gap-2">
<PieChart size={18} />
訂單狀態佔比
</h3>
<div className="h-64 w-full flex justify-center">
<ResponsiveContainer width="100%" height="100%">
<RePieChart>
<Pie
data={[
{ name: 'Backlog', value: 800 },
{ name: 'Shipped', value: 1200 },
]}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={5}
dataKey="value"
>
<Cell fill="#ec4899" />
<Cell fill="#10b981" />
</Pie>
<RechartsTooltip />
<Legend verticalAlign="bottom" height={36}/>
</RePieChart>
</ResponsiveContainer>
</div>
</Card>
</div>
{/* Attribution Table Preview */}
<Card className="overflow-hidden">
<div className="bg-slate-50 px-6 py-4 border-b border-slate-200 flex justify-between items-center">
<h3 className="font-bold text-slate-700 flex items-center gap-2">
<Activity size={18} />
DIT 歸因明細表 (LIFO 邏輯)
</h3>
<div className="text-xs text-slate-500">
<span className="flex items-center gap-1">
<Info size={12} />
Hover to see order details
</span>
</div>
</div>
<table className="w-full text-sm text-left">
<thead className="bg-white text-slate-500 border-b border-slate-200">
<tr>
<th className="px-6 py-3 w-32">OP編號</th>
<th className="px-6 py-3">Customer</th>
<th className="px-6 py-3">Part No.</th>
<th className="px-6 py-3 text-right">EAU</th>
<th className="px-6 py-3 text-center">Sample Order</th>
<th className="px-6 py-3 text-center">Order Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{PARSED_DIT_DATA.map((row, i) => {
// 簡單模擬關聯邏輯 (Find exact matches or partial matches)
const matchedOrder = PARSED_ORDER_DATA.find(o => o.customer.includes(row.customer) || row.customer.includes(o.customer.substring(0, 2)));
const matchedSample = PARSED_SAMPLE_DATA.find(s => s.customer.includes(row.customer) || row.customer.includes(s.customer.substring(0, 2)));
// 人工審核修正 (台達電子 & 合美)
const isReviewedSample = (row.customer === '台達電子' && processedCount > 0) ? {order_no: 'S202512490'} : null;
const isReviewedOrder = (row.customer === '合美企業' && processedCount > 1) ? {order_no: '1125025312'} : null;
// 決定顯示內容
const finalSample = matchedSample || isReviewedSample;
const finalOrder = matchedOrder || isReviewedOrder;
return (
<tr key={i} className="hover:bg-slate-50 group transition-colors">
<td className="px-6 py-3 font-mono text-xs text-slate-500 font-bold">
{row.id}
</td>
<td className="px-6 py-3 font-medium text-slate-800">
{row.customer}
<div className="text-[10px] text-slate-400 font-light">{row.stage}</div>
</td>
<td className="px-6 py-3 font-mono text-slate-600 text-xs">{row.pn}</td>
<td className="px-6 py-3 text-right font-mono text-slate-600">{row.eau.toLocaleString()}</td>
{/* Sample Column */}
<td className="px-6 py-3 text-center">
{finalSample ? (
<div className="inline-flex items-center gap-1 px-2 py-1 bg-purple-50 text-purple-700 rounded-md text-xs font-mono border border-purple-100 cursor-help" title={`送樣單號: ${finalSample.order_no}`}>
<CheckCircle size={12} />
{finalSample.order_no}
</div>
) : (
<span className="text-slate-300">-</span>
)}
</td>
{/* Order Column */}
<td className="px-6 py-3 text-center">
{finalOrder ? (
<div className="inline-flex items-center gap-1 px-2 py-1 bg-emerald-50 text-emerald-700 rounded-md text-xs font-mono border border-emerald-100 cursor-help" title={`訂單單號: ${finalOrder.order_no}`}>
<CheckCircle size={12} />
{finalOrder.order_no}
</div>
) : (
<span className="text-slate-300">-</span>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</Card>
</div>
)}
</main>
</div>
);
}