diff --git a/dashboard/admin.html b/dashboard/admin.html
index 61f98eb..cfb4769 100644
--- a/dashboard/admin.html
+++ b/dashboard/admin.html
@@ -668,6 +668,15 @@
Exportar CSV
+
+
@@ -1207,6 +1216,35 @@
+
+
+
+
+
+
+
+
+
+
+ | Nombre |
+ Email |
+ Negocio |
+ Rol |
+ Activo |
+ Último Login |
+ Acciones |
+
+
+
+ |
+
+
+
+
+
+
diff --git a/dashboard/admin.js b/dashboard/admin.js
index db18cec..62fe78d 100644
--- a/dashboard/admin.js
+++ b/dashboard/admin.js
@@ -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 += ``;
+ html += ``;
// 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 += ``;
+ html += ``;
}
// Next button
- html += ``;
+ html += ``;
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 'Nunca';
+ 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 '' + (role || 'N/A') + '';
+}
+
+function getActiveBadge(isActive) {
+ if (isActive) {
+ return 'Activo';
+ }
+ return 'Inactivo';
+}
+
+async function loadUsers() {
+ var token = localStorage.getItem('access_token');
+ var tbody = document.getElementById('usersTable');
+ tbody.innerHTML = ' |
';
+
+ 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 = '| No hay usuarios registrados |
';
+ return;
+ }
+
+ tbody.innerHTML = users.map(function(u) {
+ var toggleLabel = u.is_active ? 'Desactivar' : 'Activar';
+ var toggleClass = u.is_active ? 'btn-secondary' : 'btn-primary';
+ return '' +
+ '| ' + (u.name || u.nombre || '-') + ' | ' +
+ '' + (u.email || '-') + ' | ' +
+ '' + (u.business_name || u.negocio || '-') + ' | ' +
+ '' + getRoleBadge(u.role || u.rol) + ' | ' +
+ '' + getActiveBadge(u.is_active) + ' | ' +
+ '' + formatDate(u.last_login || u.ultimo_login) + ' | ' +
+ ' | ' +
+ '
';
+ }).join('');
+ } catch (e) {
+ tbody.innerHTML = '| ' + e.message + ' |
';
+ }
+}
+
+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');
+ }
+}