Spaces:
Running
Running
| window.Upload = { | |
| frontFile: null, | |
| backFile: null, | |
| isProcessing: false, | |
| init: function () { | |
| var self = this; | |
| // Doc type selector — show/hide back upload | |
| var docTypeSelect = Utils.$('#doc-type-select'); | |
| docTypeSelect.addEventListener('change', function () { | |
| self._updateLayout(); | |
| self._updateProcessButton(); | |
| }); | |
| // Front drop zone | |
| this._setupDropZone('front'); | |
| // Back drop zone | |
| this._setupDropZone('back'); | |
| // Process button | |
| Utils.$('#btn-process').addEventListener('click', function () { | |
| self.processDocument(); | |
| }); | |
| this._updateLayout(); | |
| }, | |
| _setupDropZone: function (side) { | |
| var self = this; | |
| var dropZone = Utils.$('#drop-zone-' + side); | |
| var fileInput = Utils.$('#file-input-' + side); | |
| // Click to browse (skip in camera-only mode) | |
| dropZone.addEventListener('click', function (e) { | |
| if (e.target.classList.contains('btn-remove')) return; | |
| if (e.target.classList.contains('btn-camera')) return; | |
| if (dropZone.classList.contains('camera-only')) return; | |
| if (!self.isProcessing) fileInput.click(); | |
| }); | |
| dropZone.addEventListener('keydown', function (e) { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| if (dropZone.classList.contains('camera-only')) return; | |
| if (!self.isProcessing) fileInput.click(); | |
| } | |
| }); | |
| // File input change | |
| fileInput.addEventListener('change', function () { | |
| if (fileInput.files.length > 0) { | |
| self._setFile(side, fileInput.files[0]); | |
| } | |
| fileInput.value = ''; | |
| }); | |
| // Drag and drop | |
| dropZone.addEventListener('dragover', function (e) { | |
| e.preventDefault(); | |
| if (!dropZone.classList.contains('camera-only')) { | |
| dropZone.classList.add('drag-active'); | |
| } | |
| }); | |
| dropZone.addEventListener('dragleave', function () { | |
| dropZone.classList.remove('drag-active'); | |
| }); | |
| dropZone.addEventListener('drop', function (e) { | |
| e.preventDefault(); | |
| dropZone.classList.remove('drag-active'); | |
| if (dropZone.classList.contains('camera-only')) return; | |
| if (!self.isProcessing && e.dataTransfer.files.length > 0) { | |
| self._setFile(side, e.dataTransfer.files[0]); | |
| } | |
| }); | |
| // Remove button | |
| var removeBtn = Utils.$('.btn-remove', dropZone); | |
| if (removeBtn) { | |
| removeBtn.addEventListener('click', function (e) { | |
| e.stopPropagation(); | |
| self._clearFile(side); | |
| }); | |
| } | |
| // Camera buttons (may be multiple: one in content, one in camera-only) | |
| var cameraBtns = dropZone.querySelectorAll('.btn-camera'); | |
| for (var i = 0; i < cameraBtns.length; i++) { | |
| cameraBtns[i].addEventListener('click', function (e) { | |
| e.stopPropagation(); | |
| if (!self.isProcessing) { | |
| Camera.open(side); | |
| } | |
| }); | |
| } | |
| }, | |
| _setFile: function (side, file) { | |
| var check = Utils.validateFile(file); | |
| if (!check.valid) { | |
| Utils.showToast(check.error, 'error'); | |
| return; | |
| } | |
| if (side === 'front') { | |
| this.frontFile = file; | |
| } else { | |
| this.backFile = file; | |
| } | |
| // Show preview | |
| var dropZone = Utils.$('#drop-zone-' + side); | |
| var content = Utils.$('.drop-zone-content', dropZone); | |
| var preview = Utils.$('.drop-zone-preview', dropZone); | |
| var thumb = Utils.$('.preview-thumb', dropZone); | |
| var nameEl = Utils.$('.preview-name', dropZone); | |
| content.classList.add('hidden'); | |
| var cameraOnly = Utils.$('.drop-zone-camera-only', dropZone); | |
| if (cameraOnly) cameraOnly.classList.add('hidden'); | |
| preview.classList.remove('hidden'); | |
| thumb.src = URL.createObjectURL(file); | |
| nameEl.textContent = file.name; | |
| this._updateProcessButton(); | |
| }, | |
| _clearFile: function (side) { | |
| if (side === 'front') { | |
| this.frontFile = null; | |
| } else { | |
| this.backFile = null; | |
| } | |
| var dropZone = Utils.$('#drop-zone-' + side); | |
| var content = Utils.$('.drop-zone-content', dropZone); | |
| var preview = Utils.$('.drop-zone-preview', dropZone); | |
| var thumb = Utils.$('.preview-thumb', dropZone); | |
| var cameraOnly = Utils.$('.drop-zone-camera-only', dropZone); | |
| var docType = Utils.$('#doc-type-select').value; | |
| if (side === 'front' && docType === 'product_serial' && cameraOnly) { | |
| content.classList.add('hidden'); | |
| cameraOnly.classList.remove('hidden'); | |
| } else { | |
| content.classList.remove('hidden'); | |
| if (cameraOnly) cameraOnly.classList.add('hidden'); | |
| } | |
| preview.classList.add('hidden'); | |
| if (thumb.src) { | |
| URL.revokeObjectURL(thumb.src); | |
| thumb.src = ''; | |
| } | |
| this._updateProcessButton(); | |
| }, | |
| _updateLayout: function () { | |
| var docType = Utils.$('#doc-type-select').value; | |
| var backGroup = Utils.$('#back-upload-group'); | |
| var frontLabel = Utils.$('.upload-label'); | |
| var isSerial = docType === 'product_serial'; | |
| if (docType === 'sa_id_card') { | |
| backGroup.classList.remove('hidden'); | |
| } else { | |
| backGroup.classList.add('hidden'); | |
| this._clearFile('back'); | |
| } | |
| if (frontLabel) { | |
| frontLabel.innerHTML = isSerial | |
| ? 'Product Image <span class="required">*</span>' | |
| : 'Front Image <span class="required">*</span>'; | |
| } | |
| // Serial mode: camera-only for front drop zone | |
| var frontDropZone = Utils.$('#drop-zone-front'); | |
| if (isSerial) { | |
| this._clearFile('front'); | |
| frontDropZone.classList.add('camera-only'); | |
| } else { | |
| var frontPreview = Utils.$('.drop-zone-preview', frontDropZone); | |
| if (frontPreview.classList.contains('hidden')) { | |
| Utils.$('.drop-zone-content', frontDropZone).classList.remove('hidden'); | |
| } | |
| var frontCameraOnly = Utils.$('.drop-zone-camera-only', frontDropZone); | |
| if (frontCameraOnly) frontCameraOnly.classList.add('hidden'); | |
| frontDropZone.classList.remove('camera-only'); | |
| } | |
| }, | |
| _updateProcessButton: function () { | |
| var btn = Utils.$('#btn-process'); | |
| btn.disabled = !this.frontFile || this.isProcessing; | |
| }, | |
| processDocument: function () { | |
| if (!this.frontFile || this.isProcessing) return; | |
| var self = this; | |
| var docType = Utils.$('#doc-type-select').value; | |
| this.isProcessing = true; | |
| this._updateProcessButton(); | |
| Results.showLoading(this.frontFile); | |
| var startTime = performance.now(); | |
| var isSerial = docType === 'product_serial'; | |
| var apiCall = isSerial | |
| ? Api.scanSerial(this.frontFile) | |
| : Api.processDocument(this.frontFile, docType, this.backFile); | |
| apiCall | |
| .then(function (result) { | |
| var duration = performance.now() - startTime; | |
| if (isSerial) { | |
| Results.renderSerial(result, self.frontFile); | |
| } else { | |
| Results.render(result, self.frontFile); | |
| } | |
| History.add(History.buildEntry(result, self.frontFile, duration, null)); | |
| Utils.showToast(isSerial ? 'Serial number processed' : 'Document processed successfully', 'success'); | |
| }) | |
| .catch(function (err) { | |
| var duration = performance.now() - startTime; | |
| Results.showError(err.message); | |
| History.add(History.buildEntry(null, self.frontFile, duration, err.message)); | |
| if (err.status === 401) { | |
| Utils.$('#api-key-input').classList.add('error'); | |
| Utils.showToast('Authentication failed. Check your API key.', 'error'); | |
| } else if (err.status === 429) { | |
| Utils.showToast('Rate limit exceeded. Wait a moment and try again.', 'warning'); | |
| } else { | |
| Utils.showToast(err.message, 'error'); | |
| } | |
| }) | |
| .finally(function () { | |
| self.isProcessing = false; | |
| self._updateProcessButton(); | |
| }); | |
| }, | |
| }; | |