Implement filters and column management in admin UI lists
build / build-linux-amd64 (push) Successful in 1m52s

This commit is contained in:
2026-05-22 18:40:40 +02:00
parent 6a0b698384
commit ac058d31c2
7 changed files with 1947 additions and 211 deletions
+95 -6
View File
@@ -75,11 +75,24 @@
<div id="toast"
class="fixed bottom-4 right-4 max-w-sm space-y-2 pointer-events-none"></div>
<!-- Load fragment + highlight active link based on the URL hash. -->
<!-- Load fragment + highlight active link based on the URL hash.
Sub-routes like #users/new map to dedicated fragment URLs but
keep the parent section's nav-link highlighted. -->
<script>
// Hash → fragment URL for routes that aren't owned by a sidebar
// nav-link (e.g. forms on their own page). The first path segment
// also tells us which nav-link to highlight.
const SUB_ROUTES = {
'#users/new': '/admin/pages/users/new',
};
function topLevelHash(hash) {
const slash = hash.indexOf('/');
return slash === -1 ? hash : hash.slice(0, slash);
}
function linkForHash() {
const hash = location.hash || '#users';
return document.querySelector('.nav-link[hx-push-url="' + hash + '"]')
const top = topLevelHash(hash);
return document.querySelector('.nav-link[hx-push-url="' + top + '"]')
|| document.querySelector('.nav-link[hx-push-url="#users"]');
}
function refreshActive() {
@@ -89,10 +102,16 @@
});
}
function loadFromHash() {
const link = linkForHash();
if (link) {
htmx.ajax('GET', link.getAttribute('hx-get'),
{ target: '#main', swap: 'innerHTML' });
const hash = location.hash || '#users';
const subUrl = SUB_ROUTES[hash];
if (subUrl) {
htmx.ajax('GET', subUrl, { target: '#main', swap: 'innerHTML' });
} else {
const link = linkForHash();
if (link) {
htmx.ajax('GET', link.getAttribute('hx-get'),
{ target: '#main', swap: 'innerHTML' });
}
}
refreshActive();
}
@@ -116,6 +135,76 @@
if (!d.contains(e.target)) d.removeAttribute('open');
});
});
// Read the current value of the users-search input (if present).
// Used by usersColumnToggle/usersPageSize so a column or page-size
// change preserves the active filter.
function usersSearchValue() {
const el = document.getElementById('users-search');
return el ? el.value : '';
}
// Users table column-visibility toggle. The popover in the page header
// emits checkboxes with onchange="usersColumnToggle(this)" — we POST
// the new state to the server (which persists it in user_prefs) and
// swap in the re-rendered table so the popover stays open.
function usersColumnToggle(input) {
const col = input.dataset.col;
if (!col) return;
htmx.ajax('POST', '/admin/pages/users/columns', {
target: '#users-region',
swap: 'innerHTML',
values: {
col: col,
visible: input.checked ? '1' : '0',
q: usersSearchValue(),
},
});
}
window.usersColumnToggle = usersColumnToggle;
// Users table per-page selector. Driven by the <select> in the
// pagination footer — POSTs to persist the choice and re-renders the
// table at page 1 (size change shifts which rows are on which page).
function usersPageSize(size) {
htmx.ajax('POST', '/admin/pages/users/page-size', {
target: '#users-region',
swap: 'innerHTML',
values: { size: size, q: usersSearchValue() },
});
}
window.usersPageSize = usersPageSize;
// Devices table — mirrors the users helpers above. Same persistence
// model (per-user prefs in `user_prefs`) and the same fragment-swap
// approach so the columns popover and search input stay put while
// pagination/columns/page-size all preserve the active filter.
function devicesSearchValue() {
const el = document.getElementById('devices-search');
return el ? el.value : '';
}
function devicesColumnToggle(input) {
const col = input.dataset.col;
if (!col) return;
htmx.ajax('POST', '/admin/pages/devices/columns', {
target: '#devices-region',
swap: 'innerHTML',
values: {
col: col,
visible: input.checked ? '1' : '0',
q: devicesSearchValue(),
},
});
}
window.devicesColumnToggle = devicesColumnToggle;
function devicesPageSize(size) {
htmx.ajax('POST', '/admin/pages/devices/page-size', {
target: '#devices-region',
swap: 'innerHTML',
values: { size: size, q: devicesSearchValue() },
});
}
window.devicesPageSize = devicesPageSize;
</script>
</body>
</html>