Files
Org-Parking/frontend/js/admin-users.js
2026-01-13 11:20:12 +01:00

239 lines
8.2 KiB
JavaScript

/**
* 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 = '<tr><td colspan="5" class="text-center">Nessun utente trovato</td></tr>';
return;
}
tbody.innerHTML = filtered.map(user => {
const ldapBadge = user.is_ldap_user ? '<span class="badge badge-info">LDAP</span>' : '';
const officeInfo = user.office_name || '-';
return `
<tr>
<td>${user.name || '-'} ${ldapBadge}</td>
<td>${user.email}</td>
<td><span class="badge badge-${getRoleBadgeClass(user.role)}">${user.role}</span></td>
<td>${officeInfo}</td>
<td>${user.parking_ratio !== null ? user.parking_ratio.toFixed(2) : '-'}</td>
<td>
<button class="btn btn-sm btn-secondary" onclick="editUser('${user.id}')">Modifica</button>
<button class="btn btn-sm btn-danger" onclick="deleteUser('${user.id}')" ${user.id === currentUser.id ? 'disabled' : ''}>Elimina</button>
</td>
</tr>
`;
}).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 = '<option value="">Nessun ufficio</option>';
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;