(() => { const form = document.getElementById('importForm'); const clearButton = document.getElementById('clearButton'); const progressBar = document.getElementById('importProgress'); const progressLabel = document.getElementById('progressLabel'); const progressPercent = document.getElementById('progressPercent'); const totalRowsLabel = document.getElementById('importTotalRows'); const chunkSizeLabel = document.getElementById('importChunkSize'); const statusAlert = document.getElementById('statusAlert'); const fileInput = document.getElementById('spreadsheet'); const tokenStorageKey = 'warnerImportToken'; let importToken = window.sessionStorage.getItem(tokenStorageKey) || ''; let chunkTimer = null; let chunkInFlight = false; if (!form || !progressBar || !progressLabel || !progressPercent || !statusAlert) { return; } const setStatus = (type, message) => { const classes = { success: 'border-emerald-200 bg-emerald-50 text-emerald-900', danger: 'border-rose-200 bg-rose-50 text-rose-900', warning: 'border-amber-200 bg-amber-50 text-amber-900', info: 'border-sky-200 bg-sky-50 text-sky-900', }; statusAlert.className = `mt-4 rounded-2xl border px-4 py-3 text-sm font-medium ${classes[type] || classes.info}`; statusAlert.textContent = message; statusAlert.classList.remove('hidden'); }; const setProgress = (value, label) => { const percent = Math.max(0, Math.min(100, Math.round(value))); progressBar.style.width = `${percent}%`; progressBar.setAttribute('aria-valuenow', String(percent)); progressPercent.textContent = `${percent}%`; progressLabel.textContent = label; }; const setRowMetrics = (totalRows, chunkSize) => { if (totalRowsLabel) { totalRowsLabel.textContent = Number.isFinite(totalRows) ? String(totalRows.toLocaleString()) : '0'; } if (chunkSizeLabel && Number.isFinite(chunkSize)) { chunkSizeLabel.textContent = String(chunkSize.toLocaleString()); } }; const clearImportState = () => { importToken = ''; window.sessionStorage.removeItem(tokenStorageKey); if (chunkTimer !== null) { window.clearTimeout(chunkTimer); chunkTimer = null; } chunkInFlight = false; }; const scheduleChunk = (offset, delay = 200) => { if (!importToken) { return; } if (chunkTimer !== null) { window.clearTimeout(chunkTimer); } chunkTimer = window.setTimeout(() => { processChunk(offset); }, delay); }; const parseJsonResponse = (xhr) => { try { return JSON.parse(xhr.responseText); } catch (error) { return null; } }; const confirmDestructiveAction = (message) => window.confirm(message); const deriveProgressPercent = (response) => { if (response && response.progressPercent !== undefined) { return response.progressPercent; } const total = Number(response && response.total ? response.total : 0); const offset = Number(response && response.offset ? response.offset : 0); if (total <= 0) { return response && response.done ? 100 : 0; } return Math.min(99, Math.round((offset / total) * 100)); }; const processChunk = (offset = 0) => { if (!importToken || chunkInFlight) { return; } chunkInFlight = true; const xhr = new XMLHttpRequest(); xhr.open('POST', window.APP_CONFIG.processChunkUrl, true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = () => { if (xhr.readyState !== XMLHttpRequest.DONE) { return; } chunkInFlight = false; if (xhr.status >= 200 && xhr.status < 300) { const response = parseJsonResponse(xhr); if (!response) { setStatus('danger', 'Import response was invalid.'); clearImportState(); return; } const progress = deriveProgressPercent(response); const message = response.message || 'Processing workbook...'; setProgress(progress, message); if (response.totalRows !== undefined || response.chunkSize !== undefined) { setRowMetrics( Number(response.totalRows ?? 0), Number(response.chunkSize ?? (window.APP_CONFIG.importChunkSize ?? 0)) ); } if (response.done) { setProgress(100, response.message || 'Import complete'); setStatus('success', response.message || 'Import complete. Reloading preview...'); clearImportState(); window.setTimeout(() => { window.location.href = window.APP_CONFIG.reloadUrl; }, 800); return; } setStatus('info', message); scheduleChunk(response.offset !== undefined ? response.offset : (offset + 1), 200); return; } const response = parseJsonResponse(xhr); const responseMessage = response && response.message ? response.message : 'Import failed.'; setProgress(0, 'Ready to import'); setStatus('danger', responseMessage); clearImportState(); }; xhr.send( `action=process-chunk&token=${encodeURIComponent(importToken)}&offset=${encodeURIComponent(String(offset))}` ); }; form.addEventListener('submit', (event) => { event.preventDefault(); statusAlert.classList.add('hidden'); if (!fileInput.files || fileInput.files.length === 0) { setStatus('danger', 'Please choose an Excel file before importing.'); return; } if (!confirmDestructiveAction('Importing a new file will wipe the current imported data before processing. Continue?')) { setStatus('info', 'Import cancelled.'); setProgress(0, 'Ready to import'); return; } clearImportState(); const formData = new FormData(form); const xhr = new XMLHttpRequest(); xhr.open('POST', window.APP_CONFIG.importUrl, true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.upload.onprogress = (event) => { if (!event.lengthComputable) { setProgress(65, 'Uploading file...'); return; } const percent = Math.max(1, (event.loaded / event.total) * 70); setProgress(percent, 'Uploading file...'); }; xhr.onreadystatechange = () => { if (xhr.readyState !== XMLHttpRequest.DONE) { return; } if (xhr.status >= 200 && xhr.status < 300) { const response = parseJsonResponse(xhr); if (response && response.token) { importToken = response.token; window.sessionStorage.setItem(tokenStorageKey, importToken); setProgress(5, response.message || 'Upload received. Starting chunked import...'); setRowMetrics(0, Number(window.APP_CONFIG.importChunkSize || 0)); setStatus('info', response.message || 'Upload received. Starting chunked import...'); scheduleChunk(0, 100); return; } setStatus('danger', 'Upload succeeded, but the server did not return an import token.'); return; } const response = parseJsonResponse(xhr); const responseMessage = response && response.message ? response.message : 'Import failed.'; setProgress(0, 'Ready to import'); setStatus('danger', responseMessage); }; setProgress(5, 'Preparing upload...'); xhr.send(formData); setProgress(20, 'Uploading file...'); }); if (clearButton) { clearButton.addEventListener('click', () => { if (!confirmDestructiveAction('This will permanently wipe all imported rows. Continue?')) { return; } const xhr = new XMLHttpRequest(); xhr.open('POST', window.APP_CONFIG.clearUrl, true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = () => { if (xhr.readyState !== XMLHttpRequest.DONE) { return; } if (xhr.status >= 200 && xhr.status < 300) { setProgress(0, 'Ready to import'); setStatus('success', 'Imported data cleared. Reloading page...'); clearImportState(); window.setTimeout(() => { window.location.href = window.APP_CONFIG.reloadUrl; }, 800); return; } const response = parseJsonResponse(xhr); const responseMessage = response && response.message ? response.message : 'Clear failed.'; setStatus('danger', responseMessage); }; xhr.send('action=clear'); }); } if (importToken) { setProgress(10, 'Resuming import...'); setStatus('info', 'Resuming the last import job...'); setRowMetrics(0, Number(window.APP_CONFIG.importChunkSize || 0)); scheduleChunk(0, 200); } })();