(function () { function initUserMenu() { var menu = document.querySelector('.user-menu'); var toggleButton = document.querySelector('[data-user-menu-toggle="true"]'); var dropdown = document.getElementById('userMenuDropdown'); if (!menu || !toggleButton || !dropdown) { return; } function closeMenu() { dropdown.classList.remove('active'); } function toggleMenu(event) { if (event) { event.preventDefault(); event.stopPropagation(); } dropdown.classList.toggle('active'); } toggleButton.addEventListener('click', toggleMenu); document.addEventListener('click', function (event) { if (!menu.contains(event.target)) { closeMenu(); } }); } function initPasswordModal() { var openButton = document.querySelector('.js-open-password-modal'); var closeButtons = document.querySelectorAll('.js-close-password-modal'); var modal = document.querySelector('.js-password-modal'); var form = document.getElementById('changePasswordForm'); var message = document.getElementById('passwordMessage'); if (!openButton || !closeButtons.length || !modal || !form || !message) { return; } function openModal() { modal.style.display = 'block'; form.reset(); message.innerHTML = ''; } function closeModal() { modal.style.display = 'none'; } openButton.addEventListener('click', openModal); closeButtons.forEach(function (button) { button.addEventListener('click', closeModal); }); document.addEventListener('click', function (event) { if (event.target === modal) { closeModal(); } }); form.addEventListener('submit', async function (event) { event.preventDefault(); var currentPassword = document.getElementById('currentPassword').value; var newPassword = document.getElementById('newPassword').value; var confirmPassword = document.getElementById('confirmPassword').value; var endpoint = form.dataset.changePasswordUrl; if (newPassword !== confirmPassword) { message.innerHTML = '
Passwords do not match
'; return; } try { var response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, confirm_password: confirmPassword }) }); var data = await response.json(); if (response.ok) { message.innerHTML = '
' + data.message + '
'; setTimeout(closeModal, 2000); } else { message.innerHTML = '
' + (data.error || 'Unable to update password') + '
'; } } catch (error) { message.innerHTML = '
An error occurred
'; } }); } function initUploadPage() { var tabs = document.querySelectorAll('.upload-tab'); var panels = document.querySelectorAll('.tab-panel'); if (!tabs.length || !panels.length) { return; } tabs.forEach(function (tab) { tab.addEventListener('click', function () { tabs.forEach(function (item) { item.classList.remove('active'); }); panels.forEach(function (panel) { panel.classList.remove('active'); }); tab.classList.add('active'); var target = document.getElementById('tab-' + tab.dataset.tab); if (target) { target.classList.add('active'); } }); }); function wireDropzone(options) { var zone = document.getElementById(options.zoneId); var input = document.getElementById(options.inputId); var info = document.getElementById(options.infoId); var label = document.getElementById(options.labelId); var clearButton = document.querySelector(options.clearSel); var submit = document.getElementById(options.submitId); var form = document.getElementById(options.formId); var overlay = document.getElementById(options.overlayId); if (!zone || !input || !info || !label || !submit) { return; } function showFiles(files) { var validFiles = []; for (var i = 0; i < files.length; i++) { var name = files[i].name.toLowerCase(); if (name.endsWith('.dcm') || name.endsWith('.zip')) { validFiles.push(files[i]); } } if (!validFiles.length) { return; } if (options.multi) { var totalSizeMB = 0; for (var j = 0; j < validFiles.length; j++) { totalSizeMB += validFiles[j].size / (1024 * 1024); } label.textContent = validFiles.length + ' file' + (validFiles.length > 1 ? 's' : '') + ' (' + totalSizeMB.toFixed(1) + ' MB)'; } else { label.textContent = validFiles[0].name; } info.style.display = 'flex'; zone.style.display = 'none'; submit.disabled = false; } function reset() { input.value = ''; info.style.display = 'none'; zone.style.display = 'flex'; submit.disabled = true; } zone.addEventListener('click', function () { input.click(); }); zone.addEventListener('dragover', function (event) { event.preventDefault(); zone.classList.add('dragover'); }); zone.addEventListener('dragleave', function () { zone.classList.remove('dragover'); }); zone.addEventListener('drop', function (event) { event.preventDefault(); zone.classList.remove('dragover'); if (event.dataTransfer.files.length) { input.files = event.dataTransfer.files; showFiles(event.dataTransfer.files); } }); input.addEventListener('change', function () { if (input.files.length) { showFiles(input.files); } }); if (clearButton) { clearButton.addEventListener('click', reset); } if (form && overlay) { form.addEventListener('submit', function () { overlay.style.display = 'flex'; submit.disabled = true; }); } } wireDropzone({ zoneId: 'dropzoneSingle', inputId: 'singleInput', infoId: 'singleInfo', labelId: 'singleFileName', clearSel: '.js-clear-single', submitId: 'singleSubmit', formId: 'singleForm', overlayId: 'singleOverlay', multi: false }); wireDropzone({ zoneId: 'dropzoneMulti', inputId: 'multiInput', infoId: 'multiInfo', labelId: 'multiFileName', clearSel: '.js-clear-multi', submitId: 'multiSubmit', formId: 'multiForm', overlayId: 'multiOverlay', multi: true }); var dirInput = document.getElementById('dirPath'); var dirSubmit = document.getElementById('dirSubmit'); if (dirInput && dirSubmit) { function checkDir() { dirSubmit.disabled = !dirInput.value.trim(); } dirInput.addEventListener('input', checkDir); checkDir(); } } function initBatchProgress() { var page = document.querySelector('.batch-page'); if (!page) { return; } var statusUrl = page.dataset.statusUrl; var reportsUrl = page.dataset.reportsUrl; var pollMs = 1000; var title = document.getElementById('batchTitle'); var subtitle = document.getElementById('batchSubtitle'); var fill = document.getElementById('progressFill'); var pctLabel = document.getElementById('progressPct'); var currentFile = document.getElementById('currentFile'); var statTotal = document.getElementById('statTotal'); var statProc = document.getElementById('statProcessed'); var statOK = document.getElementById('statSucceeded'); var statFail = document.getElementById('statFailed'); var feedPanel = document.getElementById('feedPanel'); var feedList = document.getElementById('batchFeed'); var donePanel = document.getElementById('donePanel'); var doneSummary = document.getElementById('doneSummary'); var failPanel = document.getElementById('failPanel'); var failList = document.getElementById('failList'); var prevIds = []; if (!statusUrl || !title || !subtitle || !fill || !pctLabel || !currentFile || !statTotal || !statProc || !statOK || !statFail || !feedPanel || !feedList || !donePanel || !doneSummary || !failPanel || !failList) { return; } function poll() { fetch(statusUrl) .then(function (response) { return response.json(); }) .then(function (data) { var pct = data.total > 0 ? Math.round(data.processed / data.total * 100) : 0; statTotal.textContent = data.total; statProc.textContent = data.processed; statOK.textContent = data.succeeded; statFail.textContent = data.failed_count; fill.style.width = pct + '%'; pctLabel.textContent = pct + '%'; if (data.current_file) { currentFile.textContent = 'Processing: ' + data.current_file; } else { currentFile.textContent = ''; } if (data.image_ids && data.image_ids.length) { feedPanel.style.display = 'block'; data.image_ids.forEach(function (imageId) { if (prevIds.indexOf(imageId) === -1) { prevIds.push(imageId); var li = document.createElement('li'); var link = document.createElement('a'); link.href = '/case/' + imageId; link.textContent = imageId; li.appendChild(link); feedList.insertBefore(li, feedList.firstChild); while (feedList.children.length > 20) { feedList.removeChild(feedList.lastChild); } } }); } if (data.status === 'completed' || data.status === 'failed') { title.textContent = 'Batch Complete'; subtitle.textContent = ''; donePanel.style.display = 'block'; doneSummary.textContent = data.succeeded + ' of ' + data.total + ' files processed successfully' + (data.failed_count > 0 ? ', ' + data.failed_count + ' failed' : '') + '.'; if (data.failed_ids && data.failed_ids.length) { failPanel.style.display = 'block'; data.failed_ids.forEach(function (failedId) { var li = document.createElement('li'); li.textContent = failedId; failList.appendChild(li); }); } if (reportsUrl) { return; } return; } setTimeout(poll, pollMs); }) .catch(function () { setTimeout(poll, pollMs * 3); }); } poll(); } function initPages() { initUserMenu(); initPasswordModal(); initUploadPage(); initBatchProgress(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initPages); } else { initPages(); } })();