diff --git a/CHANGELOG.md b/CHANGELOG.md index bfb93bf..bd2f6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changes +## v0.4.2 + +* Make the task-state columns (Queued, Active, Scheduled, Error) sortable. + Clicking a column header cycles its sort through descending, ascending, and + the original order, with a `↓`/`↑` arrow showing the active direction. + Grouped rows keep their children attached and sorted under their parent. + ## v0.4.1 * First release with automated releases. \ No newline at end of file diff --git a/tasktiger_admin/__init__.py b/tasktiger_admin/__init__.py index 0ea0b01..1adf5e7 100644 --- a/tasktiger_admin/__init__.py +++ b/tasktiger_admin/__init__.py @@ -2,7 +2,7 @@ from .views import TaskTigerView -__version__ = "0.4.1" +__version__ = "0.4.2" __all__ = ["TaskTigerView", "tasktiger_admin"] tasktiger_admin = Blueprint( diff --git a/tasktiger_admin/templates/tasktiger_admin/tasktiger.html b/tasktiger_admin/templates/tasktiger_admin/tasktiger.html index 7ca1c6b..6327e9f 100644 --- a/tasktiger_admin/templates/tasktiger_admin/tasktiger.html +++ b/tasktiger_admin/templates/tasktiger_admin/tasktiger.html @@ -11,14 +11,20 @@
| - | Queued | -Active | -Scheduled | -Error | +Queued | +Active | +Scheduled | +Error | {{ queue }} | {% endif %} -{{ stats.queued }} | -{{ stats.active }} | -{{ stats.scheduled }} | +{{ stats.queued }} | +{{ stats.active }} | +{{ stats.scheduled }} | {{ stats.error }} | {% endfor %} @@ -70,6 +76,102 @@). +function isTopLevelRow(row) { + return row.cells[0].tagName === 'TH'; +} + +// Parse the numeric value of a row's cell at the given column index. +function cellValue(row, column) { + return parseInt(row.cells[column].textContent.trim(), 10) || 0; +} + +function applySort() { + const rows = Array.from(searchableBody.rows); + + // Partition rows into blocks. A block begins at a top-level row and + // includes all following child rows up to the next top-level row. + const blocks = []; + let currentBlock = null; + rows.forEach(row => { + if (isTopLevelRow(row)) { + currentBlock = { header: row, children: [] }; + blocks.push(currentBlock); + } else if (currentBlock) { + currentBlock.children.push(row); + } + }); + + const originalIndex = row => + parseInt(row.dataset.originalIndex, 10); + + // Comparator for the current direction, with a stable tie-break on the + // original index so equal values keep a deterministic order. + const compare = (a, b) => { + if (sortDirection === null) { + return originalIndex(a) - originalIndex(b); + } + const delta = cellValue(a, sortColumn) - cellValue(b, sortColumn); + const ordered = sortDirection === 'asc' ? delta : -delta; + return ordered !== 0 ? ordered : originalIndex(a) - originalIndex(b); + }; + + blocks.sort((a, b) => compare(a.header, b.header)); + blocks.forEach(block => block.children.sort(compare)); + + // Re-append rows in the new order. appendChild moves nodes, preserving + // each row's inline display so collapse/filter visibility is kept. + blocks.forEach(block => { + searchableBody.appendChild(block.header); + block.children.forEach(child => searchableBody.appendChild(child)); + }); +} + +function updateArrows() { + document.querySelectorAll('.sort-header').forEach(header => { + const arrow = header.querySelector('.sort-arrow'); + const column = header.cellIndex; + if (column === sortColumn && sortDirection === 'desc') { + arrow.textContent = '↓'; + } else if (column === sortColumn && sortDirection === 'asc') { + arrow.textContent = '↑'; + } else { + arrow.textContent = ''; + } + }); +} + function filterRows(value) { const regex = new RegExp(value, 'i'); const rows = document.querySelectorAll('.searchable tr'); |
|---|