Historial turnero: desglose de tomas de muestra y modal para documentos firmados
Muestra el detalle de cada toma (tipo, quien la recibió, tiempo) cuando un turno tuvo varias, para explicar tiempos de servicio prolongados. Los documentos firmados ahora se abren en un modal en vez de nueva pestaña, preservando los filtros de búsqueda activos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
397dc208a0
commit
0b8418ec76
@@ -139,11 +139,12 @@ $stmtTurnos = $pdo->prepare($sqlTurnos);
|
||||
$stmtTurnos->execute($bindsPage);
|
||||
$turnos = $stmtTurnos->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ── Enriquecer con exámenes, consentimientos y comentarios (batch) ─
|
||||
// ── Enriquecer con exámenes, consentimientos, comentarios y tomas (batch) ─
|
||||
$turnoIds = array_column($turnos, 'id');
|
||||
$examenes = [];
|
||||
$consentimientos = [];
|
||||
$comentarios = [];
|
||||
$tomas = [];
|
||||
|
||||
if ($turnoIds) {
|
||||
$ph = implode(',', array_fill(0, count($turnoIds), '?'));
|
||||
@@ -193,6 +194,26 @@ if ($turnoIds) {
|
||||
$comentarios[$tid][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// Tomas de muestra — batch (una solicitud por turno, varias tomas por solicitud)
|
||||
$stmtTom = $pdo->prepare(
|
||||
"SELECT ts.turno_id, tm.tipo_muestra, tm.estado, tm.creado_at, tm.recibida_at,
|
||||
ar.full_name AS recibida_por_nombre
|
||||
FROM turnero_muestras tm
|
||||
JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id
|
||||
LEFT JOIN admin_users ar ON ar.id = tm.recibida_por
|
||||
WHERE ts.turno_id IN ($ph)
|
||||
ORDER BY tm.creado_at ASC"
|
||||
);
|
||||
$stmtTom->execute($turnoIds);
|
||||
foreach ($stmtTom->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$tid = (int)$row['turno_id'];
|
||||
unset($row['turno_id']);
|
||||
$row['tiempo_min'] = ($row['creado_at'] && $row['recibida_at'])
|
||||
? round((strtotime($row['recibida_at']) - strtotime($row['creado_at'])) / 60, 1)
|
||||
: null;
|
||||
$tomas[$tid][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($turnos as &$turno) {
|
||||
@@ -200,6 +221,7 @@ foreach ($turnos as &$turno) {
|
||||
$turno['examenes'] = $examenes[$tid] ?? [];
|
||||
$turno['consentimientos'] = $consentimientos[$tid] ?? [];
|
||||
$turno['comentarios'] = $comentarios[$tid] ?? [];
|
||||
$turno['tomas'] = $tomas[$tid] ?? [];
|
||||
}
|
||||
|
||||
// ── Resumen por estado (del rango filtrado) ───────────────────
|
||||
|
||||
@@ -126,6 +126,18 @@ Layout::open('Historial de Turnos', 'fas fa-history');
|
||||
.estado-modal select { width:100%; padding:8px 10px; border:1px solid #e2e8f0;
|
||||
border-radius:8px; font-size:.85rem; margin-bottom:16px; }
|
||||
.estado-modal .actions { display:flex; gap:8px; justify-content:flex-end; }
|
||||
|
||||
/* ── Modal documento firmado ── */
|
||||
.doc-modal-backdrop {
|
||||
position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:1060;
|
||||
display:flex; align-items:center; justify-content:center; padding:20px;
|
||||
}
|
||||
.doc-modal { background:#fff; border-radius:12px; width:100%; max-width:820px; height:88vh;
|
||||
display:flex; flex-direction:column; overflow:hidden; box-shadow:0 20px 60px rgba(0,0,0,.25); }
|
||||
.doc-modal-head { display:flex; align-items:center; gap:10px; padding:12px 16px; border-bottom:1px solid #e2e8f0; }
|
||||
.doc-modal-head h5 { font-size:.9rem; font-weight:700; margin:0; flex:1; }
|
||||
.doc-modal-head button { border:none; background:none; font-size:1.1rem; color:#64748b; cursor:pointer; }
|
||||
.doc-modal iframe { flex:1; border:none; width:100%; }
|
||||
</style>
|
||||
|
||||
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
||||
@@ -521,6 +533,25 @@ function renderDetalle(t) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Tomas de muestra (solo si hay más de una — turno prolongado por varias tomas)
|
||||
let tomasHtml = '';
|
||||
if (t.tomas && t.tomas.length > 1) {
|
||||
tomasHtml = `<div class="detail-extra">
|
||||
<div class="d-lbl"><i class="fas fa-tint me-1 text-danger"></i>Tomas de muestra (${t.tomas.length})</div>
|
||||
<table class="table table-sm mb-0" style="font-size:.78rem">
|
||||
<thead><tr><th>Muestra</th><th>Recibió</th><th>Recibida</th><th>Tiempo</th></tr></thead>
|
||||
<tbody>
|
||||
${t.tomas.map(m => `<tr>
|
||||
<td>${esc(m.tipo_muestra)}</td>
|
||||
<td>${esc(m.recibida_por_nombre || '—')}</td>
|
||||
<td>${m.recibida_at ? new Date(m.recibida_at.replace(' ','T')).toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'}) : '—'}</td>
|
||||
<td>${m.tiempo_min != null ? m.tiempo_min + ' m' : '—'}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Comentarios
|
||||
let comHtml = '';
|
||||
if (t.comentarios && t.comentarios.length) {
|
||||
@@ -543,7 +574,7 @@ function renderDetalle(t) {
|
||||
<div class="d-val">${it.val}</div>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
${examHtml}${comHtml}
|
||||
${examHtml}${tomasHtml}${comHtml}
|
||||
<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>
|
||||
@@ -586,9 +617,8 @@ async function cargarDocumentosTurno(turnoId, container) {
|
||||
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">
|
||||
return `<a class="doc-pill firmado" href="${url}" onclick="return abrirDocModal(event,'${url}','${nombre.replace(/'/g,"\\'")}')">
|
||||
<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';
|
||||
@@ -746,6 +776,29 @@ async function confirmarCambioEstado() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Modal documento firmado ────────────────────────────────────
|
||||
function abrirDocModal(ev, url, nombre) {
|
||||
ev.preventDefault();
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = 'doc-modal-backdrop';
|
||||
backdrop.id = 'docModalBackdrop';
|
||||
backdrop.innerHTML = `
|
||||
<div class="doc-modal" onclick="event.stopPropagation()">
|
||||
<div class="doc-modal-head">
|
||||
<h5><i class="fas fa-file-signature me-2 text-success"></i>${esc(nombre)}</h5>
|
||||
<a href="${url}" target="_blank" title="Abrir en pestaña nueva" class="me-2"><i class="fas fa-external-link-alt"></i></a>
|
||||
<button onclick="cerrarDocModal()" title="Cerrar"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<iframe src="${url}"></iframe>
|
||||
</div>`;
|
||||
backdrop.addEventListener('click', cerrarDocModal);
|
||||
document.body.appendChild(backdrop);
|
||||
return false;
|
||||
}
|
||||
function cerrarDocModal() {
|
||||
document.getElementById('docModalBackdrop')?.remove();
|
||||
}
|
||||
|
||||
// ── Helpers visuales ──────────────────────────────────────────
|
||||
function mostrarSpinner() {
|
||||
document.getElementById('tbody').innerHTML =
|
||||
|
||||
Reference in New Issue
Block a user