From 9ebaae1b0c5e8b054877045a3f4f5f09122ca63a Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Tue, 24 Mar 2026 22:30:11 +0200 Subject: [PATCH] Fix batch pay radio toggle: use persistent JS reference for radio group The radio group was being removed from DOM then accessed via getElementById which returned null for detached elements, silently breaking the toggle. Now uses a persistent JS variable reference that survives DOM removal. Co-Authored-By: Claude Opus 4.6 --- core/templates/core/payroll_dashboard.html | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index cdb8bd1..939a882 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -2128,21 +2128,23 @@ document.addEventListener('DOMContentLoaded', function() { // 'schedule' = split at last paydate (default), 'all' = pay everything unpaid // --- Helper: Load batch preview for the given mode --- + // Persistent radio group reference (survives DOM removal/re-append) + var _batchRadioGroup = null; + function loadBatchPreview(mode) { var body = document.getElementById('batchPayModalBody'); var footer = document.getElementById('batchPayModalFooter'); footer.style.display = 'none'; - // Keep radio buttons if they exist, clear everything else - var radioGroup = document.getElementById('batchModeRadioGroup'); + // Clear all content from modal body while (body.firstChild) body.removeChild(body.firstChild); - // === Radio button group (always at top) === - if (!radioGroup) { - radioGroup = document.createElement('div'); - radioGroup.id = 'batchModeRadioGroup'; - radioGroup.className = 'btn-group w-100 mb-3'; - radioGroup.setAttribute('role', 'group'); + // === Radio button group (created once, re-appended each time) === + if (!_batchRadioGroup) { + _batchRadioGroup = document.createElement('div'); + _batchRadioGroup.id = 'batchModeRadioGroup'; + _batchRadioGroup.className = 'btn-group w-100 mb-3'; + _batchRadioGroup.setAttribute('role', 'group'); // "Until Last Paydate" radio var radioSchedule = document.createElement('input'); @@ -2152,7 +2154,7 @@ document.addEventListener('DOMContentLoaded', function() { radioSchedule.id = 'batchModeSchedule'; radioSchedule.value = 'schedule'; radioSchedule.checked = (mode === 'schedule'); - radioGroup.appendChild(radioSchedule); + _batchRadioGroup.appendChild(radioSchedule); var labelSchedule = document.createElement('label'); labelSchedule.className = 'btn btn-outline-primary'; @@ -2161,7 +2163,7 @@ document.addEventListener('DOMContentLoaded', function() { iconSchedule.className = 'fas fa-calendar-check me-1'; labelSchedule.appendChild(iconSchedule); labelSchedule.appendChild(document.createTextNode('Until Last Paydate')); - radioGroup.appendChild(labelSchedule); + _batchRadioGroup.appendChild(labelSchedule); // "Pay All" radio var radioAll = document.createElement('input'); @@ -2171,7 +2173,7 @@ document.addEventListener('DOMContentLoaded', function() { radioAll.id = 'batchModeAll'; radioAll.value = 'all'; radioAll.checked = (mode === 'all'); - radioGroup.appendChild(radioAll); + _batchRadioGroup.appendChild(radioAll); var labelAll = document.createElement('label'); labelAll.className = 'btn btn-outline-primary'; @@ -2180,17 +2182,17 @@ document.addEventListener('DOMContentLoaded', function() { iconAll.className = 'fas fa-list me-1'; labelAll.appendChild(iconAll); labelAll.appendChild(document.createTextNode('Pay All')); - radioGroup.appendChild(labelAll); + _batchRadioGroup.appendChild(labelAll); // Re-fetch preview when radio changes radioSchedule.addEventListener('change', function() { loadBatchPreview('schedule'); }); radioAll.addEventListener('change', function() { loadBatchPreview('all'); }); } else { - // Update checked state on existing radios - document.getElementById('batchModeSchedule').checked = (mode === 'schedule'); - document.getElementById('batchModeAll').checked = (mode === 'all'); + // Update checked state using the saved reference (not getElementById — element is detached) + _batchRadioGroup.querySelector('#batchModeSchedule').checked = (mode === 'schedule'); + _batchRadioGroup.querySelector('#batchModeAll').checked = (mode === 'all'); } - body.appendChild(radioGroup); + body.appendChild(_batchRadioGroup); // Show loading spinner below radio buttons var loadDiv = document.createElement('div'); @@ -2344,9 +2346,8 @@ document.addEventListener('DOMContentLoaded', function() { if (batchPayBtn) { batchPayBtn.addEventListener('click', function() { var modal = new bootstrap.Modal(document.getElementById('batchPayModal')); - // Reset radio group so it gets re-created fresh - var oldRadio = document.getElementById('batchModeRadioGroup'); - if (oldRadio) oldRadio.remove(); + // Reset radio group so it gets re-created fresh each time modal opens + _batchRadioGroup = null; modal.show(); // Default mode: split at last paydate loadBatchPreview('schedule');