feat: add users management tab to admin panel
New Sistema > Usuarios section with user listing, role badges (ADMIN=blue, OWNER=purple, TALLER=green, BODEGA=orange), activate/deactivate toggle, and pending users badge count. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -118,6 +118,9 @@ function showSection(sectionId) {
|
||||
case 'diagrams':
|
||||
// Just show section, user uses search
|
||||
break;
|
||||
case 'users':
|
||||
loadUsers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,18 +1229,18 @@ function renderPagination(containerId, pagination, pageKey, loadFunction) {
|
||||
let html = '';
|
||||
|
||||
// Previous button
|
||||
html += `<button ${page <= 1 ? 'disabled' : ''} onclick="goToPage('${pageKey}', ${page - 1}, ${loadFunction.name})">← Anterior</button>`;
|
||||
html += `<button ${page <= 1 ? 'disabled' : ''} onclick="goToPage('${pageKey}', ${page - 1}, '${loadFunction.name}')">← Anterior</button>`;
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, page - 2);
|
||||
const endPage = Math.min(total_pages, page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
html += `<button class="${i === page ? 'active' : ''}" onclick="goToPage('${pageKey}', ${i}, ${loadFunction.name})">${i}</button>`;
|
||||
html += `<button class="${i === page ? 'active' : ''}" onclick="goToPage('${pageKey}', ${i}, '${loadFunction.name}')">${i}</button>`;
|
||||
}
|
||||
|
||||
// Next button
|
||||
html += `<button ${page >= total_pages ? 'disabled' : ''} onclick="goToPage('${pageKey}', ${page + 1}, ${loadFunction.name})">Siguiente →</button>`;
|
||||
html += `<button ${page >= total_pages ? 'disabled' : ''} onclick="goToPage('${pageKey}', ${page + 1}, '${loadFunction.name}')">Siguiente →</button>`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
@@ -1967,3 +1970,107 @@ async function deleteHotspot(hotspotId) {
|
||||
showAlert(e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// User Management
|
||||
// ============================================================================
|
||||
|
||||
const roleBadgeColors = {
|
||||
ADMIN: '#3b82f6',
|
||||
OWNER: '#8b5cf6',
|
||||
TALLER: '#22c55e',
|
||||
BODEGA: '#f59e0b'
|
||||
};
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return '<span style="color:var(--text-secondary)">Nunca</span>';
|
||||
var d = new Date(dateStr);
|
||||
if (isNaN(d.getTime())) return dateStr;
|
||||
return d.toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric' })
|
||||
+ ' ' + d.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function getRoleBadge(role) {
|
||||
var color = roleBadgeColors[role] || '#6b7280';
|
||||
return '<span style="background:' + color + '; color:#fff; padding:2px 8px; border-radius:10px; font-size:0.75rem; font-weight:600;">' + (role || 'N/A') + '</span>';
|
||||
}
|
||||
|
||||
function getActiveBadge(isActive) {
|
||||
if (isActive) {
|
||||
return '<span style="background:var(--success); color:#000; padding:2px 8px; border-radius:10px; font-size:0.75rem; font-weight:600;">Activo</span>';
|
||||
}
|
||||
return '<span style="background:#ef4444; color:#fff; padding:2px 8px; border-radius:10px; font-size:0.75rem; font-weight:600;">Inactivo</span>';
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
var token = localStorage.getItem('access_token');
|
||||
var tbody = document.getElementById('usersTable');
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="loading"><div class="spinner"></div></td></tr>';
|
||||
|
||||
try {
|
||||
var res = await fetch('/api/admin/users', {
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if (!res.ok) throw new Error('Error al cargar usuarios (' + res.status + ')');
|
||||
var data = await res.json();
|
||||
var users = Array.isArray(data) ? data : (data.data || []);
|
||||
|
||||
// Update pending badge
|
||||
var pending = users.filter(function(u) { return !u.is_active; }).length;
|
||||
var badge = document.getElementById('pendingUsersBadge');
|
||||
if (badge) {
|
||||
if (pending > 0) {
|
||||
badge.textContent = pending;
|
||||
badge.style.display = 'inline-block';
|
||||
} else {
|
||||
badge.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (users.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; color:var(--text-secondary); padding:2rem;">No hay usuarios registrados</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = users.map(function(u) {
|
||||
var toggleLabel = u.is_active ? 'Desactivar' : 'Activar';
|
||||
var toggleClass = u.is_active ? 'btn-secondary' : 'btn-primary';
|
||||
return '<tr>' +
|
||||
'<td>' + (u.name || u.nombre || '-') + '</td>' +
|
||||
'<td>' + (u.email || '-') + '</td>' +
|
||||
'<td>' + (u.business_name || u.negocio || '-') + '</td>' +
|
||||
'<td>' + getRoleBadge(u.role || u.rol) + '</td>' +
|
||||
'<td>' + getActiveBadge(u.is_active) + '</td>' +
|
||||
'<td>' + formatDate(u.last_login || u.ultimo_login) + '</td>' +
|
||||
'<td><button class="btn ' + toggleClass + '" style="font-size:0.8rem; padding:4px 10px;" onclick="toggleUserActive(' + u.id + ', ' + u.is_active + ')">' + toggleLabel + '</button></td>' +
|
||||
'</tr>';
|
||||
}).join('');
|
||||
} catch (e) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; color:#ef4444; padding:2rem;">' + e.message + '</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleUserActive(userId, currentActive) {
|
||||
var token = localStorage.getItem('access_token');
|
||||
var action = currentActive ? 'desactivar' : 'activar';
|
||||
if (!confirm('¿Seguro que deseas ' + action + ' este usuario?')) return;
|
||||
|
||||
try {
|
||||
var res = await fetch('/api/admin/users/' + userId + '/activate', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
body: JSON.stringify({ is_active: !currentActive })
|
||||
});
|
||||
if (!res.ok) {
|
||||
var err = await res.json();
|
||||
throw new Error(err.error || 'Error al actualizar usuario');
|
||||
}
|
||||
showAlert('Usuario ' + (currentActive ? 'desactivado' : 'activado') + ' correctamente');
|
||||
loadUsers();
|
||||
} catch (e) {
|
||||
showAlert(e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user