RetinAI / web /users.html
Akira-Kurusu's picture
Update web/users.html
deeb3ee verified
Raw
History Blame
32.1 kB
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Gestión de Usuarios">
<meta name="author" content="Sistema Médico">
<link rel="icon" href="favicon.ico">
<title>Usuarios</title>
<link rel="stylesheet" href="css/simplebar.css">
<link href="https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,100;0,200;0,300;0,400;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script>
(function() {
var dark = localStorage.getItem('theme') === 'dark';
document.write(
'<link rel="stylesheet" href="css/app-' + (dark ? 'dark' : 'light') + '.css" id="' + (dark ? 'dark' : 'light') + 'Theme">' +
'<link rel="stylesheet" href="css/app-' + (dark ? 'light' : 'dark') + '.css" id="' + (dark ? 'light' : 'dark') + 'Theme" disabled>'
);
})();
</script>
<style>
/* ── TOAST NOTIFICATIONS ─────────────────────────── */
#toast-container {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 99999;
display: flex;
flex-direction: column;
gap: 0.6rem;
pointer-events: none;
}
.toast-notify {
display: flex;
align-items: flex-start;
gap: 12px;
min-width: 300px;
max-width: 380px;
padding: 14px 16px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
background: #fff;
border-left: 5px solid #ccc;
pointer-events: all;
opacity: 0;
transform: translateX(60px);
transition: opacity 0.3s ease, transform 0.3s ease;
position: relative;
overflow: hidden;
}
.toast-notify.show { opacity: 1; transform: translateX(0); }
.toast-notify.hide { opacity: 0; transform: translateX(60px); }
.toast-notify.toast-success { border-left-color: #28a745; }
.toast-notify.toast-error { border-left-color: #dc3545; }
.toast-notify.toast-warning { border-left-color: #ffc107; }
.toast-notify.toast-info { border-left-color: #17a2b8; }
.toast-icon { flex-shrink: 0; margin-top: 1px; }
.toast-success .toast-icon svg { stroke: #28a745; }
.toast-error .toast-icon svg { stroke: #dc3545; }
.toast-warning .toast-icon svg { stroke: #ffc107; }
.toast-info .toast-icon svg { stroke: #17a2b8; }
.toast-body { flex: 1; }
.toast-title { font-weight: 600; font-size: 0.875rem; margin-bottom: 2px; }
.toast-success .toast-title { color: #155724; }
.toast-error .toast-title { color: #721c24; }
.toast-warning .toast-title { color: #856404; }
.toast-info .toast-title { color: #0c5460; }
.toast-msg { font-size: 0.825rem; color: #555; line-height: 1.4; }
.toast-close {
background: none; border: none; font-size: 1rem;
line-height: 1; color: #aaa; cursor: pointer; padding: 0; flex-shrink: 0;
}
.toast-close:hover { color: #333; }
.toast-progress {
position: absolute;
bottom: 0; left: 0;
height: 3px;
border-radius: 0 0 0 10px;
}
.toast-success .toast-progress { background: #28a745; }
.toast-error .toast-progress { background: #dc3545; }
.toast-warning .toast-progress { background: #ffc107; }
.toast-info .toast-progress { background: #17a2b8; }
</style>
</head>
<body class="vertical light">
<div class="wrapper">
<!-- Top Navigation -->
<nav class="topnav navbar navbar-light">
<button type="button" class="navbar-toggler text-muted mt-2 p-0 mr-3 collapseSidebar">
<i data-feather="menu" class="navbar-toggler-icon"></i>
</button>
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-muted my-2" href="#" id="modeSwitcher" data-mode="light">
<i data-feather="sun"></i>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-muted pr-0" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="avatar avatar-sm mt-2">
<img src="./assets/images/persona.png" alt="..." class="avatar-img rounded-circle">
</span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="#" onclick="confirmLogout()">Cerrar Sesión</a>
</div>
</li>
</ul>
</nav>
<!-- Sidebar -->
<aside class="sidebar-left border-right bg-white shadow" id="leftSidebar" data-simplebar>
<a href="#" class="btn collapseSidebar toggle-btn d-lg-none text-muted ml-2 mt-3" data-toggle="toggle">
<i data-feather="x"><span class="sr-only"></span></i>
</a>
<nav class="vertnav navbar navbar-light">
<!-- nav bar -->
<div class="w-100 mb-4 d-flex">
<a class="navbar-brand mx-auto mt-2 flex-fill text-center">
<img src="assets/images/LOGO.png" alt="Logo">
</a>
</div>
<ul class="navbar-nav flex-fill w-100 mb-2">
<li class="nav-item">
<a class="nav-link" href="./index.html">
<i data-feather="home" class="fe-16"></i>
<span class="ml-3 item-text">Dashboard</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./diagnosis.html">
<i data-feather="eye" class="fe-16"></i>
<span class="ml-3 item-text">Diagnóstico</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./patients.html">
<i data-feather="users" class="fe-16"></i>
<span class="ml-3 item-text">Pacientes</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./historial-consultas.html">
<i data-feather="file-text" class="fe-16"></i>
<span class="ml-3 item-text">Consultas</span>
</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="./users.html">
<i data-feather="user" class="fe-16"></i>
<span class="ml-3 item-text">Usuarios</span>
</a>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main role="main" class="main-content">
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-12">
<!-- Header -->
<div class="row align-items-center mb-2">
<div class="col">
<h2 class="h5 page-title">Gestión de Usuarios</h2>
</div>
<div class="col-auto">
<button class="btn btn-primary" onclick="loadUsers()">
<i data-feather="refresh-cw" class="fe-16 mr-2"></i>Actualizar
</button>
<button class="btn btn-success ml-2" onclick="showAddUserModal()">
<i data-feather="plus" class="fe-16 mr-2"></i>Nuevo Usuario
</button>
</div>
</div>
<!-- Users Table -->
<div class="card shadow">
<div class="card-header">
<h6 class="card-title mb-0">
<i data-feather="users" class="mr-2"></i>Lista de Usuarios
</h6>
</div>
<div class="card-body">
<div id="loadingSpinner" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Cargando...</span>
</div>
<p class="text-muted mt-2">Cargando usuarios...</p>
</div>
<div id="usersTableContainer" style="display: none;">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-light">
<tr>
<th>ID</th>
<th>Nombre de Usuario</th>
<th>Rol</th>
<th>Fecha de Creación</th>
<th class="text-center">Acciones</th>
</tr>
</thead>
<tbody id="usersTableBody">
<!-- Datos se cargarán aquí dinámicamente -->
</tbody>
</table>
</div>
</div>
<div id="emptyState" class="text-center py-5" style="display: none;">
<i data-feather="users" class="fe-48 text-muted mb-3"></i>
<h6 class="text-muted">No se encontraron usuarios</h6>
<p class="text-muted">No hay usuarios registrados en el sistema.</p>
<button class="btn btn-primary" onclick="showAddUserModal()">
<i data-feather="plus" class="mr-1"></i>Crear Primer Usuario
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Modal para agregar/editar usuario -->
<div class="modal fade" id="userModal" tabindex="-1" role="dialog" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userModalLabel">
<i data-feather="user" class="mr-2"></i>Nuevo Usuario
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="userForm">
<input type="hidden" id="userId">
<div class="form-group">
<label for="username">Nombre de Usuario</label>
<input type="text" class="form-control" id="username" required>
</div>
<div class="form-group">
<label for="password">Contraseña</label>
<input type="password" class="form-control" id="password" required>
</div>
<div class="form-group">
<label for="confirmPassword">Confirmar Contraseña</label>
<input type="password" class="form-control" id="confirmPassword" required>
</div>
<div class="form-group">
<label for="role">Rol</label>
<select class="form-control" id="role" required>
<option value="Doctor">Doctor</option>
<option value="Admin">Administrador</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" id="saveUserBtn" onclick="saveUser()">Guardar</button>
</div>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/simplebar.min.js"></script>
<script src="js/config.js"></script>
<script src="api.js"></script>
<script>
$(document).ready(function() {
checkAuthentication();
loadUsers();
$('#logoutBtn').on('click', function(e) {
e.preventDefault();
if (confirm('¿Estás seguro que deseas cerrar sesión?')) {
api.logout().then(() => { window.location.href = 'auth-login.html'; });
}
});
});
function checkAuthentication() {
api.getSession().then(r => { if (!r.success) window.location.href = 'auth-login.html'; }).catch(() => { window.location.href = 'auth-login.html'; });
}
function loadUsers() {
$('#loadingSpinner').show();
$('#usersTableContainer').hide();
$('#emptyState').hide();
api.getUsers().then(function(response) {
$('#loadingSpinner').hide();
if (response.success) {
if (response.users.length > 0) {
renderUsersTable(response.users);
$('#usersTableContainer').show();
} else {
$('#emptyState').show();
}
} else {
console.error('Error cargando usuarios:', response.message);
showAlert('error', 'Error cargando usuarios: ' + response.message);
$('#emptyState').show();
}
});
}
function renderUsersTable(users) {
const tbody = $('#usersTableBody');
tbody.empty();
users.forEach(user => {
const row = `
<tr>
<td>${user.userID}</td>
<td>${user.username}</td>
<td>
<span class="badge ${user.role === 'Admin' ? 'badge-primary' : 'badge-secondary'}">
${user.role}
</span>
</td>
<td>${user.creationDate}</td>
<td class="text-center">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary"
onclick="editUser(${user.userID})"
title="Editar">
<i data-feather="edit" class="fe-12"></i>
</button>
<button class="btn btn-sm btn-outline-danger"
onclick="confirmDeleteUser(${user.userID})"
title="Eliminar">
<i data-feather="trash-2" class="fe-12"></i>
</button>
</div>
</td>
</tr>
`;
tbody.append(row);
});
}
function showAddUserModal() {
$('#userForm')[0].reset();
$('#userId').val('');
$('#userModalLabel').html('<i data-feather="user-plus" class="mr-2"></i>Nuevo Usuario');
$('#userModal').modal('show');
}
function editUser(userId) {
api.getUsers().then(function(response) {
if (response.success) {
const user = response.users.find(u => u.userID == userId);
if (user) {
$('#userId').val(user.userID);
$('#username').val(user.username);
$('#role').val(user.role);
$('#password').val('');
$('#confirmPassword').val('');
$('#userModalLabel').html('<i data-feather="edit" class="mr-2"></i>Editar Usuario');
$('#userModal').modal('show');
}
}
});
}
function saveUser() {
const userId = $('#userId').val();
const username = $('#username').val().trim();
const password = $('#password').val();
const confirmPassword = $('#confirmPassword').val();
const role = $('#role').val();
if (!username) {
showAlert('error', 'El nombre de usuario es requerido');
return;
}
if (!userId && (!password || !confirmPassword)) {
showAlert('error', 'La contraseña es requerida para nuevos usuarios');
return;
}
if (password !== confirmPassword) {
showAlert('error', 'Las contraseñas no coinciden');
return;
}
if (userId) {
const userData = {
userId: userId,
username: username,
role: role
};
if (password) {
userData.password = password;
}
api.updateUser(userId, userData).then(function(response) {
if (response.success) {
showAlert('success', 'Usuario actualizado exitosamente');
$('#userModal').modal('hide');
loadUsers();
} else {
showAlert('error', response.message || 'Error actualizando usuario');
}
});
} else {
api.createUser(username, password, role).then(function(response) {
if (response.success) {
showAlert('success', 'Usuario creado exitosamente');
$('#userModal').modal('hide');
loadUsers();
} else {
showAlert('error', response.message || 'Error creando usuario');
}
});
}
}
function confirmDeleteUser(userId) {
if (confirm('¿Estás seguro que deseas eliminar este usuario? Esta acción no se puede deshacer.')) {
deleteUser(userId);
}
}
function deleteUser(userId) {
api.deleteUser(userId).then(function(response) {
if (response.success) {
showAlert('success', 'Usuario eliminado exitosamente');
loadUsers();
} else {
showAlert('error', response.message || 'Error eliminando usuario');
}
});
}
function showAlert(type, message) {
if (!document.getElementById('toast-container')) {
const container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
const container = document.getElementById('toast-container');
const icons = {
success: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
error: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
warning: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
info: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
};
const titles = { success: 'Éxito', error: 'Error', warning: 'Advertencia', info: 'Información' };
const duration = type === 'error' ? 6000 : 4000;
const toast = document.createElement('div');
toast.className = `toast-notify toast-${type}`;
toast.innerHTML = `
<div class="toast-icon">${icons[type] || icons.info}</div>
<div class="toast-body">
<div class="toast-title">${titles[type] || 'Aviso'}</div>
<div class="toast-msg">${message}</div>
</div>
<button class="toast-close" onclick="this.closest('.toast-notify').remove()">&times;</button>
<div class="toast-progress" style="width:100%;transition:width ${duration}ms linear;"></div>
`;
container.appendChild(toast);
requestAnimationFrame(() => requestAnimationFrame(() => {
toast.classList.add('show');
const bar = toast.querySelector('.toast-progress');
if (bar) requestAnimationFrame(() => { bar.style.width = '0%'; });
}));
const timer = setTimeout(() => {
toast.classList.add('hide');
toast.addEventListener('transitionend', () => toast.remove(), { once: true });
}, duration);
toast.querySelector('.toast-close').addEventListener('click', () => clearTimeout(timer));
}
</script>
<script>
function confirmLogout() {
executeLogout();
}
function executeLogout() {
api.logout().then(() => { window.location.href = 'auth-login.html'; });
}
</script>
<script>
function checkAdminAccess() {
api.getSession().then(r => { if (!r.success) { window.location.href = 'auth-login.html'; } else if (r.user.role !== 'Admin') { alert('Acceso denegado: Solo administradores'); window.location.href = 'index.html'; } }).catch(() => { window.location.href = 'auth-login.html'; });
}
function renderUsersTableSecure(users) {
const tbody = $('#usersTableBody');
tbody.empty();
api.getSession().then(function(currentUserResponse) { if (!currentUserResponse.success) return; const currentUserId = currentUserResponse.user.userID;
users.forEach(user => {
const canDelete = user.userID !== currentUserId;
const deleteButton = canDelete ?
`<button class="btn btn-sm btn-outline-danger"
onclick="confirmDeleteUser(${user.userID})"
title="Eliminar">
<i data-feather="trash-2" class="fe-12"></i>
</button>` :
`<button class="btn btn-sm btn-outline-secondary"
disabled
title="No puedes eliminar tu propia cuenta">
<i data-feather="user-x" class="fe-12"></i>
</button>`;
const row = `
<tr>
<td>${user.userID}</td>
<td>
${user.username}
${user.userID === currentUserId ? '<span class="badge badge-info ml-2">Tú</span>' : ''}
</td>
<td>
<span class="badge ${user.role === 'Admin' ? 'badge-primary' : 'badge-secondary'}">
${user.role}
</span>
</td>
<td>${user.creationDate}</td>
<td class="text-center">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary"
onclick="editUser(${user.userID})"
title="Editar">
<i data-feather="edit" class="fe-12"></i>
</button>
${deleteButton}
</div>
</td>
</tr>
`;
tbody.append(row);
});
});
}
function confirmDeleteUserSecure(userId) {
api.getSession().then(function(response) { if (!response.success) { showAlert('error', 'Error de autenticación'); return; } if (response.user.userID === userId) {
showAlert('error', 'No puedes eliminar tu propia cuenta');
return;
}
const confirmation = confirm(
'ELIMINAR USUARIO\n\n' +
'¿Estás seguro que deseas eliminar este usuario?\n\n' +
'• Esta acción NO se puede deshacer\n' +
'• Se perderán todos los datos asociados\n' +
'• El usuario no podrá acceder al sistema\n\n' +
'Escribe "CONFIRMAR" para continuar:'
);
if (confirmation) {
const secondConfirmation = prompt(
'Para confirmar la eliminación, escribe: CONFIRMAR'
);
if (secondConfirmation === 'CONFIRMAR') {
deleteUser(userId);
} else {
showAlert('info', 'Eliminación cancelada - Confirmación incorrecta');
}
}
});
}
$(document).ready(function() {
checkAdminAccess();
setTimeout(() => {
checkAuthentication();
loadUsers();
$('#logoutBtn').on('click', function(e) {
e.preventDefault();
if (confirm('¿Estás seguro que deseas cerrar sesión?')) {
api.logout().then(() => { window.location.href = 'auth-login.html'; });
}
});
}, 500); // Pequeño delay para asegurar que la verificación de admin se complete
});
window.renderUsersTable = renderUsersTableSecure;
window.confirmDeleteUser = confirmDeleteUserSecure;
</script>
<script>
(function() {
function centerWindow() {
try {
const screenWidth = window.screen.availWidth;
const screenHeight = window.screen.availHeight;
const x = Math.floor((screenWidth - 1200) / 2);
const y = Math.floor((screenHeight - 800) / 2);
if (window.moveTo) {
window.moveTo(Math.max(0, x), Math.max(0, y));
}
} catch (e) {
console.warn('No se pudo centrar ventana:', e);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', centerWindow);
} else {
centerWindow();
}
window.addEventListener('load', centerWindow);
window.addEventListener('focus', centerWindow);
})();
</script>
<script>
// Feather Icons: reemplaza <i data-feather="X"> con SVG inline
// Inmune a problemas de fuentes locales
function _initFeather() {
if (typeof feather !== 'undefined') feather.replace({ 'stroke-width': 1.8 });
}
document.addEventListener('DOMContentLoaded', _initFeather);
// Re-render al inyectar HTML dinámico (modals, cards, tablas)
var _fObserver = new MutationObserver(function(mutations) {
var needsRefresh = mutations.some(function(m) {
return m.addedNodes.length > 0;
});
if (needsRefresh) _initFeather();
});
document.addEventListener('DOMContentLoaded', function() {
_fObserver.observe(document.body, { childList: true, subtree: true });
});
</script>
<script>
(function () {
const overlay = document.createElement('div');
overlay.id = 'theme-overlay';
overlay.style.cssText = [
'position:fixed','inset:0','z-index:99999',
'pointer-events:none','opacity:0',
'transition:opacity 0.18s ease','background:#fff'
].join(';');
document.body.appendChild(overlay);
function switchTheme() {
const lightTheme = document.getElementById('lightTheme');
const darkTheme = document.getElementById('darkTheme');
const goingDark = !darkTheme.disabled;
overlay.style.background = goingDark ? '#fff' : '#1a1a2e';
overlay.style.opacity = '1';
setTimeout(function () {
lightTheme.disabled = !lightTheme.disabled;
darkTheme.disabled = !darkTheme.disabled;
setTimeout(function () { overlay.style.opacity = '0'; }, 50);
}, 180);
}
document.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('modeSwitcher');
if (!btn) return;
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
switchTheme();
});
});
})();
</script>
<script>
(function () {
// 1. Aplicar tema guardado al cargar
function applyTheme(theme) {
const lightTheme = document.getElementById('lightTheme');
const darkTheme = document.getElementById('darkTheme');
if (theme === 'dark') {
lightTheme.disabled = true;
darkTheme.disabled = false;
} else {
lightTheme.disabled = false;
darkTheme.disabled = true;
}
}
applyTheme(localStorage.getItem('theme') || 'light');
// 2. Escuchar cambios desde OTRAS pestañas
window.addEventListener('storage', function(e) {
if (e.key === 'theme') {
applyTheme(e.newValue);
}
});
// 3. Overlay de transición
const overlay = document.createElement('div');
overlay.id = 'theme-overlay';
overlay.style.cssText = [
'position:fixed','inset:0','z-index:99999',
'pointer-events:none','opacity:0',
'transition:opacity 0.18s ease','background:#fff'
].join(';');
document.body.appendChild(overlay);
function switchTheme() {
const darkTheme = document.getElementById('darkTheme');
const goingDark = darkTheme.disabled; // si está disabled, vamos a dark
overlay.style.background = goingDark ? '#fff' : '#1a1a2e';
overlay.style.opacity = '1';
setTimeout(function () {
const newTheme = goingDark ? 'dark' : 'light';
applyTheme(newTheme);
localStorage.setItem('theme', newTheme); // 👈 esto dispara el evento en otras pestañas
setTimeout(function () { overlay.style.opacity = '0'; }, 50);
}, 180);
}
document.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('modeSwitcher');
if (!btn) return;
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
switchTheme();
});
});
})();
</script>
</body>
</html>