import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/Components/ui/dialog"; import { Badge } from "@/Components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/Components/ui/table"; import { User, Clock, Package, Activity as ActivityIcon } from "lucide-react"; interface Activity { id: number; description: string; subject_type: string; event: string; causer: string; created_at: string; properties: { attributes?: Record; old?: Record; snapshot?: Record; sub_subject?: string; items_diff?: { added: any[]; removed: any[]; updated: any[]; }; }; } interface Props { open: boolean; onOpenChange: (open: boolean) => void; activity: Activity | null; } // Field translation map const fieldLabels: Record = { name: '名稱', code: '代碼', username: '登入帳號', description: '描述', price: '價格', cost: '成本', stock: '庫存', category_id: '分類', unit_id: '單位', is_active: '啟用狀態', conversion_rate: '換算率', specification: '規格', brand: '品牌', base_unit_id: '基本單位', large_unit_id: '大單位', purchase_unit_id: '採購單位', email: 'Email', password: '密碼', phone: '電話', address: '地址', role_id: '角色', // Snapshot fields category_name: '分類名稱', base_unit_name: '基本單位名稱', large_unit_name: '大單位名稱', purchase_unit_name: '採購單位名稱', // Vendor fields short_name: '簡稱', tax_id: '統編', owner: '負責人', contact_name: '聯絡人', tel: '電話', remark: '備註', // Warehouse & Inventory fields warehouse_name: '倉庫名稱', product_name: '商品名稱', warehouse_id: '倉庫', product_id: '商品', quantity: '數量', safety_stock: '安全庫存', location: '儲位', // Purchase Order fields po_number: '採購單號', vendor_id: '廠商', vendor_name: '廠商名稱', user_name: '建單人員', user_id: '建單人員', total_amount: '小計', expected_delivery_date: '預計到貨日', status: '狀態', tax_amount: '稅額', grand_total: '總計', invoice_number: '發票號碼', invoice_date: '發票日期', invoice_amount: '發票金額', last_price: '供貨價格', }; // Purchase Order Status Map const statusMap: Record = { draft: '草稿', pending: '待審核', approved: '已核准', ordered: '已下單', received: '已收貨', cancelled: '已取消', completed: '已完成', }; export default function ActivityDetailDialog({ open, onOpenChange, activity }: Props) { if (!activity) return null; const attributes = activity.properties?.attributes || {}; const old = activity.properties?.old || {}; const snapshot = activity.properties?.snapshot || {}; // Get all keys from both attributes and old to ensure we show all changes const allKeys = Array.from(new Set([...Object.keys(attributes), ...Object.keys(old)])); // Custom sort order for fields const sortOrder = [ 'po_number', 'vendor_name', 'warehouse_name', 'expected_delivery_date', 'status', 'remark', 'invoice_number', 'invoice_date', 'invoice_amount', 'total_amount', 'tax_amount', 'grand_total' // Ensure specific order for amounts ]; // Filter out internal keys often logged but not useful for users const filteredKeys = allKeys .filter(key => !['created_at', 'updated_at', 'deleted_at', 'id'].includes(key) ) .sort((a, b) => { const indexA = sortOrder.indexOf(a); const indexB = sortOrder.indexOf(b); // If both are in sortOrder, compare indices if (indexA !== -1 && indexB !== -1) return indexA - indexB; // If only A is in sortOrder, it comes first (or wherever logic dictates, usually put known fields first) if (indexA !== -1) return -1; if (indexB !== -1) return 1; // Otherwise alphabetical or default return a.localeCompare(b); }); // Helper to check if a key is a snapshot name field const isSnapshotField = (key: string) => { return [ 'category_name', 'base_unit_name', 'large_unit_name', 'purchase_unit_name', 'warehouse_name', 'user_name' ].includes(key); }; const getEventBadgeClass = (event: string) => { switch (event) { case 'created': return 'bg-green-50 text-green-700 border-green-200'; case 'updated': return 'bg-blue-50 text-blue-700 border-blue-200'; case 'deleted': return 'bg-red-50 text-red-700 border-red-200'; default: return 'bg-gray-50 text-gray-700 border-gray-200'; } }; const getEventLabel = (event: string) => { switch (event) { case 'created': return '新增'; case 'updated': return '更新'; case 'deleted': return '刪除'; case 'updated_items': return '異動品項'; default: return event; } }; const formatValue = (key: string, value: any) => { // Mask password if (key === 'password') return '******'; if (value === null || value === undefined) return '-'; if (typeof value === 'boolean') return value ? '是' : '否'; if (key === 'is_active') return value ? '啟用' : '停用'; // Handle Purchase Order Status if (key === 'status' && typeof value === 'string' && statusMap[value]) { return statusMap[value]; } // Handle Date Fields (YYYY-MM-DD) if ((key === 'expected_delivery_date' || key === 'invoice_date') && typeof value === 'string') { // Take only the date part (YYYY-MM-DD) return value.split('T')[0].split(' ')[0]; } return String(value); }; const getFormattedValue = (key: string, value: any) => { // If it's an ID field, try to find a corresponding name in snapshot or attributes if (key.endsWith('_id')) { const nameKey = key.replace('_id', '_name'); // Check snapshot first, then attributes const nameValue = snapshot[nameKey] || attributes[nameKey]; if (nameValue) { return `${nameValue}`; } } return formatValue(key, value); }; // Helper to get translated field label const getFieldLabel = (key: string) => { return fieldLabels[key] || key; }; // Get subject name for header const getSubjectName = () => { // Special handling for Inventory: show "Warehouse - Product" if ((snapshot.warehouse_name || attributes.warehouse_name) && (snapshot.product_name || attributes.product_name)) { const wName = snapshot.warehouse_name || attributes.warehouse_name; const pName = snapshot.product_name || attributes.product_name; return `${wName} - ${pName}`; } const nameParams = ['po_number', 'name', 'code', 'product_name', 'warehouse_name', 'category_name', 'base_unit_name', 'title']; for (const param of nameParams) { if (snapshot[param]) return snapshot[param]; if (attributes[param]) return attributes[param]; if (old[param]) return old[param]; } if (attributes.id || old.id) return `#${attributes.id || old.id}`; return ''; }; const subjectName = getSubjectName(); return (
操作詳情 {getEventLabel(activity.event)}
{/* Modern Metadata Strip */}
{activity.causer}
{activity.created_at}
{subjectName ? `${subjectName} ` : ''} {activity.properties?.sub_subject || activity.subject_type}
{/* Only show 'description' if it differs from event name (unlikely but safe) */} {activity.description !== getEventLabel(activity.event) && activity.description !== 'created' && activity.description !== 'updated' && (
{activity.description}
)}
{activity.event === 'created' ? (
欄位 異動前 異動後 {filteredKeys .filter(key => attributes[key] !== null && attributes[key] !== '' && !isSnapshotField(key)) .map((key) => ( {getFieldLabel(key)} - {getFormattedValue(key, attributes[key])} ))} {filteredKeys.filter(key => attributes[key] !== null && attributes[key] !== '' && !isSnapshotField(key)).length === 0 && ( 無初始資料 )}
) : (
欄位 異動前 異動後 {filteredKeys.some(key => !isSnapshotField(key)) ? ( filteredKeys .filter(key => !isSnapshotField(key)) .map((key) => { const oldValue = old[key]; const newValue = attributes[key]; const isChanged = JSON.stringify(oldValue) !== JSON.stringify(newValue); // For deleted events, we want to show the current attributes in the "Before" column const displayBefore = activity.event === 'deleted' ? getFormattedValue(key, newValue || oldValue) : getFormattedValue(key, oldValue); const displayAfter = activity.event === 'deleted' ? '-' : getFormattedValue(key, newValue); return ( {getFieldLabel(key)} {displayBefore} {displayAfter} ); }) ) : ( 無詳細異動內容 )}
)} {/* Items Diff Section (Special for Purchase Orders) */} {activity.properties?.items_diff && (

品項異動明細

商品名稱 異動類型 異動詳情 (舊 → 新) {/* Updated Items */} {activity.properties.items_diff.updated.map((item: any, idx: number) => ( {item.product_name} 更新
{item.old.quantity !== item.new.quantity && (
數量: {item.old.quantity}{item.new.quantity}
)} {item.old.unit_name !== item.new.unit_name && (
單位: {item.old.unit_name || '-'}{item.new.unit_name || '-'}
)} {item.old.subtotal !== item.new.subtotal && (
小計: ${item.old.subtotal}${item.new.subtotal}
)}
))} {/* Added Items */} {activity.properties.items_diff.added.map((item: any, idx: number) => ( {item.product_name} 新增 數量: {item.quantity} {item.unit_name} / 小計: ${item.subtotal} ))} {/* Removed Items */} {activity.properties.items_diff.removed.map((item: any, idx: number) => ( {item.product_name} 移除 原紀錄: {item.quantity} {item.unit_name} ))}
)}
); }