From a1ea5e95e5c2c71effa93dd1e5d4ea7c8071d35d Mon Sep 17 00:00:00 2001 From: averel10 Date: Fri, 13 Mar 2026 09:28:15 +0100 Subject: [PATCH] feat: enhance file upload progress tracking in UploadDatasetEntriesModal --- src/components/UploadDatasetEntriesModal.tsx | 141 ++++++++++++++----- 1 file changed, 103 insertions(+), 38 deletions(-) diff --git a/src/components/UploadDatasetEntriesModal.tsx b/src/components/UploadDatasetEntriesModal.tsx index 47b9570..888df32 100644 --- a/src/components/UploadDatasetEntriesModal.tsx +++ b/src/components/UploadDatasetEntriesModal.tsx @@ -16,7 +16,7 @@ export default function UploadDatasetEntriesModal({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); - const [uploadQueue, setUploadQueue] = useState>([]); + const [uploadQueue, setUploadQueue] = useState>([]); const [status, setStatus] = useState(''); function handleFileChange(e: React.ChangeEvent) { @@ -24,8 +24,8 @@ export default function UploadDatasetEntriesModal({ if (selectedFiles.length > 0) { setFiles(selectedFiles); setError(null); - // Initialize queue with pending status - setUploadQueue(selectedFiles.map(file => ({ file, status: 'pending' }))); + // Initialize queue with pending status and 0 progress + setUploadQueue(selectedFiles.map(file => ({ file, status: 'pending', progress: 0 }))); } } @@ -48,37 +48,23 @@ export default function UploadDatasetEntriesModal({ for (let i = 0; i < newQueue.length; i++) { const item = newQueue[i]; item.status = 'uploading'; + item.progress = 0; setUploadQueue([...newQueue]); setStatus(`Processing file ${i + 1} of ${newQueue.length}: ${item.file.name}`); try { - const formData = new FormData(); - formData.append('file', item.file); - formData.append('datasetId', datasetId.toString()); - - const uploadResponse = await fetch('/api/upload', { - method: 'POST', - body: formData, - }); - - if (!uploadResponse.ok) { - const errorData = await uploadResponse.json(); - throw new Error(errorData.error || 'Failed to upload ZIP file'); - } - - const uploadResult = await uploadResponse.json(); - - if (!uploadResult.success) { - throw new Error('Failed to upload ZIP file'); - } + // Upload with progress tracking + const uploadResult = await uploadFileWithProgress(item.file, i, newQueue); // Process the extracted data const processResult = await processDatasetEntries(datasetId, uploadResult.tempDir); item.status = 'completed'; + item.progress = 100; item.message = `✓ Processed (${processResult.entriesCreated} entries)`; } catch (err) { item.status = 'failed'; + item.progress = 0; item.message = err instanceof Error ? err.message : 'Unknown error'; } @@ -117,6 +103,62 @@ export default function UploadDatasetEntriesModal({ } } + function uploadFileWithProgress( + file: File, + index: number, + queue: Array<{ file: File; status: 'pending' | 'uploading' | 'completed' | 'failed'; progress: number; message?: string }> + ): Promise<{ tempDir: string }> { + return new Promise((resolve, reject) => { + const formData = new FormData(); + formData.append('file', file); + formData.append('datasetId', datasetId.toString()); + + const xhr = new XMLHttpRequest(); + + // Track upload progress + xhr.upload.addEventListener('progress', (event) => { + if (event.lengthComputable) { + const progress = Math.round((event.loaded / event.total) * 100); + queue[index].progress = progress; + setUploadQueue([...queue]); + } + }); + + xhr.addEventListener('load', () => { + if (xhr.status === 200) { + try { + const result = JSON.parse(xhr.responseText); + if (result.success) { + resolve(result); + } else { + reject(new Error(result.error || 'Upload failed')); + } + } catch (error) { + reject(new Error('Failed to parse response')); + } + } else { + try { + const error = JSON.parse(xhr.responseText); + reject(new Error(error.error || `Upload failed with status ${xhr.status}`)); + } catch { + reject(new Error(`Upload failed with status ${xhr.status}`)); + } + } + }); + + xhr.addEventListener('error', () => { + reject(new Error('Upload failed')); + }); + + xhr.addEventListener('abort', () => { + reject(new Error('Upload aborted')); + }); + + xhr.open('POST', '/api/upload'); + xhr.send(formData); + }); + } + function handleClose() { if (!loading) { setIsOpen(false); @@ -215,24 +257,47 @@ export default function UploadDatasetEntriesModal({ {uploadQueue.length > 0 && ( -
+

Upload Queue:

-
+
{uploadQueue.map((item, idx) => ( -
- - {item.status === 'completed' ? '✓' : - item.status === 'failed' ? '✗' : - item.status === 'uploading' ? '⟳' : - '○'} - - {item.file.name} - {item.message && {item.message}} +
+
+ + {item.status === 'completed' ? '✓' : + item.status === 'failed' ? '✗' : + item.status === 'uploading' ? '⟳' : + '○'} + + {item.file.name} + {item.progress > 0 && item.progress < 100 && ( + {item.progress}% + )} + {item.message && {item.message}} +
+ {item.status === 'uploading' && ( +
+
+
+ )} + {item.status === 'completed' && ( +
+
+
+ )} + {item.status === 'failed' && ( +
+
+
+ )}
))}