/** * Admin Users Page * Manage users with LDAP-aware editing and Office assignment */ let currentUser = null; let users = []; let offices = []; let currentSort = { column: 'name', direction: 'asc' }; document.addEventListener('DOMContentLoaded', async () => { currentUser = await api.requireAuth(); if (!currentUser) return; if (currentUser.role !== 'admin') { window.location.href = '/presence'; return; } await loadOffices(); await loadUsers(); setupEventListeners(); }); async function loadOffices() { const response = await api.get('/api/offices'); if (response && response.ok) { offices = await response.json(); } } async function loadUsers() { const response = await api.get('/api/users'); if (response && response.ok) { users = await response.json(); renderUsers(); } } function renderUsers(filter = '') { const tbody = document.getElementById('usersBody'); const filterLower = filter.toLowerCase(); let filtered = users; if (filter) { filtered = users.filter(u => (u.name || '').toLowerCase().includes(filterLower) || (u.email || '').toLowerCase().includes(filterLower) || (u.role || '').toLowerCase().includes(filterLower) || (u.office_name || '').toLowerCase().includes(filterLower) ); } // Sort filtered.sort((a, b) => { let valA = a[currentSort.column]; let valB = b[currentSort.column]; // Handle nulls for ratio if (currentSort.column === 'parking_ratio') { valA = valA !== null ? valA : 999; // Null ratio (new users) -> low priority? No, new users have ratio 0. // Actually get_user_parking_ratio returns 0.0 for new users. // If office_id is missing, it's None. Treat as high val to push to bottom? valA = (valA === undefined || valA === null) ? 999 : valA; valB = (valB === undefined || valB === null) ? 999 : valB; } else { valA = (valA || '').toString().toLowerCase(); valB = (valB || '').toString().toLowerCase(); } if (valA < valB) return currentSort.direction === 'asc' ? -1 : 1; if (valA > valB) return currentSort.direction === 'asc' ? 1 : -1; return 0; }); // Update icons document.querySelectorAll('th.sortable .sort-icon').forEach(icon => icon.textContent = ''); const activeTh = document.querySelector(`th[data-sort="${currentSort.column}"]`); if (activeTh) { const icon = activeTh.querySelector('.sort-icon'); if (icon) icon.textContent = currentSort.direction === 'asc' ? ' ▲' : ' ▼'; } if (filtered.length === 0) { tbody.innerHTML = 'Nessun utente trovato'; return; } tbody.innerHTML = filtered.map(user => { const ldapBadge = user.is_ldap_user ? 'LDAP' : ''; const officeInfo = user.office_name || '-'; return ` ${user.name || '-'} ${ldapBadge} ${user.email} ${user.role} ${officeInfo} ${user.parking_ratio !== null ? user.parking_ratio.toFixed(2) : '-'} `; }).join(''); } function getRoleBadgeClass(role) { switch (role) { case 'admin': return 'danger'; case 'manager': return 'warning'; default: return 'secondary'; } } async function editUser(userId) { const user = users.find(u => u.id === userId); if (!user) return; // Populate form document.getElementById('userId').value = user.id; document.getElementById('editName').value = user.name || ''; document.getElementById('editEmail').value = user.email; document.getElementById('editRole').value = user.role; // Populate office dropdown const officeSelect = document.getElementById('editOffice'); officeSelect.innerHTML = ''; offices.forEach(o => { const option = document.createElement('option'); option.value = o.id; option.textContent = o.name; if (o.id === user.office_id) option.selected = true; officeSelect.appendChild(option); }); // Handle LDAP restrictions const isLdap = user.is_ldap_user; const isLdapAdmin = user.is_ldap_admin; // LDAP notice document.getElementById('ldapNotice').style.display = isLdap ? 'block' : 'none'; // Name field - disabled for LDAP users const nameInput = document.getElementById('editName'); nameInput.disabled = isLdap; document.getElementById('nameHelp').style.display = isLdap ? 'block' : 'none'; // Role field - admin option disabled for LDAP admins (they can't be demoted) const roleSelect = document.getElementById('editRole'); roleSelect.disabled = isLdapAdmin; document.getElementById('roleHelp').style.display = isLdapAdmin ? 'block' : 'none'; document.getElementById('userModalTitle').textContent = 'Modifica Utente'; document.getElementById('userModal').style.display = 'flex'; } async function deleteUser(userId) { const user = users.find(u => u.id === userId); if (!user) return; if (!confirm(`Eliminare l'utente "${user.name || user.email}"?`)) return; const response = await api.delete(`/api/users/${userId}`); if (response && response.ok) { utils.showMessage('Utente eliminato', 'success'); await loadUsers(); } else { const error = await response.json(); utils.showMessage(error.detail || 'Impossibile eliminare l\'utente', 'error'); } } function setupEventListeners() { // Search document.getElementById('searchInput').addEventListener('input', (e) => { renderUsers(e.target.value); }); // Modal close document.getElementById('closeUserModal').addEventListener('click', () => { document.getElementById('userModal').style.display = 'none'; }); document.getElementById('cancelUser').addEventListener('click', () => { document.getElementById('userModal').style.display = 'none'; }); utils.setupModalClose('userModal'); // Form submit document.getElementById('userForm').addEventListener('submit', async (e) => { e.preventDefault(); const userId = document.getElementById('userId').value; const role = document.getElementById('editRole').value; const data = { role: role, office_id: document.getElementById('editOffice').value || null }; // Only include name if not disabled (LDAP users can't change name) const nameInput = document.getElementById('editName'); if (!nameInput.disabled) { data.name = nameInput.value; } const response = await api.put(`/api/users/${userId}`, data); if (response && response.ok) { document.getElementById('userModal').style.display = 'none'; utils.showMessage('Utente aggiornato', 'success'); await loadUsers(); } else { const error = await response.json(); utils.showMessage(error.detail || 'Impossibile aggiornare l\'utente', 'error'); } }); // Sort headers document.querySelectorAll('th.sortable').forEach(th => { th.addEventListener('click', () => { const column = th.dataset.sort; if (currentSort.column === column) { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.column = column; currentSort.direction = 'asc'; } renderUsers(document.getElementById('searchInput').value); }); }); } // Make functions available globally for onclick handlers window.editUser = editUser; window.deleteUser = deleteUser;