Files
Warner-Vistex-Automater/public/assets/app.js
T
Stefan Lazic a451060c1b Initial
Takes in a file, process it and output a new file with additional columns.
2026-07-10 15:45:02 +01:00

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);
}
})();