diff --git a/core/templates/core/payroll_dashboard.html b/core/templates/core/payroll_dashboard.html index 0aa154d..e26ebf1 100644 --- a/core/templates/core/payroll_dashboard.html +++ b/core/templates/core/payroll_dashboard.html @@ -803,14 +803,34 @@ aria-label="Select all unpaid adjustments on this page" title="Select all unpaid on this page"> - Date - Worker + {# === Sortable column headers — click toggles sort via filter form === #} + {# Each sortable reflects the current sort/order from adj_filter_values. #} + {# The arrow icon is fa-sort (inactive), fa-sort-down (desc) or fa-sort-up (asc). #} + {# JS click handler is wired up in the DOMContentLoaded block further down. #} + + Date + + + + Worker + + Type - Amount + + Amount + + Project Team Description - Status + + Status + + Actions @@ -3752,6 +3772,51 @@ document.addEventListener('DOMContentLoaded', function() { }); } + // === ADJUSTMENTS TAB — Sortable column headers === + // Click a to sort by that column. Same column + // twice flips asc/desc. A different column resets to desc. Submits + // the filter form so the sort persists in the URL and survives + // pagination. Keyboard Enter/Space also works (role="button"). + // Backend whitelists allowed columns in sort_map (see views.py), + // so the data-sort values here must match: date / worker / amount / status. + var adjFilterForm = document.getElementById('adjFilterForm'); + if (adjFilterForm) { + // Scope the query to the adjustments tab. The table sits after the + // filter bar as a sibling, so we walk up to the tab container and + // collect every sortable header inside it. + var adjFilterBar = document.getElementById('adjustmentsFilters'); + var adjTabRoot = adjFilterBar ? adjFilterBar.parentElement : document; + var sortInput = adjFilterForm.querySelector('input[name="sort"]'); + var orderInput = adjFilterForm.querySelector('input[name="order"]'); + + adjTabRoot.querySelectorAll('th.sortable').forEach(function(th) { + // Clicking a sortable mutates the hidden sort/order inputs + // and submits the form so the URL carries the new state. + function triggerSort() { + if (!sortInput || !orderInput) return; + var col = th.getAttribute('data-sort'); + if (sortInput.value === col) { + // Same column clicked again — flip direction. + orderInput.value = orderInput.value === 'asc' ? 'desc' : 'asc'; + } else { + // Different column — switch to it, start at desc (default). + sortInput.value = col; + orderInput.value = 'desc'; + } + adjFilterForm.submit(); + } + th.addEventListener('click', triggerSort); + // Keyboard access: Enter or Space triggers the same sort. + // preventDefault stops Space from scrolling the page. + th.addEventListener('keydown', function(ev) { + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault(); + triggerSort(); + } + }); + }); + } + // --- Direct delete buttons on each unpaid row --- // Short-circuits the edit modal's usual 2-step delete flow by opening // #deleteConfirmModal directly with the correct form action + labels.