import React, { memo } from 'react'; import type { DownloadHistoryEntry } from '../types'; import TrashIcon from './TrashIcon'; import RetryIcon from './RetryIcon'; import CheckIcon from './CheckIcon'; import XCircleIcon from './XCircleIcon'; import { CopyButtonWithModal } from './CopyButtonWithModal'; import { TruncatedPath } from './TruncatedPath'; import { FolderPathDisplay } from './FolderPathDisplay'; // Helper function to format file size (extracted from App.tsx) const formatFileSize = (bytes: number | undefined): string => { if (!bytes || bytes === 0) return ''; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } const formatted = unitIndex === 0 ? size.toString() : size.toFixed(2); return ` (${formatted} ${units[unitIndex]})`; }; interface DownloadHistoryItemProps { entry: DownloadHistoryEntry; onDelete: (id: string) => void; onRetry: (entry: DownloadHistoryEntry) => void; onErrorClick: (details: string) => void; } const DownloadHistoryItem: React.FC = ({ entry, onDelete, onRetry, onErrorClick }) => { // Use geIdAndLang from entry (already formatted by converter) const geIdAndLang = entry.geIdAndLang || 'N/A'; // Determine overall status const getStatus = () => { if (entry.successCount === entry.totalFiles) return 'success'; if (entry.successCount === 0) return 'error'; return 'partial'; }; const status = getStatus(); // Get status icon and color const getStatusDisplay = () => { switch (status) { case 'success': return { icon: , color: 'text-green-400', bg: 'bg-green-900/5 border-green-700/25', label: 'Hoàn thành', }; case 'error': return { icon: , color: 'text-red-400', bg: 'bg-red-900/30 border-red-700/25', label: 'Thất bại', }; case 'partial': return { icon: , color: 'text-yellow-400', bg: 'bg-yellow-900/30 border-yellow-700/25', label: 'Một phần', }; } }; const statusDisplay = getStatusDisplay(); // Get error details for failed files const getErrorDetails = () => { const failedFiles = entry.files.filter((f) => f.status === 'error'); if (failedFiles.length === 0) return null; return failedFiles .map((f) => `${f.name}: ${f.message}`) .join('\n'); }; const errorDetails = getErrorDetails(); return (
{/* Header: GE ID, Time, Delete Button */}
{geIdAndLang} {entry.timestamp}
{/* Action Buttons */}
{errorDetails && ( )} {status !== 'success' && ( )}
{/* Path Flow: Lezhin Disk → NAS Path */}
{/* Lezhin Disk Path (Source - only show once) */} {entry.mongoDbPath && (
)} {/* Arrow Icon */} {entry.mongoDbPath && entry.files.length > 0 && ( )} {/* NAS Path (Folder name only - deduplicated) */}
{(() => { // Get unique folder names from all files const folderNames = new Set( entry.files .map((file: any) => { const filePath = file.path || ''; if (!filePath) return ''; // Extract folder: D:/.../raw/1000_DE/file.zip → D:/.../raw/1000_DE // Keep original path separators (don't convert \ to /) const cleanPath = filePath.replace(/[/\\]+$/, ''); const lastSepIndex = Math.max( cleanPath.lastIndexOf('/'), cleanPath.lastIndexOf('\\') ); return lastSepIndex > 0 ? cleanPath.substring(0, lastSepIndex) : cleanPath; }) .filter(Boolean) ); // Display unique folders return Array.from(folderNames).map((folderPath, index) => ( )); })()}
{/* Files List */} {entry.files.length > 0 && (
{entry.files.map((file: any, index) => { const isSuccess = file.status === 'success' || file.status === 'completed'; const isFailed = file.status === 'failed' || file.status === 'error'; const sizeStr = formatFileSize(file.file_size); const is38Bytes = file.file_size === 38; // Detect 38B error file return (
{file.name}{sizeStr}
); })}
)}
); }; export default memo(DownloadHistoryItem);