| <!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> |
|
|
| |
| <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> |
| <link rel="stylesheet" href="css/select2.css"> |
| <link rel="stylesheet" href="css/daterangepicker.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"> |
| |
| <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 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> |
|
|
| |
| <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"> |
| |
| <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 role="main" class="main-content"> |
| <div class="container-fluid"> |
| <div class="row justify-content-center"> |
| <div class="col-12"> |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> </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> |
|
|
| |
| <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"> |
| |
| <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> |
|
|
| |
| <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"> |
| |
| </tbody> |
| </table> |
| </div> |
|
|
| |
| <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"> |
| |
| </ul> |
| </nav> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <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> |
|
|
| |
| <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">×</span> |
| </button> |
| </div> |
| <div class="modal-body" id="consultationModalBody"> |
| |
| </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> |
|
|
| |
| <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() { |
| |
| let currentPage = 1; |
| let currentPerPage = 10; |
| let currentSearch = ''; |
| let currentFilter = 'all'; |
| let currentConsultationId = null; |
| |
| |
| initializePage(); |
| |
| |
| setupEventListeners(); |
| |
| function initializePage() { |
| console.log('Inicializando página de consultas...'); |
| |
| |
| checkAuthentication(); |
| |
| |
| loadSummaryStats(); |
| |
| |
| 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() { |
| |
| let searchTimeout; |
| $('#searchInput').on('input', function() { |
| clearTimeout(searchTimeout); |
| searchTimeout = setTimeout(() => { |
| currentSearch = $(this).val(); |
| currentPage = 1; |
| loadConsultations(); |
| }, 500); |
| }); |
| |
| |
| $('#filterType').on('change', function() { |
| currentFilter = $(this).val(); |
| currentPage = 1; |
| loadConsultations(); |
| }); |
| |
| |
| $('#perPageSelect').on('change', function() { |
| currentPerPage = parseInt($(this).val()); |
| currentPage = 1; |
| loadConsultations(); |
| }); |
| |
| |
| $('#logoutBtn').on('click', function(e) { |
| e.preventDefault(); |
| executeLogout(); |
| }); |
| |
| |
| $('#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}`); |
| |
| |
| $('#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; |
| } |
| |
| |
| 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> |
| `); |
| |
| |
| const startPage = Math.max(1, pagination.current_page - 2); |
| const endPage = Math.min(pagination.total_pages, pagination.current_page + 2); |
| |
| |
| 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>'); |
| } |
| } |
| |
| |
| 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> |
| `); |
| } |
| |
| |
| 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> |
| `); |
| } |
| |
| |
| 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'; |
| |
| |
| 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'; |
| } |
| } |
| |
| |
| 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">×</span> |
| </button> |
| </div> |
| `; |
| |
| $('.container-fluid').prepend(alertHtml); |
| |
| setTimeout(() => { |
| $('.alert').first().alert('close'); |
| }, 5000); |
| } |
| |
| |
| 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() { |
| |
| executeLogout(); |
| } |
| |
| function executeLogout() { |
| api.logout().then(() => { window.location.href = 'auth-login.html'; }); |
| } |
| </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> |
| 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') { |
| |
| if (navItemContainer) { |
| navItemContainer.style.display = 'block'; |
| } |
| console.log('Navegacion de usuarios visible para administrador:', response.user.username); |
| } else { |
| |
| 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'); |
| } |
| }); |
| } |
| |
| |
| function checkUsersPageAccess() { |
| |
| 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'; } |
| }); |
| } |
| } |
| |
| |
| $(document).ready(function() { |
| |
| setTimeout(() => { |
| checkUserRoleAndHideNavigation(); |
| checkUsersPageAccess(); |
| }, 100); |
| }); |
| |
| |
| function updateNavigationForUser() { |
| checkUserRoleAndHideNavigation(); |
| } |
| |
| |
| 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; |
| } |
| } |
| }); |
| } |
| |
| |
| $(document).ready(function() { |
| setTimeout(() => { |
| updateUserInfo(); |
| }, 200); |
| }); |
| </script> |
|
|
|
|
| |
| <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 () { |
| |
| 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'); |
| |
| |
| window.addEventListener('storage', function(e) { |
| if (e.key === 'theme') { |
| applyTheme(e.newValue); |
| } |
| }); |
| |
| |
| 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; |
| overlay.style.background = goingDark ? '#fff' : '#1a1a2e'; |
| overlay.style.opacity = '1'; |
| setTimeout(function () { |
| const newTheme = goingDark ? 'dark' : 'light'; |
| applyTheme(newTheme); |
| localStorage.setItem('theme', newTheme); |
| 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> |