up
This commit is contained in:
@@ -33,6 +33,7 @@ if ($fechaDesde > $fechaHasta) $fechaDesde = $fechaHast
|
||||
$estadoFiltro = trim($_GET['estado'] ?? '');
|
||||
$lugarId = (int)($_GET['lugar_id'] ?? 0);
|
||||
$prioridadId = (int)($_GET['prioridad_id'] ?? 0);
|
||||
$pacienteId = (int)($_GET['paciente_id'] ?? 0);
|
||||
$q = trim($_GET['q'] ?? '');
|
||||
|
||||
$perPage = min(200, max(10, (int)($_GET['per_page'] ?? 50)));
|
||||
@@ -57,6 +58,10 @@ if ($prioridadId > 0) {
|
||||
$where[] = 't.prioridad_id = ?';
|
||||
$binds[] = $prioridadId;
|
||||
}
|
||||
if ($pacienteId > 0) {
|
||||
$where[] = 'ts2.paciente_id = ?';
|
||||
$binds[] = $pacienteId;
|
||||
}
|
||||
if ($q !== '') {
|
||||
$where[] = '(t.codigo LIKE ? OR t.paciente_nombre LIKE ? OR s2.nombre_completo LIKE ?)';
|
||||
$like = '%' . $q . '%';
|
||||
|
||||
@@ -94,6 +94,17 @@ Layout::open('Historial de Turnos', 'fas fa-history');
|
||||
.detail-item { background:#fff; border:1px solid #e2e8f0; border-radius:8px; padding:8px 12px; }
|
||||
.detail-item .d-lbl { font-size:.67rem; text-transform:uppercase; letter-spacing:.07em; color:#94a3b8; }
|
||||
.detail-item .d-val { font-size:.85rem; font-weight:600; color:#1e293b; }
|
||||
|
||||
/* ── Documentos firmados ── */
|
||||
.docs-section { margin-top:10px; padding-top:10px; border-top:1px solid #e2e8f0; }
|
||||
.docs-lbl { font-size:.65rem; font-weight:700; text-transform:uppercase; letter-spacing:.07em; color:#94a3b8; margin-bottom:5px; }
|
||||
.doc-pill { display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:99px;
|
||||
font-size:.73rem; font-weight:600; margin:2px 2px; text-decoration:none; transition:opacity .15s; }
|
||||
.doc-pill:hover { opacity:.8; }
|
||||
.doc-pill.firmado { background:#dcfce7; color:#166534; border:1px solid #bbf7d0; }
|
||||
.doc-pill.enviado { background:#fef9c3; color:#78350f; border:1px solid #fde68a; }
|
||||
.doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; }
|
||||
.docs-empty { font-size:.78rem; color:#94a3b8; font-style:italic; }
|
||||
</style>
|
||||
|
||||
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
||||
@@ -414,8 +425,8 @@ function renderTabla(turnos) {
|
||||
<td class="text-nowrap small text-muted">${horaEnt}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2"
|
||||
onclick="toggleDetalle('${rowId}', this)"
|
||||
title="Ver detalles de timestamps">
|
||||
onclick="toggleDetalle('${rowId}', this, ${t.id})"
|
||||
title="Ver detalles">
|
||||
<i class="fas fa-chevron-down" style="font-size:.65rem"></i>
|
||||
</button>
|
||||
</td>
|
||||
@@ -452,10 +463,14 @@ function renderDetalle(t) {
|
||||
<div class="d-lbl">${it.lbl}</div>
|
||||
<div class="d-val">${it.val}</div>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
<div class="docs-section" id="docs-${t.id}">
|
||||
<div class="docs-lbl"><i class="fas fa-file-signature me-1"></i>Documentos</div>
|
||||
<span class="text-muted small"><i class="fas fa-spinner fa-spin me-1"></i>Cargando…</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function toggleDetalle(rowId, btn) {
|
||||
function toggleDetalle(rowId, btn, turnoId) {
|
||||
const row = document.getElementById(rowId);
|
||||
const icon = btn.querySelector('i');
|
||||
if (!row) return;
|
||||
@@ -463,6 +478,48 @@ function toggleDetalle(rowId, btn) {
|
||||
row.style.display = visible ? 'none' : '';
|
||||
icon.className = visible ? 'fas fa-chevron-down' : 'fas fa-chevron-up';
|
||||
icon.style.fontSize = '.65rem';
|
||||
if (!visible && turnoId) {
|
||||
const docsEl = document.getElementById(`docs-${turnoId}`);
|
||||
if (docsEl && !docsEl.dataset.loaded) {
|
||||
docsEl.dataset.loaded = '1';
|
||||
cargarDocumentosTurno(turnoId, docsEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BASE_URL_ROOT = '<?= BASE_URL ?>';
|
||||
|
||||
async function cargarDocumentosTurno(turnoId, container) {
|
||||
try {
|
||||
const res = await fetch(`${API}get_consentimientos.php?turno_id=${turnoId}`);
|
||||
const json = await res.json();
|
||||
if (!json.ok) { container.querySelector('span').textContent = 'Error al cargar'; return; }
|
||||
const list = json.consentimientos || [];
|
||||
if (!list.length) {
|
||||
container.innerHTML = '<div class="docs-lbl"><i class="fas fa-file-signature me-1"></i>Documentos</div>'
|
||||
+ '<span class="docs-empty">Sin documentos para este turno</span>';
|
||||
return;
|
||||
}
|
||||
const LBLS = { firmado:'Firmado', enviado:'Enviado', visto:'Visto', pendiente:'Pendiente' };
|
||||
const pills = list.map(c => {
|
||||
const est = c.estado || 'pendiente';
|
||||
const nombre = esc(c.formulario_nombre || 'Documento');
|
||||
if (est === 'firmado' && c.token) {
|
||||
const url = BASE_URL_ROOT + 'ver_formulario_enviado.php?token=' + encodeURIComponent(c.token);
|
||||
return `<a class="doc-pill firmado" href="${url}" target="_blank">
|
||||
<i class="fas fa-check-circle"></i>${nombre}
|
||||
<i class="fas fa-external-link-alt ms-1" style="font-size:.6rem;opacity:.6"></i>
|
||||
</a>`;
|
||||
}
|
||||
const cls = est === 'enviado' || est === 'visto' ? 'enviado' : 'pendiente';
|
||||
const ico = est === 'enviado' ? 'fa-paper-plane' : (est === 'visto' ? 'fa-eye' : 'fa-clock');
|
||||
return `<span class="doc-pill ${cls}"><i class="fas ${ico}"></i>${nombre}</span>`;
|
||||
}).join('');
|
||||
container.innerHTML = '<div class="docs-lbl"><i class="fas fa-file-signature me-1"></i>Documentos</div>' + pills;
|
||||
} catch(_) {
|
||||
const sp = container.querySelector('span');
|
||||
if (sp) sp.textContent = 'Error de conexión';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Paginación ────────────────────────────────────────────────
|
||||
|
||||
@@ -211,6 +211,42 @@ $adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['
|
||||
.rec-toast.info .ico { color: #3b82f6; }
|
||||
.rec-toast.warn .ico { color: #f59e0b; }
|
||||
.rec-toast.error .ico { color: #ef4444; }
|
||||
|
||||
/* ── Historial del paciente ── */
|
||||
.hist-toggle-btn {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
width: 100%; padding: 6px 10px; margin-top: 8px;
|
||||
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;
|
||||
font-size: .78rem; font-weight: 600; color: #475569;
|
||||
cursor: pointer; text-align: left; transition: background .15s;
|
||||
}
|
||||
.hist-toggle-btn:hover { background: #f1f5f9; }
|
||||
.hist-toggle-btn .hist-chev { margin-left: auto; transition: transform .2s; }
|
||||
.hist-toggle-btn.open .hist-chev { transform: rotate(180deg); }
|
||||
.hist-pac-body {
|
||||
border: 1px solid #e2e8f0; border-top: none;
|
||||
border-radius: 0 0 8px 8px; background: #fff;
|
||||
max-height: 260px; overflow-y: auto;
|
||||
}
|
||||
.pac-timeline { padding: 10px 12px; }
|
||||
.timeline-item {
|
||||
display: flex; gap: 10px; align-items: flex-start;
|
||||
padding-bottom: 10px; position: relative;
|
||||
}
|
||||
.timeline-item:not(:last-child)::before {
|
||||
content: ''; position: absolute; left: 6px; top: 14px;
|
||||
width: 2px; bottom: 0; background: #e2e8f0;
|
||||
}
|
||||
.timeline-dot {
|
||||
width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0;
|
||||
margin-top: 2px; border: 2px solid #fff;
|
||||
box-shadow: 0 0 0 2px currentColor;
|
||||
}
|
||||
.timeline-content { flex: 1; min-width: 0; }
|
||||
.timeline-fecha { font-size: .68rem; color: #94a3b8; }
|
||||
.timeline-codigo { font-size: .82rem; font-weight: 700; }
|
||||
.timeline-lugar { font-size: .73rem; color: #64748b; }
|
||||
.timeline-eb { font-size: .62rem; padding: 1px 7px; border-radius: 99px; font-weight: 700; display: inline-block; margin-top: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -327,6 +363,22 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Historial colapsable del paciente -->
|
||||
<div id="sec-historial-pac" class="d-none">
|
||||
<button class="hist-toggle-btn" id="btn-hist-toggle" onclick="toggleHistorialPaciente()">
|
||||
<i class="fas fa-history" style="color:#6366f1"></i>
|
||||
Historial del paciente
|
||||
<i class="fas fa-chevron-down hist-chev"></i>
|
||||
</button>
|
||||
<div id="hist-pac-body" class="hist-pac-body d-none">
|
||||
<div id="hist-pac-content" class="pac-timeline">
|
||||
<div class="text-center text-muted py-2 small">
|
||||
<i class="fas fa-spinner fa-spin me-1"></i>Cargando…
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -802,12 +854,27 @@ function seleccionarPaciente(pac) {
|
||||
document.getElementById('lbl-pac-cel').textContent =
|
||||
pac.telefono || pac.celular || '';
|
||||
document.getElementById('lista-pacientes-res').innerHTML = '';
|
||||
|
||||
// Mostrar historial del paciente
|
||||
const secHist = document.getElementById('sec-historial-pac');
|
||||
secHist.classList.remove('d-none');
|
||||
// Resetear estado colapsado
|
||||
document.getElementById('hist-pac-body').classList.add('d-none');
|
||||
document.getElementById('btn-hist-toggle').classList.remove('open');
|
||||
// Precargar en background (usuario decide si abre)
|
||||
_historialPacienteCargado = false;
|
||||
_historialPacienteId = pac.id;
|
||||
}
|
||||
|
||||
function desvincularPaciente() {
|
||||
pacienteActivo = null;
|
||||
_historialPacienteId = null;
|
||||
_historialPacienteCargado = false;
|
||||
document.getElementById('bloque-pac-no-vinculado').classList.remove('d-none');
|
||||
document.getElementById('bloque-pac-seleccionado').classList.add('d-none');
|
||||
document.getElementById('sec-historial-pac').classList.add('d-none');
|
||||
document.getElementById('hist-pac-body').classList.add('d-none');
|
||||
document.getElementById('btn-hist-toggle').classList.remove('open');
|
||||
document.getElementById('lista-pacientes-res').innerHTML = '';
|
||||
document.getElementById('inp-buscar-pac').value = '';
|
||||
}
|
||||
@@ -816,6 +883,74 @@ function abrirNuevoPaciente(nombre) {
|
||||
window.open('<?= BASE_URL ?>lab_pacientes.php?nuevo=1&nombre=' + encodeURIComponent(nombre), '_blank');
|
||||
}
|
||||
|
||||
// ── Historial del paciente ──────────────────────────────────────
|
||||
let _historialPacienteId = null;
|
||||
let _historialPacienteCargado = false;
|
||||
|
||||
async function toggleHistorialPaciente() {
|
||||
const body = document.getElementById('hist-pac-body');
|
||||
const btn = document.getElementById('btn-hist-toggle');
|
||||
const open = !body.classList.contains('d-none');
|
||||
body.classList.toggle('d-none', open);
|
||||
btn.classList.toggle('open', !open);
|
||||
if (!open && !_historialPacienteCargado && _historialPacienteId) {
|
||||
_historialPacienteCargado = true;
|
||||
await cargarHistorialPaciente(_historialPacienteId);
|
||||
}
|
||||
}
|
||||
|
||||
async function cargarHistorialPaciente(pacienteId) {
|
||||
const content = document.getElementById('hist-pac-content');
|
||||
content.innerHTML = '<div class="text-center text-muted py-2 small"><i class="fas fa-spinner fa-spin me-1"></i>Cargando historial…</div>';
|
||||
try {
|
||||
const res = await fetch(`${API}get_historial.php?paciente_id=${pacienteId}&per_page=15&page=1`);
|
||||
const json = await res.json();
|
||||
if (!json.ok || !json.turnos || !json.turnos.length) {
|
||||
content.innerHTML = '<div class="text-center text-muted py-3 small"><i class="fas fa-inbox me-1"></i>Sin visitas anteriores registradas</div>';
|
||||
return;
|
||||
}
|
||||
content.innerHTML = renderHistorialTimeline(json.turnos, json.total);
|
||||
} catch(_) {
|
||||
content.innerHTML = '<div class="text-center text-danger py-2 small">Error al cargar historial</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderHistorialTimeline(turnos, total) {
|
||||
const ESTADO_LBL = {
|
||||
espera:'Espera', en_recepcion:'Recepción', en_espera_lugar:'Esp. lugar',
|
||||
en_servicio:'En servicio', finalizado:'Finalizado', ausente:'Ausente', cancelado:'Cancelado',
|
||||
};
|
||||
const ESTADO_STYLE = {
|
||||
finalizado: 'background:#f1f5f9;color:#334155',
|
||||
ausente: 'background:#fee2e2;color:#991b1b',
|
||||
cancelado: 'background:#f1f5f9;color:#9ca3af',
|
||||
en_servicio: 'background:#dcfce7;color:#166534',
|
||||
en_espera_lugar:'background:#fde68a;color:#92400e',
|
||||
en_recepcion: 'background:#dbeafe;color:#1e40af',
|
||||
espera: 'background:#fef9c3;color:#78350f',
|
||||
};
|
||||
const totalLabel = total > turnos.length ? `<div class="text-end px-3 pb-1" style="font-size:.67rem;color:#94a3b8">Mostrando ${turnos.length} de ${total} visitas</div>` : '';
|
||||
const items = turnos.map(t => {
|
||||
const color = t.prioridad_color || '#6366f1';
|
||||
const lugar = escHtml(t.lugar_nombre || 'Recepción');
|
||||
const fecha = t.fecha_sesion || '—';
|
||||
const est = t.estado || '';
|
||||
const eStyle = ESTADO_STYLE[est] || 'background:#f1f5f9;color:#334155';
|
||||
const eLbl = ESTADO_LBL[est] || est;
|
||||
const mins = t.servicio_min != null ? `<span style="font-size:.65rem;color:#94a3b8;margin-left:4px">${t.servicio_min} min</span>` : '';
|
||||
return `<div class="timeline-item">
|
||||
<div class="timeline-dot" style="color:${color};background:${color}"></div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-fecha">${escHtml(fecha)}</div>
|
||||
<div class="timeline-codigo" style="color:${color}">${escHtml(t.codigo)}</div>
|
||||
<div class="timeline-lugar">${lugar}</div>
|
||||
<span class="timeline-eb" style="${eStyle}">${escHtml(eLbl)}</span>${mins}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
return `<div class="pac-timeline">${items}</div>${totalLabel}`;
|
||||
}
|
||||
|
||||
// ── Guardar solicitud ─────────────────────────────────────────
|
||||
async function guardarSolicitud() {
|
||||
if (!turnoActivo) return;
|
||||
|
||||
Reference in New Issue
Block a user