Primo commit
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Admin Users Page
|
||||
* Manage users with LDAP-aware editing
|
||||
* Manage users with LDAP-aware editing and Office assignment
|
||||
*/
|
||||
|
||||
let currentUser = null;
|
||||
let users = [];
|
||||
let managers = [];
|
||||
let offices = [];
|
||||
let currentSort = { column: 'name', direction: 'asc' };
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
currentUser = await api.requireAuth();
|
||||
@@ -16,15 +17,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await loadManagers();
|
||||
await loadOffices();
|
||||
await loadUsers();
|
||||
setupEventListeners();
|
||||
});
|
||||
|
||||
async function loadManagers() {
|
||||
const response = await api.get('/api/managers');
|
||||
async function loadOffices() {
|
||||
const response = await api.get('/api/offices');
|
||||
if (response && response.ok) {
|
||||
managers = await response.json();
|
||||
offices = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,30 +47,60 @@ function renderUsers(filter = '') {
|
||||
(u.name || '').toLowerCase().includes(filterLower) ||
|
||||
(u.email || '').toLowerCase().includes(filterLower) ||
|
||||
(u.role || '').toLowerCase().includes(filterLower) ||
|
||||
(u.manager_name || '').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">No users found</td></tr>';
|
||||
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 managerInfo = user.role === 'manager'
|
||||
? `<span class="badge badge-success">${user.managed_user_count || 0} users</span>`
|
||||
: (user.manager_name || '-');
|
||||
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>${managerInfo}</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}')">Edit</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser('${user.id}')" ${user.id === currentUser.id ? 'disabled' : ''}>Delete</button>
|
||||
<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>
|
||||
`;
|
||||
@@ -93,20 +124,16 @@ async function editUser(userId) {
|
||||
document.getElementById('editName').value = user.name || '';
|
||||
document.getElementById('editEmail').value = user.email;
|
||||
document.getElementById('editRole').value = user.role;
|
||||
document.getElementById('editQuota').value = user.manager_parking_quota || 0;
|
||||
document.getElementById('editPrefix').value = user.manager_spot_prefix || '';
|
||||
|
||||
// Populate manager dropdown
|
||||
const managerSelect = document.getElementById('editManager');
|
||||
managerSelect.innerHTML = '<option value="">No manager</option>';
|
||||
managers.forEach(m => {
|
||||
if (m.id !== userId) { // Can't be own manager
|
||||
const option = document.createElement('option');
|
||||
option.value = m.id;
|
||||
option.textContent = m.name;
|
||||
if (m.id === user.manager_id) option.selected = true;
|
||||
managerSelect.appendChild(option);
|
||||
}
|
||||
// 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
|
||||
@@ -126,13 +153,7 @@ async function editUser(userId) {
|
||||
roleSelect.disabled = isLdapAdmin;
|
||||
document.getElementById('roleHelp').style.display = isLdapAdmin ? 'block' : 'none';
|
||||
|
||||
// Manager group - show for all users (admins can also be assigned to a manager)
|
||||
document.getElementById('managerGroup').style.display = 'block';
|
||||
|
||||
// Manager fields - show only for managers
|
||||
document.getElementById('managerFields').style.display = user.role === 'manager' ? 'block' : 'none';
|
||||
|
||||
document.getElementById('userModalTitle').textContent = 'Edit User';
|
||||
document.getElementById('userModalTitle').textContent = 'Modifica Utente';
|
||||
document.getElementById('userModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
@@ -140,15 +161,15 @@ async function deleteUser(userId) {
|
||||
const user = users.find(u => u.id === userId);
|
||||
if (!user) return;
|
||||
|
||||
if (!confirm(`Delete user "${user.name || user.email}"?`)) 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('User deleted', 'success');
|
||||
utils.showMessage('Utente eliminato', 'success');
|
||||
await loadUsers();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
utils.showMessage(error.detail || 'Failed to delete user', 'error');
|
||||
utils.showMessage(error.detail || 'Impossibile eliminare l\'utente', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,13 +179,6 @@ function setupEventListeners() {
|
||||
renderUsers(e.target.value);
|
||||
});
|
||||
|
||||
// Role change - toggle manager fields (manager group always visible since any user can have a manager)
|
||||
document.getElementById('editRole').addEventListener('change', (e) => {
|
||||
const role = e.target.value;
|
||||
document.getElementById('managerFields').style.display = role === 'manager' ? 'block' : 'none';
|
||||
// Manager group stays visible - any user (including admins) can have a manager assigned
|
||||
});
|
||||
|
||||
// Modal close
|
||||
document.getElementById('closeUserModal').addEventListener('click', () => {
|
||||
document.getElementById('userModal').style.display = 'none';
|
||||
@@ -183,7 +197,7 @@ function setupEventListeners() {
|
||||
|
||||
const data = {
|
||||
role: role,
|
||||
manager_id: document.getElementById('editManager').value || null
|
||||
office_id: document.getElementById('editOffice').value || null
|
||||
};
|
||||
|
||||
// Only include name if not disabled (LDAP users can't change name)
|
||||
@@ -192,23 +206,31 @@ function setupEventListeners() {
|
||||
data.name = nameInput.value;
|
||||
}
|
||||
|
||||
// Manager-specific fields
|
||||
if (role === 'manager') {
|
||||
data.manager_parking_quota = parseInt(document.getElementById('editQuota').value) || 0;
|
||||
data.manager_spot_prefix = document.getElementById('editPrefix').value || null;
|
||||
}
|
||||
|
||||
const response = await api.put(`/api/users/${userId}`, data);
|
||||
if (response && response.ok) {
|
||||
document.getElementById('userModal').style.display = 'none';
|
||||
utils.showMessage('User updated', 'success');
|
||||
await loadManagers(); // Reload in case role changed
|
||||
utils.showMessage('Utente aggiornato', 'success');
|
||||
await loadUsers();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
utils.showMessage(error.detail || 'Failed to update user', 'error');
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user