Spaces:
Running
Running
| import React from 'react'; | |
| import { FileItem, UploadStatus } from '../types'; | |
| import { FileText, Loader2, Check, AlertCircle, ExternalLink, X } from 'lucide-react'; | |
| interface UploadListProps { | |
| files: FileItem[]; | |
| onRemove: (id: string) => void; | |
| onPathChange: (id: string, newPath: string) => void; | |
| } | |
| export const UploadList: React.FC<UploadListProps> = ({ files, onRemove, onPathChange }) => { | |
| if (files.length === 0) return null; | |
| return ( | |
| <div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> | |
| <div className="px-6 py-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center"> | |
| <h3 className="font-semibold text-gray-800">Upload Queue ({files.length})</h3> | |
| <span className="text-xs text-gray-500">You can rename the destination path below</span> | |
| </div> | |
| <div className="divide-y divide-gray-100"> | |
| {files.map((item) => ( | |
| <div key={item.id} className="p-4 hover:bg-gray-50 transition-colors flex items-center gap-4 group"> | |
| <div className="p-2 bg-blue-50 text-blue-600 rounded-lg"> | |
| <FileText className="w-5 h-5" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <span className="font-medium text-gray-900 truncate max-w-[200px]" title={item.file.name}> | |
| {item.file.name} | |
| </span> | |
| <span className="text-xs text-gray-400"> | |
| ({(item.file.size / 1024).toFixed(1)} KB) | |
| </span> | |
| </div> | |
| {item.status === UploadStatus.IDLE && ( | |
| <div className="flex items-center gap-2"> | |
| <span className="text-xs text-gray-500">To:</span> | |
| <input | |
| type="text" | |
| value={item.path} | |
| onChange={(e) => onPathChange(item.id, e.target.value)} | |
| className="text-xs py-1 px-2 border border-gray-200 rounded bg-white focus:border-yellow-400 outline-none w-full max-w-xs" | |
| placeholder="path/to/file.ext" | |
| /> | |
| </div> | |
| )} | |
| {item.status === UploadStatus.UPLOADING && ( | |
| <span className="text-xs text-blue-600 flex items-center gap-1"> | |
| Processing... | |
| </span> | |
| )} | |
| {item.status === UploadStatus.SUCCESS && ( | |
| <a | |
| href={item.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-xs text-green-600 hover:text-green-700 hover:underline flex items-center gap-1" | |
| > | |
| View on Hub <ExternalLink className="w-3 h-3" /> | |
| </a> | |
| )} | |
| {item.status === UploadStatus.ERROR && ( | |
| <span className="text-xs text-red-600 truncate" title={item.error}> | |
| {item.error} | |
| </span> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {item.status === UploadStatus.UPLOADING && ( | |
| <Loader2 className="w-5 h-5 text-blue-500 animate-spin" /> | |
| )} | |
| {item.status === UploadStatus.SUCCESS && ( | |
| <div className="p-1 bg-green-100 rounded-full"> | |
| <Check className="w-4 h-4 text-green-600" /> | |
| </div> | |
| )} | |
| {item.status === UploadStatus.ERROR && ( | |
| <div className="p-1 bg-red-100 rounded-full group-hover:hidden"> | |
| <AlertCircle className="w-4 h-4 text-red-600" /> | |
| </div> | |
| )} | |
| {item.status !== UploadStatus.UPLOADING && item.status !== UploadStatus.SUCCESS && ( | |
| <button | |
| onClick={() => onRemove(item.id)} | |
| className="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors" | |
| > | |
| <X className="w-4 h-4" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| }; |