Files
whatsapp/lab_dashboard.php
T
2026-04-08 09:02:32 -05:00

313 lines
14 KiB
PHP

<?php
/**
* Dashboard del Módulo Administrativo de Laboratorio
* Punto de entrada principal del módulo
*/
require_once 'config/config.php';
if (!isUserLoggedIn()) {
header('Location: login.php');
exit;
}
if (isEnfermero()) { header('Location: enfermero_portal.php'); exit; }
$adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Admin';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Módulo Lab — Dashboard</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./assets/css/styles.css?v=12" rel="stylesheet">
<style>
:root {
--lab-primary: #0d6efd;
--lab-success: #198754;
--lab-warning: #fd7e14;
--lab-danger: #dc3545;
--lab-info: #0dcaf0;
}
.lab-card {
border: none;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,.07);
transition: transform .15s, box-shadow .15s;
}
.lab-card:hover { transform: translateY(-2px); box-shadow: 0 4px 20px rgba(0,0,0,.12); }
.stat-icon { width: 48px; height: 48px; border-radius: 12px; display:flex; align-items:center; justify-content:center; font-size: 1.4rem; }
.badge-estado { font-size: .72rem; padding: .3em .6em; border-radius: 6px; }
.actividad-item { padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
.actividad-item:last-child { border-bottom: none; }
.pendiente-urgente { background: #fff8f0; border-left: 4px solid var(--lab-warning); }
</style>
</head>
<body>
<!-- Sidebar heredado del sistema -->
<nav class="sidebar">
<div class="sidebar-header">
<h4><i class="fas fa-flask"></i> Módulo Lab</h4>
</div>
<ul class="sidebar-menu">
<li><a href="lab_dashboard.php" class="nav-link active"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="lab_ordenes.php" class="nav-link"><i class="fas fa-file-medical"></i> Órdenes Médicas</a></li>
<li><a href="lab_pacientes.php" class="nav-link"><i class="fas fa-users"></i> Pacientes</a></li>
<li><a href="lab_domicilios.php"class="nav-link"><i class="fas fa-house-medical"></i> Domicilios</a></li>
<li><a href="lab_enfermeras.php"class="nav-link"><i class="fas fa-user-nurse"></i> Enfermeras</a></li>
<li><a href="lab_formularios.php" class="nav-link"><i class="fas fa-wpforms"></i> Formularios</a></li>
<li><a href="lab_reportes.php" class="nav-link"><i class="fas fa-chart-bar"></i> Reportes</a></li>
<li><a href="lab_actividad.php" class="nav-link"><i class="fas fa-history"></i> Actividad</a></li>
<li><a href="lab_configuracion.php" class="nav-link"><i class="fas fa-sliders-h"></i> Configuración</a></li>
<li><a href="lab_usuarios.php" class="nav-link"><i class="fas fa-users-cog"></i> Usuarios</a></li>
<li class="sidebar-divider"></li>
<li><a href="index.php" class="nav-link"><i class="fas fa-arrow-left"></i> Volver al Bot</a></li>
<li><a href="logout.php" class="nav-link logout-link" onclick="return confirm('¿Cerrar sesión?')"><i class="fas fa-sign-out-alt"></i> Cerrar Sesión</a></li>
</ul>
</nav>
<main class="main-content">
<header class="content-header d-flex align-items-center justify-content-between">
<div>
<h1><i class="fas fa-flask text-primary"></i> Módulo Administrativo — Laboratorio</h1>
<small class="text-muted"><i class="fas fa-user"></i> <?= htmlspecialchars($adminNombre) ?> &mdash; <?= date('l, d \d\e F \d\e Y') ?></small>
</div>
<button class="btn btn-outline-primary btn-sm" onclick="cargarStats()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
</header>
<div class="container-fluid py-3">
<!-- ── Tarjetas resumen ─────────────────────────────────────────── -->
<div class="row g-3 mb-4" id="stats-cards">
<!-- Se renderizan dinámicamente -->
<div class="col-12 text-center py-4 text-muted">
<i class="fas fa-spinner fa-spin me-2"></i> Cargando estadísticas...
</div>
</div>
<div class="row g-3">
<!-- ── Órdenes pendientes urgentes ─────────────────────────── -->
<div class="col-lg-6">
<div class="lab-card card h-100">
<div class="card-header bg-white border-0 pb-0">
<h6 class="mb-0 fw-semibold">
<i class="fas fa-clock text-warning me-2"></i>Órdenes sin revisar (más antiguas)
</h6>
</div>
<div class="card-body p-0" id="pendientes-list">
<div class="text-center py-4 text-muted">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
<!-- ── Domicilios sin asignar hoy ─────────────────────────── -->
<div class="col-lg-6">
<div class="lab-card card h-100">
<div class="card-header bg-white border-0 pb-0">
<h6 class="mb-0 fw-semibold">
<i class="fas fa-house-medical text-danger me-2"></i>Domicilios sin enfermera — hoy
</h6>
</div>
<div class="card-body p-0" id="sinasignar-list">
<div class="text-center py-4 text-muted">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
<!-- ── Actividad reciente ──────────────────────────────────── -->
<div class="col-lg-6">
<div class="lab-card card h-100">
<div class="card-header bg-white border-0 pb-0">
<h6 class="mb-0 fw-semibold">
<i class="fas fa-history text-info me-2"></i>Actividad reciente
</h6>
</div>
<div class="card-body" id="actividad-list">
<div class="text-center py-4 text-muted">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
<!-- ── Carga de enfermeras hoy ─────────────────────────────── -->
<div class="col-lg-6">
<div class="lab-card card h-100">
<div class="card-header bg-white border-0 pb-0">
<h6 class="mb-0 fw-semibold">
<i class="fas fa-user-nurse text-success me-2"></i>Carga de enfermeras — hoy
</h6>
</div>
<div class="card-body" id="carga-list">
<div class="text-center py-4 text-muted">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
</div>
</div><!-- /row -->
</div><!-- /container -->
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const ESTADOS_COLOR = {
pendiente: 'warning',
en_revision: 'info',
autorizada: 'success',
rechazada: 'danger',
en_domicilio: 'primary',
completada: 'secondary',
};
async function cargarStats() {
try {
const r = await fetch('api/lab/get_stats.php');
const d = await r.json();
if (!d.success) return;
renderCards(d);
renderPendientes(d.ordenes.pendientes_viejos);
renderSinAsignar(d.domicilios.lista_sin_asignar);
renderActividad(d.actividad_reciente);
renderCarga(d.enfermeras.carga_hoy);
} catch(e) {
console.error(e);
}
}
function renderCards(d) {
const cards = [
{ icon: 'fa-file-medical', color: 'warning', label: 'Órdenes pendientes', val: d.ordenes.pendientes_hoy, href: 'lab_ordenes.php?estado=pendiente' },
{ icon: 'fa-magnifying-glass',color: 'info', label: 'En revisión', val: d.ordenes.en_revision_hoy, href: 'lab_ordenes.php?estado=en_revision'},
{ icon: 'fa-house-medical', color: 'danger', label: 'Domicilios sin asignar',val: d.domicilios.sin_asignar, href: 'lab_domicilios.php' },
{ icon: 'fa-calendar-check', color: 'success', label: 'Domicilios hoy', val: d.domicilios.total_hoy, href: 'lab_domicilios.php' },
{ icon: 'fa-users', color: 'primary', label: 'Pacientes', val: d.generales.total_pacientes, href: 'lab_pacientes.php' },
{ icon: 'fa-user-nurse', color: 'success', label: 'Enfermeras activas', val: d.enfermeras.activas, href: 'lab_enfermeras.php' },
];
document.getElementById('stats-cards').innerHTML = cards.map(c => `
<div class="col-sm-6 col-xl-4">
<a href="${c.href}" class="text-decoration-none">
<div class="lab-card card">
<div class="card-body d-flex align-items-center gap-3 py-3">
<div class="stat-icon bg-${c.color} bg-opacity-10 text-${c.color}">
<i class="fas ${c.icon}"></i>
</div>
<div>
<div class="fs-4 fw-bold lh-1">${c.val}</div>
<div class="text-muted small">${c.label}</div>
</div>
</div>
</div>
</a>
</div>
`).join('');
}
function renderPendientes(lista) {
const el = document.getElementById('pendientes-list');
if (!lista.length) {
el.innerHTML = '<div class="text-center py-4 text-success"><i class="fas fa-check-circle me-2"></i>Sin órdenes pendientes</div>';
return;
}
el.innerHTML = `<table class="table table-sm table-hover mb-0">
<thead class="table-light"><tr>
<th>Paciente</th><th>Espera</th><th></th>
</tr></thead><tbody>` +
lista.map(o => `<tr class="pendiente-urgente">
<td class="py-2">
<div class="fw-semibold">${esc(o.nombre_completo)}</div>
<small class="text-muted">#${o.id}</small>
</td>
<td class="py-2 text-warning fw-semibold">${o.horas_espera}h</td>
<td class="py-2">
<a href="lab_ordenes.php?id=${o.id}" class="btn btn-xs btn-outline-primary py-0 px-2">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>`).join('') +
'</tbody></table>';
}
function renderSinAsignar(lista) {
const el = document.getElementById('sinasignar-list');
if (!lista.length) {
el.innerHTML = '<div class="text-center py-4 text-success"><i class="fas fa-check-circle me-2"></i>Todos asignados</div>';
return;
}
el.innerHTML = `<table class="table table-sm table-hover mb-0">
<thead class="table-light"><tr><th>Paciente</th><th>Hora</th><th></th></tr></thead><tbody>` +
lista.map(d => `<tr>
<td class="py-2">
<div class="fw-semibold">${esc(d.paciente_nombre)}</div>
<small class="text-muted">${esc(d.barrio || d.direccion)}</small>
</td>
<td class="py-2">${d.hora_programada || '—'}</td>
<td class="py-2">
<a href="lab_domicilios.php?id=${d.id}" class="btn btn-xs btn-outline-danger py-0 px-2">
Asignar
</a>
</td>
</tr>`).join('') +
'</tbody></table>';
}
function renderActividad(lista) {
const el = document.getElementById('actividad-list');
if (!lista.length) {
el.innerHTML = '<p class="text-muted text-center py-3">Sin actividad reciente</p>';
return;
}
el.innerHTML = lista.map(a => `
<div class="actividad-item d-flex gap-2">
<div><i class="fas fa-circle-dot text-primary opacity-50 mt-1"></i></div>
<div class="flex-grow-1">
<span class="fw-semibold">${esc(a.admin_nombre || a.admin_username || 'Sistema')}</span>
&mdash;
<span class="text-muted">${esc(a.modulo)} / ${esc(a.accion)}</span>
<br><small class="text-muted">${formatFecha(a.created_at)}</small>
</div>
</div>
`).join('');
}
function renderCarga(lista) {
const el = document.getElementById('carga-list');
if (!lista.length) {
el.innerHTML = '<p class="text-muted text-center py-3">Sin asignaciones hoy</p>';
return;
}
el.innerHTML = lista.map(e => `
<div class="d-flex align-items-center justify-content-between mb-2">
<div>
<i class="fas fa-user-nurse text-success me-2"></i>
<span class="fw-semibold">${esc(e.nombre_completo)}</span>
</div>
<div class="d-flex align-items-center gap-2">
<span class="badge bg-primary">${e.total_asignaciones} dom.</span>
<span class="badge bg-success">${e.completados || 0} ok</span>
</div>
</div>
`).join('');
}
const esc = s => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
const formatFecha = s => s ? new Date(s).toLocaleString('es-CO', {day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'}) : '';
// Cargar al iniciar y cada 60 s
cargarStats();
setInterval(cargarStats, 60000);
</script>
<script src="assets/js/lab-sidebar.js"></script>
</body>
</html>