Hoghoghi / app /frontend /js /file-upload-handler.js
Really-amin's picture
Upload 25 files
89ba751 verified
raw
history blame
10.9 kB
/**
* File Upload Handler for Legal Dashboard
* Manages document uploads, OCR processing, and real-time progress
*/
class FileUploadHandler {
constructor() {
this.uploadEndpoint = '/api/ocr/upload';
this.processEndpoint = '/api/ocr/process';
this.maxFileSize = 10 * 1024 * 1024; // 10MB
this.allowedTypes = ['application/pdf', 'image/jpeg', 'image/png', 'image/tiff'];
this.currentUpload = null;
this.uploadQueue = [];
this.initializeEventListeners();
}
initializeEventListeners() {
// File input change
const fileInput = document.getElementById('documentUpload');
if (fileInput) {
fileInput.addEventListener('change', (e) => this.handleFileSelection(e));
}
// Upload button
const uploadBtn = document.getElementById('uploadButton');
if (uploadBtn) {
uploadBtn.addEventListener('click', () => this.startUpload());
}
// Drag and drop
const dropZone = document.getElementById('uploadDropZone');
if (dropZone) {
dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
dropZone.addEventListener('drop', (e) => this.handleDrop(e));
dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
}
}
handleFileSelection(event) {
const files = event.target.files;
if (files.length > 0) {
this.validateAndQueueFiles(files);
}
}
handleDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add('drag-over');
}
handleDragLeave(event) {
event.preventDefault();
event.currentTarget.classList.remove('drag-over');
}
handleDrop(event) {
event.preventDefault();
event.currentTarget.classList.remove('drag-over');
const files = event.dataTransfer.files;
if (files.length > 0) {
this.validateAndQueueFiles(files);
}
}
validateAndQueueFiles(files) {
const validFiles = [];
const errors = [];
for (let file of files) {
// Check file size
if (file.size > this.maxFileSize) {
errors.push(`${file.name}: File too large (max 10MB)`);
continue;
}
// Check file type
if (!this.allowedTypes.includes(file.type)) {
errors.push(`${file.name}: Unsupported file type`);
continue;
}
validFiles.push(file);
}
// Show errors if any
if (errors.length > 0) {
this.showErrors(errors);
}
// Queue valid files
if (validFiles.length > 0) {
this.uploadQueue.push(...validFiles);
this.updateUploadQueue();
}
}
updateUploadQueue() {
const queueContainer = document.getElementById('uploadQueue');
if (!queueContainer) return;
if (this.uploadQueue.length === 0) {
queueContainer.innerHTML = '<p class="no-files">هیچ فایلی برای آپلود انتخاب نشده</p>';
return;
}
const queueHTML = this.uploadQueue.map((file, index) => `
<div class="queue-item" data-index="${index}">
<div class="file-info">
<span class="file-name">${file.name}</span>
<span class="file-size">${this.formatFileSize(file.size)}</span>
</div>
<div class="file-actions">
<button class="remove-file" onclick="fileUploadHandler.removeFromQueue(${index})">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`).join('');
queueContainer.innerHTML = queueHTML;
}
removeFromQueue(index) {
this.uploadQueue.splice(index, 1);
this.updateUploadQueue();
}
async startUpload() {
if (this.uploadQueue.length === 0) {
this.showToast('لطفاً فایلی برای آپلود انتخاب کنید', 'warning');
return;
}
if (this.currentUpload) {
this.showToast('آپلود در حال انجام است، لطفاً صبر کنید', 'warning');
return;
}
this.currentUpload = true;
this.showUploadProgress();
try {
for (let i = 0; i < this.uploadQueue.length; i++) {
const file = this.uploadQueue[i];
await this.uploadFile(file, i + 1, this.uploadQueue.length);
}
this.showToast('تمام فایل‌ها با موفقیت آپلود شدند', 'success');
this.refreshDocumentsList();
} catch (error) {
this.showToast(`خطا در آپلود: ${error.message}`, 'error');
} finally {
this.currentUpload = false;
this.hideUploadProgress();
this.uploadQueue = [];
this.updateUploadQueue();
}
}
async uploadFile(file, current, total) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
formData.append('filename', file.name);
const xhr = new XMLHttpRequest();
// Progress tracking
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
this.updateProgress(percentComplete, current, total);
}
});
// Response handling
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
this.handleUploadSuccess(response, file);
resolve(response);
} catch (error) {
reject(new Error('Invalid response format'));
}
} else {
reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('Network error during upload'));
});
xhr.addEventListener('abort', () => {
reject(new Error('Upload cancelled'));
});
// Start upload
xhr.open('POST', this.uploadEndpoint);
xhr.send(formData);
});
}
handleUploadSuccess(response, file) {
// Update dashboard stats
this.updateDashboardStats();
// Show success message
this.showToast(`${file.name} با موفقیت آپلود شد`, 'success');
// Process OCR if needed
if (response.document_id) {
this.processOCR(response.document_id, file.name);
}
}
async processOCR(documentId, fileName) {
try {
const response = await fetch(this.processEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
document_id: documentId,
filename: fileName
})
});
if (!response.ok) {
throw new Error(`OCR processing failed: ${response.status}`);
}
const result = await response.json();
this.showOCRResult(result, fileName);
} catch (error) {
this.showToast(`خطا در پردازش OCR: ${error.message}`, 'error');
}
}
showOCRResult(result, fileName) {
const ocrResultsContainer = document.getElementById('ocrResults');
if (!ocrResultsContainer) return;
const resultHTML = `
<div class="ocr-result">
<h4>نتایج OCR - ${fileName}</h4>
<div class="ocr-content">
<p><strong>کیفیت:</strong> ${(result.quality || 0).toFixed(2)}%</p>
<p><strong>متن استخراج شده:</strong></p>
<div class="extracted-text">
${result.text || 'متنی استخراج نشد'}
</div>
</div>
</div>
`;
ocrResultsContainer.insertAdjacentHTML('afterbegin', resultHTML);
}
updateProgress(percent, current, total) {
const progressBar = document.getElementById('uploadProgressBar');
const progressText = document.getElementById('uploadProgressText');
if (progressBar) {
progressBar.style.width = `${percent}%`;
}
if (progressText) {
progressText.textContent = `آپلود فایل ${current} از ${total} (${Math.round(percent)}%)`;
}
}
showUploadProgress() {
const progressContainer = document.getElementById('uploadProgress');
if (progressContainer) {
progressContainer.style.display = 'block';
}
}
hideUploadProgress() {
const progressContainer = document.getElementById('uploadProgress');
if (progressContainer) {
progressContainer.style.display = 'none';
}
}
updateDashboardStats() {
// Trigger dashboard stats refresh
if (typeof loadDashboardStats === 'function') {
loadDashboardStats();
}
}
refreshDocumentsList() {
// Trigger documents list refresh
if (typeof loadDocumentsList === 'function') {
loadDocumentsList();
}
}
showErrors(errors) {
const errorMessage = errors.join('\n');
this.showToast(errorMessage, 'error');
}
showToast(message, type = 'info') {
if (typeof showToast === 'function') {
showToast(message, type);
} else {
console.log(`${type.toUpperCase()}: ${message}`);
}
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// Initialize file upload handler
const fileUploadHandler = new FileUploadHandler();