feat(users): implement comprehensive user listing and control
This commit is contained in:
101
static/js/account-user-edit.js
Normal file
101
static/js/account-user-edit.js
Normal file
@@ -0,0 +1,101 @@
|
||||
(function () {
|
||||
const form = document.querySelector('[data-ue-form]');
|
||||
const dirtyIndicator = document.querySelector('[data-ue-dirty]');
|
||||
const menuItems = Array.from(document.querySelectorAll('.menu-item'));
|
||||
const toast = document.getElementById('account-toast');
|
||||
let dirty = false;
|
||||
let toastTimer = null;
|
||||
|
||||
function setDirty(next) {
|
||||
dirty = next;
|
||||
if (dirtyIndicator) {
|
||||
dirtyIndicator.textContent = dirty ? 'Unsaved changes' : 'No unsaved changes';
|
||||
}
|
||||
const chip = document.querySelector('[data-dirty-chip]');
|
||||
if (chip) {
|
||||
chip.textContent = dirty ? '● unsaved' : '';
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(msg, type) {
|
||||
if (!toast) return;
|
||||
toast.textContent = msg;
|
||||
toast.className = 'toast is-visible' + (type ? ' toast-' + type : '');
|
||||
window.clearTimeout(toastTimer);
|
||||
toastTimer = window.setTimeout(function () {
|
||||
toast.classList.remove('is-visible');
|
||||
}, 2400);
|
||||
}
|
||||
|
||||
function closeMenus() {
|
||||
menuItems.forEach(function (item) { item.classList.remove('is-open'); });
|
||||
}
|
||||
|
||||
menuItems.forEach(function (item) {
|
||||
const btn = item.querySelector('.menu-button');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
const open = item.classList.contains('is-open');
|
||||
closeMenus();
|
||||
if (!open) item.classList.add('is-open');
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('click', closeMenus);
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeMenus();
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault();
|
||||
if (form) form.requestSubmit ? form.requestSubmit() : form.submit();
|
||||
}
|
||||
});
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('change', function () { setDirty(true); });
|
||||
form.addEventListener('input', function () { setDirty(true); });
|
||||
form.addEventListener('submit', function () { setDirty(false); });
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-ue-command]').forEach(function (el) {
|
||||
el.addEventListener('click', function () {
|
||||
closeMenus();
|
||||
const cmd = el.getAttribute('data-ue-command');
|
||||
switch (cmd) {
|
||||
case 'save':
|
||||
if (form) { form.requestSubmit ? form.requestSubmit() : form.submit(); }
|
||||
break;
|
||||
case 'discard':
|
||||
if (dirty && form) {
|
||||
if (window.confirm('Discard unsaved changes?')) {
|
||||
setDirty(false);
|
||||
form.reset();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'reset-password': {
|
||||
const resetForm = document.querySelector('form[action*="/password/reset"]');
|
||||
if (resetForm && window.confirm('Reset this user\'s password? A temporary password will be generated and shown.')) {
|
||||
resetForm.submit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
showToast('Action: ' + cmd);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// sticky scroll shadow on taskbar
|
||||
const header = document.querySelector('.top-taskbar');
|
||||
if (header) {
|
||||
function updateScroll() {
|
||||
header.classList.toggle('is-scrolled', window.scrollY > 4);
|
||||
}
|
||||
updateScroll();
|
||||
window.addEventListener('scroll', updateScroll, { passive: true });
|
||||
}
|
||||
}());
|
||||
67
static/js/account-users.js
Normal file
67
static/js/account-users.js
Normal file
@@ -0,0 +1,67 @@
|
||||
(function () {
|
||||
const masterCheck = document.getElementById('master-check');
|
||||
const rowChecks = document.querySelectorAll('.row-check');
|
||||
const bulkForm = document.getElementById('users-bulk-form');
|
||||
const selectedIdsInput = document.getElementById('bulk-selected-ids');
|
||||
const selectedCount = document.getElementById('selected-count');
|
||||
const focusCreateBtn = document.querySelector('[data-users-action="focus-create"]');
|
||||
const selectVisibleBtns = document.querySelectorAll('[data-users-action="select-visible"]');
|
||||
|
||||
function updateSelected() {
|
||||
const checked = document.querySelectorAll('.row-check:checked');
|
||||
const ids = Array.from(checked).map(cb => cb.value);
|
||||
selectedIdsInput.value = ids.join(',');
|
||||
if (selectedCount) {
|
||||
selectedCount.textContent = ids.length + ' selected';
|
||||
}
|
||||
if (masterCheck) {
|
||||
const allRowChecks = document.querySelectorAll('.row-check');
|
||||
masterCheck.checked = allRowChecks.length > 0 && checked.length === allRowChecks.length;
|
||||
masterCheck.indeterminate = checked.length > 0 && checked.length < allRowChecks.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (masterCheck) {
|
||||
masterCheck.addEventListener('change', function () {
|
||||
document.querySelectorAll('.row-check').forEach(function (cb) {
|
||||
cb.checked = masterCheck.checked;
|
||||
});
|
||||
updateSelected();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('change', function (event) {
|
||||
if (event.target.classList.contains('row-check')) {
|
||||
updateSelected();
|
||||
}
|
||||
});
|
||||
|
||||
selectVisibleBtns.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('.row-check').forEach(function (cb) {
|
||||
cb.checked = true;
|
||||
});
|
||||
updateSelected();
|
||||
});
|
||||
});
|
||||
|
||||
if (focusCreateBtn) {
|
||||
focusCreateBtn.addEventListener('click', function () {
|
||||
var usernameInput = document.getElementById('users-username');
|
||||
if (usernameInput) {
|
||||
usernameInput.scrollIntoView({ behavior: 'smooth' });
|
||||
setTimeout(function () { usernameInput.focus(); }, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateSelected();
|
||||
})();
|
||||
|
||||
function setBulkAction(actionUrl) {
|
||||
var form = document.getElementById('users-bulk-form');
|
||||
if (form) {
|
||||
form.action = actionUrl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user