270 lines
8.7 KiB
JavaScript
270 lines
8.7 KiB
JavaScript
(() => {
|
|
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);
|
|
}
|
|
})();
|