RetinAI / web /historial-consultas.html
ViannyCruz's picture
Update web/historial-consultas.html
8979898 verified
Raw
History Blame Contribute Delete
48 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="Sistema de Gestión de Consultas">
<meta name="author" content="Sistema Médico">
<link rel="icon" href="favicon.ico">
<title>Consultas</title>
<!-- Simple bar CSS -->
<link rel="stylesheet" href="css/simplebar.css">
<!-- Fonts 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">
<!-- Icons CSS -->
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<link rel="stylesheet" href="css/select2.css">
<link rel="stylesheet" href="css/daterangepicker.css">
<!-- App CSS -->
<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>
</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">
<a class="nav-link text-muted my-2" href="#" id="modeSwitcher" data-mode="light">
<i data-feather="sun" class="fe-16"></i>
</a>
</li>
-->
<li class="nav-item">
<a href="#" onclick="confirmLogout()" class="nav-link text-muted" title="Cerrar sesión" style="display: flex; align-items: center; gap: 6px;">
<span class="avatar avatar-sm mt-2">
<img src="./assets/images/persona.png" alt="..." class="avatar-img rounded-circle">
</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" title="Cerrar sesión">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
</a>
</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 -->
<a class="navbar-brand mx-auto mt-2 flex-fill text-center">
<img src="assets/images/RetinAILogo.png" alt="Logo" id="sidebarLogo" style="max-width:140px; transition: opacity 0.3s ease;">
<img src="assets/images/LogoMini.png" alt="Logo Mini" id="sidebarLogoMini" style="max-width:44px; display:none; transition: opacity 0.3s ease;">
</a>
<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 active">
<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">
<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 Consultas</h2>
</div>
<div class="col-auto">
<button class="btn btn-primary" onclick="refreshConsultations()">
<i data-feather="refresh-cw" class="fe-16 mr-2"></i>Actualizar
</button>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card border-0 shadow">
<div class="card-body">
<div class="row align-items-center">
<div class="col-3 text-center">
<span class="circle circle-sm bg-primary">
<i data-feather="clipboard" class="fe-16 text-white"></i>
</span>
</div>
<div class="col pr-0">
<p class="small text-muted mb-0">Total Consultas</p>
<span class="h3 mb-0" id="totalConsultations">-</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow">
<div class="card-body">
<div class="row align-items-center">
<div class="col-3 text-center">
<span class="circle circle-sm bg-warning">
<i data-feather="alert-triangle" class="fe-16 text-white"></i>
</span>
</div>
<div class="col pr-0">
<p class="small text-muted mb-0">Casos Positivos</p>
<span class="h3 mb-0" id="positiveCases">-</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow">
<div class="card-body">
<div class="row align-items-center">
<div class="col-3 text-center">
<span class="circle circle-sm bg-success">
<i data-feather="check" class="fe-16 text-white"></i>
</span>
</div>
<div class="col pr-0">
<p class="small text-muted mb-0">Casos Negativos</p>
<span class="h3 mb-0" id="negativeCases">-</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow">
<div class="card-body">
<div class="row align-items-center">
<div class="col-3 text-center">
<span class="circle circle-sm bg-info">
<i data-feather="users" class="fe-16 text-white"></i>
</span>
</div>
<div class="col pr-0">
<p class="small text-muted mb-0">Pacientes Únicos</p>
<span class="h3 mb-0" id="uniquePatients">-</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filters and Search -->
<div class="card shadow mb-4">
<div class="card-header">
<h6 class="card-title mb-0">
<i data-feather="filter" class="mr-2"></i>Filtros y Búsqueda
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="searchInput">Buscar</label>
<input type="text" class="form-control" id="searchInput" placeholder="Buscar por paciente o notas...">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="filterType">Tipo de Resultado</label>
<select class="form-control" id="filterType">
<option value="all">Todos</option>
<option value="positive">Positivos</option>
<option value="negative">Negativos</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="perPageSelect">Resultados por página</label>
<select class="form-control" id="perPageSelect">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label>&nbsp;</label>
<button type="button" class="btn btn-primary btn-block" onclick="applyFilters()">
<i data-feather="search" class="mr-1"></i>Buscar
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Consultations Table -->
<div class="card shadow">
<div class="card-header">
<h6 class="card-title mb-0">
<i data-feather="clipboard" class="mr-2"></i>Lista de Consultas
<span class="badge badge-secondary ml-2" id="recordCount">0 registros</span>
</h6>
</div>
<div class="card-body">
<!-- Loading Spinner -->
<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 consultas...</p>
</div>
<!-- Consultations Table -->
<div id="consultationsTableContainer" style="display: none;">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-light">
<tr>
<th>Fecha</th>
<th>Paciente</th>
<th>Resultado</th>
<th>Confianza</th>
<th>Notas</th>
<th class="text-center">Acciones</th>
</tr>
</thead>
<tbody id="consultationsTableBody">
<!-- Datos se cargarán aquí dinámicamente -->
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="row align-items-center mt-4">
<div class="col-md-6">
<p class="text-muted" id="paginationInfo">
Mostrando 0 de 0 resultados
</p>
</div>
<div class="col-md-6">
<nav aria-label="Paginación de consultas">
<ul class="pagination justify-content-end mb-0" id="paginationContainer">
<!-- Paginación se generará dinámicamente -->
</ul>
</nav>
</div>
</div>
</div>
<!-- Empty State -->
<div id="emptyState" class="text-center py-5" style="display: none;">
<i data-feather="inbox" class="fe-48 text-muted mb-3"></i>
<h6 class="text-muted">No se encontraron consultas</h6>
<p class="text-muted">No hay consultas que coincidan con los filtros seleccionados.</p>
<button class="btn btn-outline-primary" onclick="clearFilters()">
<i data-feather="refresh-cw" class="mr-1"></i>Limpiar Filtros
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Modal para detalles de consulta -->
<div class="modal fade" id="consultationModal" tabindex="-1" role="dialog" aria-labelledby="consultationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="consultationModalLabel">
<i data-feather="eye" class="mr-2"></i>Detalles de Consulta
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" id="consultationModalBody">
<!-- Contenido se cargara dinamicamente -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cerrar</button>
<button type="button" class="btn btn-danger" id="deleteConsultationBtn" onclick="deleteConsultation()">
<i data-feather="trash-2" class="mr-1"></i>Eliminar
</button>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="js/jquery.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/moment.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/simplebar.min.js"></script>
<script src="js/daterangepicker.js"></script>
<script src="js/select2.min.js"></script>
<script src="js/config.js"></script>
<script src="api.js"></script>
<script>
$(document).ready(function() {
// Variables globales
let currentPage = 1;
let currentPerPage = 10;
let currentSearch = '';
let currentFilter = 'all';
let currentConsultationId = null;
// Inicializar pagina
initializePage();
// Event listeners
setupEventListeners();
function initializePage() {
console.log('Inicializando página de consultas...');
// Verificar autenticación
checkAuthentication();
// Cargar estadísticas
loadSummaryStats();
// Cargar consultas inicial
loadConsultations();
}
function checkAuthentication() {
api.getSession().then(function(response) {
if (!response.success) window.location.href = 'auth-login.html';
}).catch(() => { window.location.href = 'auth-login.html'; });
}
function setupEventListeners() {
// Búsqueda en tiempo real (con debounce)
let searchTimeout;
$('#searchInput').on('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
currentSearch = $(this).val();
currentPage = 1;
loadConsultations();
}, 500);
});
// Filtro por tipo
$('#filterType').on('change', function() {
currentFilter = $(this).val();
currentPage = 1;
loadConsultations();
});
// Resultados por página
$('#perPageSelect').on('change', function() {
currentPerPage = parseInt($(this).val());
currentPage = 1;
loadConsultations();
});
// Logout
$('#logoutBtn').on('click', function(e) {
e.preventDefault();
executeLogout();
});
// Tecla Enter en búsqueda
$('#searchInput').on('keypress', function(e) {
if (e.which === 13) {
applyFilters();
}
});
}
function loadSummaryStats() {
api.getDashboardStats().then(function(response) {
if (response.success) {
const s = response.stats;
$('#totalConsultations').text(s.total_consultations || 0);
$('#positiveCases').text(s.positive_cases || 0);
$('#negativeCases').text(s.negative_cases || 0);
$('#uniquePatients').text(s.total_patients || 0);
}
});
}
function loadConsultations() {
console.log(`Cargando consultas - Página: ${currentPage}, Por página: ${currentPerPage}`);
// Mostrar loading
$('#loadingSpinner').show();
$('#consultationsTableContainer').hide();
$('#emptyState').hide();
api.getConsultations(currentPage, currentPerPage, currentSearch, currentFilter).then(function(response) {
$('#loadingSpinner').hide();
if (response.success) {
if (response.consultations.length > 0) {
renderConsultationsTable(response.consultations);
renderPagination(response.pagination);
updateRecordCount(response.pagination);
$('#consultationsTableContainer').show();
} else {
$('#emptyState').show();
}
} else {
console.error('Error cargando consultas:', response.error);
showAlert('error', 'Error cargando consultas: ' + response.error);
$('#emptyState').show();
}
});
}
function renderConsultationsTable(consultations) {
const tbody = $('#consultationsTableBody');
tbody.empty();
consultations.forEach(consultation => {
const date = consultation.consultationDate ?
new Date(consultation.consultationDate).toLocaleString('es-ES') : 'N/A';
const isPositive = consultation.diabeticRetinopathy;
const resultClass = isPositive ? 'badge-warning' : 'badge-success';
const resultText = isPositive ? 'Positivo' : 'Negativo';
const resultIcon = isPositive ? 'fe-alert-triangle' : 'fe-check';
const confidence = consultation.confidence ?
`${consultation.confidence.toFixed(1)}%` : 'N/A';
const notes = consultation.notes ?
(consultation.notes.length > 50 ?
consultation.notes.substring(0, 50) + '...' :
consultation.notes) :
'<em class="text-muted">Sin notas</em>';
const row = `
<tr>
<td>
<small class="text-muted">${date}</small>
</td>
<td>
<strong>${consultation.patientName || consultation.patient_name || 'Desconocido'}</strong>
</td>
<td>
<span class="badge ${resultClass}">
<i class="fe ${resultIcon} mr-1"></i>${resultText}
</span>
</td>
<td>
<span class="text-${isPositive ? 'warning' : 'success'}">${confidence}</span>
</td>
<td>
<small>${notes}</small>
</td>
<td class="text-center">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-primary"
onclick="viewConsultationDetails(${consultation.consultationID})"
title="Ver detalles">
<i data-feather="eye" class="fe-12"></i>
</button>
<button class="btn btn-sm btn-outline-danger"
onclick="confirmDeleteConsultation(${consultation.consultationID})"
title="Eliminar">
<i data-feather="trash-2" class="fe-12"></i>
</button>
</div>
</td>
</tr>
`;
tbody.append(row);
});
}
function renderPagination(pagination) {
const container = $('#paginationContainer');
container.empty();
if (pagination.total_pages <= 1) {
return;
}
// Boton anterior
const prevDisabled = !pagination.has_previous ? 'disabled' : '';
container.append(`
<li class="page-item ${prevDisabled}">
<a class="page-link" href="#" onclick="changePage(${pagination.current_page - 1})" ${prevDisabled ? 'tabindex="-1"' : ''}>
<i data-feather="chevron-left"></i>
</a>
</li>
`);
// Paginas
const startPage = Math.max(1, pagination.current_page - 2);
const endPage = Math.min(pagination.total_pages, pagination.current_page + 2);
// Primera pagina
if (startPage > 1) {
container.append(`
<li class="page-item">
<a class="page-link" href="#" onclick="changePage(1)">1</a>
</li>
`);
if (startPage > 2) {
container.append('<li class="page-item disabled"><span class="page-link">...</span></li>');
}
}
// Paginas del rango
for (let i = startPage; i <= endPage; i++) {
const activeClass = i === pagination.current_page ? 'active' : '';
container.append(`
<li class="page-item ${activeClass}">
<a class="page-link" href="#" onclick="changePage(${i})">${i}</a>
</li>
`);
}
// Ultima pagina
if (endPage < pagination.total_pages) {
if (endPage < pagination.total_pages - 1) {
container.append('<li class="page-item disabled"><span class="page-link">...</span></li>');
}
container.append(`
<li class="page-item">
<a class="page-link" href="#" onclick="changePage(${pagination.total_pages})">${pagination.total_pages}</a>
</li>
`);
}
// Boton siguiente
const nextDisabled = !pagination.has_next ? 'disabled' : '';
container.append(`
<li class="page-item ${nextDisabled}">
<a class="page-link" href="#" onclick="changePage(${pagination.current_page + 1})" ${nextDisabled ? 'tabindex="-1"' : ''}>
<i data-feather="chevron-right"></i>
</a>
</li>
`);
}
function updateRecordCount(pagination) {
const start = (pagination.current_page - 1) * pagination.per_page + 1;
const end = Math.min(pagination.current_page * pagination.per_page, pagination.total_records);
$('#recordCount').text(`${pagination.total_records} registros`);
$('#paginationInfo').text(`Mostrando ${start} - ${end} de ${pagination.total_records} resultados`);
}
function viewConsultationDetails(consultationId) {
console.log(`Viendo detalles de consulta: ${consultationId}`);
currentConsultationId = consultationId;
api.getConsultations(1, 100).then(function(response) {
const consultation = (response.consultations || []).find(c => c.consultationID === consultationId);
if (consultation) {
renderConsultationModal(consultation, []);
$('#consultationModal').modal('show');
} else {
showAlert('error', 'Consulta no encontrada');
}
});
}
function renderConsultationModal(consultation, riskFactors) {
const isPositive = consultation.diabeticRetinopathy;
const resultClass = isPositive ? 'alert-warning' : 'alert-success';
const resultIcon = isPositive ? 'fe-alert-triangle' : 'fe-check-circle';
const resultText = isPositive ? 'Retinopatía Diabética Detectada' : 'No se Detectó Retinopatía Diabética';
const date = consultation.consultationDate ?
new Date(consultation.consultationDate).toLocaleString('es-ES') : 'N/A';
const confidence = consultation.confidence ?
`${consultation.confidence.toFixed(1)}%` : 'N/A';
// Calcular edad si hay fecha de nacimiento
let age = 'N/A';
if (consultation.birthDate) {
try {
const birthDate = new Date(consultation.birthDate);
const today = new Date();
age = today.getFullYear() - birthDate.getFullYear();
if (today.getMonth() < birthDate.getMonth() ||
(today.getMonth() === birthDate.getMonth() && today.getDate() < birthDate.getDate())) {
age--;
}
age += ' años';
} catch (e) {
age = 'N/A';
}
}
// Factores de riesgo
let riskFactorsHtml = '';
if (riskFactors && riskFactors.length > 0) {
riskFactorsHtml = riskFactors.map(rf =>
`<span class="badge badge-secondary mr-1 mb-1">${rf.name}</span>`
).join('');
} else {
riskFactorsHtml = '<span class="text-muted">No se han registrado factores de riesgo</span>';
}
const modalContent = `
<div class="row">
<div class="col-md-6">
<h6 class="mb-3">
<i data-feather="user" class="mr-2"></i>Información del Paciente
</h6>
<table class="table table-sm">
<tr>
<td><strong>Nombre:</strong></td>
<td>${consultation.patientName || consultation.patient_name || 'N/A'}</td>
</tr>
<tr>
<td><strong>Edad:</strong></td>
<td>${age}</td>
</tr>
<tr>
<td><strong>Género:</strong></td>
<td>${consultation.gender || 'N/A'}</td>
</tr>
<tr>
<td><strong>Tipo Diabetes:</strong></td>
<td>${consultation.diabetesType || 'N/A'}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<h6 class="mb-3">
<i data-feather="clipboard" class="mr-2"></i>Información de la Consulta
</h6>
<table class="table table-sm">
<tr>
<td><strong>Fecha:</strong></td>
<td>${date}</td>
</tr>
<tr>
<td><strong>ID Consulta:</strong></td>
<td>#${consultation.consultationID}</td>
</tr>
<tr>
<td><strong>Confianza:</strong></td>
<td>${confidence}</td>
</tr>
<tr>
<td><strong>Imagen:</strong></td>
<td>${consultation.imagePath || 'N/A'}</td>
</tr>
</table>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="alert ${resultClass} d-flex align-items-center">
<i class="fe ${resultIcon} fe-24 mr-3"></i>
<div>
<h6 class="mb-1">${resultText}</h6>
<p class="mb-0">Confianza del diagnóstico: <strong>${confidence}</strong></p>
</div>
</div>
</div>
</div>
${consultation.notes ? `
<div class="row">
<div class="col-12">
<h6 class="mb-2">
<i data-feather="edit-2" class="mr-2"></i>Notas de la Consulta
</h6>
<div class="card bg-light">
<div class="card-body">
<p class="mb-0">${consultation.notes}</p>
</div>
</div>
</div>
</div>
` : ''}
`;
$('#consultationModalBody').html(modalContent);
}
function confirmDeleteConsultation(consultationId) {
if (confirm('¿Estás seguro que deseas eliminar esta consulta? Esta acción no se puede deshacer.')) {
currentConsultationId = consultationId;
deleteConsultation();
}
}
function deleteConsultation() {
if (!currentConsultationId) {
showAlert('error', 'No se ha seleccionado ninguna consulta');
return;
}
fetch('/api/consultations/' + currentConsultationId, {method:'DELETE',credentials:'same-origin'})
.then(r => r.json()).then(function(response) {
if (response.success) {
showAlert('success', 'Consulta eliminada exitosamente');
$('#consultationModal').modal('hide');
loadConsultations();
loadSummaryStats();
} else {
showAlert('error', 'Error eliminando consulta: ' + (response.error||response.message));
}
});
}
function changePage(page) {
if (page >= 1) {
currentPage = page;
loadConsultations();
}
}
function applyFilters() {
currentSearch = $('#searchInput').val();
currentFilter = $('#filterType').val();
currentPerPage = parseInt($('#perPageSelect').val());
currentPage = 1;
loadConsultations();
}
function clearFilters() {
$('#searchInput').val('');
$('#filterType').val('all');
$('#perPageSelect').val('10');
currentSearch = '';
currentFilter = 'all';
currentPerPage = 10;
currentPage = 1;
loadConsultations();
}
function refreshConsultations() {
loadConsultations();
loadSummaryStats();
showAlert('success', 'Datos actualizados correctamente');
}
function showAlert(type, message) {
const alertClass = {
'success': 'alert-success',
'error': 'alert-danger',
'warning': 'alert-warning',
'info': 'alert-info'
}[type] || 'alert-info';
const alertHtml = `
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="close" data-dismiss="alert">
<span aria-hidden="true">&times;</span>
</button>
</div>
`;
$('.container-fluid').prepend(alertHtml);
setTimeout(() => {
$('.alert').first().alert('close');
}, 5000);
}
// Funciones globales para uso en HTML
window.viewConsultationDetails = viewConsultationDetails;
window.confirmDeleteConsultation = confirmDeleteConsultation;
window.deleteConsultation = deleteConsultation;
window.changePage = changePage;
window.applyFilters = applyFilters;
window.clearFilters = clearFilters;
window.refreshConsultations = refreshConsultations;
});
</script>
<script>
function confirmLogout() {
// Sin confirm() para evitar el mensaje del navegador
executeLogout();
}
function executeLogout() {
api.logout().then(() => { window.location.href = 'auth-login.html'; });
}
</script>
<script>
// Centrar ventana al cargar página
(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);
}
}
// Centrar al cargar
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', centerWindow);
} else {
centerWindow();
}
window.addEventListener('load', centerWindow);
window.addEventListener('focus', centerWindow);
})();
</script>
<script>
function checkUserRoleAndHideNavigation() {
api.getSession().then(function(response) {
if (response.success && response.user) {
const userRole = response.user.role;
const usersNavItem = document.querySelector('a[href="./users.html"]');
if (usersNavItem) {
const navItemContainer = usersNavItem.closest('li.nav-item');
if (userRole === 'Admin') {
// Usuario es Admin - mostrar elemento
if (navItemContainer) {
navItemContainer.style.display = 'block';
}
console.log('Navegacion de usuarios visible para administrador:', response.user.username);
} else {
// Usuario NO es Admin - ocultar elemento
if (navItemContainer) {
navItemContainer.style.display = 'none';
}
console.log('Navegacion de usuarios oculta para usuario:', response.user.username, '(Rol:', userRole + ')');
}
} else {
console.warn('No se encontró el elemento de navegación de usuarios');
}
} else {
console.warn('No se pudo obtener información del usuario actual');
}
});
}
// Funcion para verificar si el usuario actual está intentando acceder a usuarios.html
function checkUsersPageAccess() {
// Solo ejecutar en la pagina de usuarios
if (window.location.pathname.includes('users.html') || window.location.href.includes('users.html')) {
api.getSession().then(function(response) {
if (!response.success) { window.location.href = 'auth-login.html'; return; }
if (response.user.role !== 'Admin') { window.location.href = 'index.html'; }
});
}
}
// Ejecutar al cargar la página
$(document).ready(function() {
// Pequeno delay para asegurar que Eel esté listo
setTimeout(() => {
checkUserRoleAndHideNavigation();
checkUsersPageAccess();
}, 100);
});
// Tambien ejecutar cuando cambie el estado de autenticacion
function updateNavigationForUser() {
checkUserRoleAndHideNavigation();
}
// Funcion para mostrar informacion del usuario en el dropdown (opcional)
function updateUserInfo() {
api.getSession().then(function(response) {
if (response.success && response.user) {
const userDropdown = document.querySelector('#navbarDropdownMenuLink');
if (userDropdown) {
const roleBadge = response.user.role === 'Admin' ?
'<span class="badge badge-primary badge-sm ml-1">Admin</span>' :
'<span class="badge badge-secondary badge-sm ml-1">Doctor</span>';
if (!userDropdown.querySelector('.badge')) userDropdown.innerHTML += roleBadge;
}
}
});
}
// Llamar tambien a updateUserInfo al cargar
$(document).ready(function() {
setTimeout(() => {
updateUserInfo();
}, 200);
});
</script>
<!-- App scripts -->
<script src="js/apps.js"></script>
<script>
function _initFeather() {
if (typeof feather !== 'undefined') feather.replace({ 'stroke-width': 1.8 });
}
document.addEventListener('DOMContentLoaded', _initFeather);
var _fObserver = new MutationObserver(function(mutations) {
if (mutations.some(function(m){ return m.addedNodes.length > 0; })) _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 () {
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);
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();
});
});
})();
(function() {
var sidebar = document.getElementById('leftSidebar');
var logo = document.getElementById('sidebarLogo');
var logoMini = document.getElementById('sidebarLogoMini');
if (!sidebar || !logo || !logoMini) return;
function updateLogo() {
var isCollapsed = sidebar.classList.contains('collapsed')
|| document.body.classList.contains('collapsed')
|| sidebar.offsetWidth < 100;
logo.style.display = isCollapsed ? 'none' : '';
logoMini.style.display = isCollapsed ? 'block' : 'none';
}
var observer = new MutationObserver(updateLogo);
observer.observe(sidebar, { attributes: true, attributeFilter: ['class', 'style'] });
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
updateLogo();
})();
</script>
</body>
</html>