up
This commit is contained in:
@@ -231,12 +231,126 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '')
|
||||
.stat-item .lbl { font-size: .6rem; color: rgba(255,255,255,.65); text-transform: uppercase; letter-spacing: 1px; }
|
||||
.stat-sep { width: 1px; height: 26px; background: rgba(255,255,255,.2); }
|
||||
</style>
|
||||
<style>
|
||||
/* ══ DARK THEME OVERRIDE ═════════════════════════════════════ */
|
||||
:root {
|
||||
--bg: #070e1d;
|
||||
--glass: rgba(255,255,255,.04);
|
||||
--border:rgba(255,255,255,.08);
|
||||
--muted: #4a5568;
|
||||
}
|
||||
html, body {
|
||||
background: var(--bg);
|
||||
font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
/* Header modernize */
|
||||
.pg-header::before {
|
||||
content: ''; position: absolute; inset: 0; pointer-events: none;
|
||||
background: linear-gradient(135deg,rgba(0,0,0,.32) 0%,rgba(0,0,0,0) 55%,rgba(255,255,255,.07) 100%);
|
||||
}
|
||||
.pg-header { position: relative; }
|
||||
.pg-header::after {
|
||||
content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 1px;
|
||||
background: linear-gradient(90deg,transparent,rgba(255,255,255,.28),transparent);
|
||||
}
|
||||
.btn-son {
|
||||
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.2);
|
||||
color: rgba(255,255,255,.82); border-radius: 8px; padding: 4px 11px;
|
||||
font-size: .68rem; font-weight: 600; cursor: pointer; font-family: inherit;
|
||||
transition: background .2s;
|
||||
}
|
||||
.btn-son.on { background: rgba(34,197,94,.2); border-color: rgba(34,197,94,.35); color: #86efac; }
|
||||
.live-dot { width: 7px; height: 7px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 8px #22c55e; animation: pdot 2s ease-in-out infinite; }
|
||||
@keyframes pdot { 0%,100%{opacity:1} 50%{opacity:.35} }
|
||||
/* Override body/pg-* for dark */
|
||||
.pg-body { background: var(--bg); }
|
||||
.v-split { background: linear-gradient(180deg,transparent,var(--border) 12%,var(--border) 88%,transparent); width: 1px; }
|
||||
.area-col { background: var(--bg); display: flex; flex-direction: column; overflow: hidden; }
|
||||
.col-hdr { display: flex; align-items: center; gap: .55rem; padding: .38rem 1.1rem; font-size: .62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 2.5px; color: rgba(255,255,255,.88); flex-shrink: 0; }
|
||||
.col-hdr.rec { background: linear-gradient(90deg,#1e1b4b,#1e3a8a); border-bottom: 1px solid rgba(96,165,250,.18); }
|
||||
.col-hdr.mue { background: linear-gradient(90deg,#052e16,#064e3b); border-bottom: 1px solid rgba(52,211,153,.18); }
|
||||
.slot-grid { display: grid; grid-auto-rows: 1fr; height: 100%; overflow: hidden; }
|
||||
.slot { display: flex; flex-direction: column; border-bottom: 1px solid var(--border); position: relative; overflow: hidden; }
|
||||
.slot:last-child { border-bottom: none; }
|
||||
.slot-hdr { display: flex; align-items: center; gap: .4rem; padding: .27rem .9rem; font-size: .59rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1.8px; flex-shrink: 0; z-index: 1; position: relative; }
|
||||
.slot-hdr.rec { color: #93c5fd; border-bottom: 1px solid rgba(59,130,246,.1); background: rgba(30,58,138,.22); }
|
||||
.slot-hdr.mue { color: #6ee7b7; border-bottom: 1px solid rgba(16,185,129,.1); background: rgba(6,78,59,.22); }
|
||||
.slot-hdr .hdot { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; }
|
||||
.slot-hdr.rec .hdot { background: #60a5fa; box-shadow: 0 0 5px #60a5fa88; }
|
||||
.slot-hdr.mue .hdot { background: #34d399; box-shadow: 0 0 5px #34d39988; }
|
||||
.slot-body { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: .8rem 1rem; position: relative; overflow: hidden; }
|
||||
.slot-body::after { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0; transition: opacity .6s; background: radial-gradient(ellipse 80% 60% at 50% 60%, var(--pc,transparent) 0%, transparent 70%); }
|
||||
.slot-body.lit::after { opacity: .07; }
|
||||
.slot-body::before { content: ''; position: absolute; left: 0; top: 18%; bottom: 18%; width: 3px; border-radius: 0 3px 3px 0; background: var(--pc,transparent); opacity: 0; transition: opacity .5s,background .5s; }
|
||||
.slot-body.lit::before { opacity: 1; }
|
||||
.lbl-dest { font-size: clamp(.5rem,.95vw,.66rem); font-weight: 700; text-transform: uppercase; letter-spacing: 3px; color: var(--muted); margin-bottom: .1rem; z-index: 1; position: relative; transition: color .4s; }
|
||||
.slot-body.lit .lbl-dest { color: var(--pc); opacity: .7; }
|
||||
.big-code { font-size: clamp(2.5rem,8.5vw,8rem); font-weight: 900; line-height: 1; color: var(--muted); letter-spacing: -2px; font-variant-numeric: tabular-nums; z-index: 1; position: relative; transition: color .45s; }
|
||||
.slot-body.lit .big-code { color: var(--pc); }
|
||||
.pac-name { font-size: clamp(.68rem,1.5vw,1.05rem); font-weight: 700; color: rgba(255,255,255,.6); margin-top: .2rem; text-align: center; min-height: 1.3em; z-index: 1; position: relative; }
|
||||
.prio-badge { display: inline-flex; align-items: center; gap: .3rem; padding: .27rem .82rem; border-radius: 99px; font-size: clamp(.54rem,.92vw,.72rem); font-weight: 700; margin-top: .42rem; border: 1px solid rgba(255,255,255,.08); background: rgba(255,255,255,.04); color: var(--muted); transition: background .4s,color .4s,border-color .4s; z-index: 1; position: relative; }
|
||||
.ring-pop-el { position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; border-radius: 50%; transform: translate(-50%,-50%); pointer-events: none; z-index: 0; }
|
||||
.ring-pop-el.do-pop { animation: r-pop .85s ease-out forwards; }
|
||||
@keyframes r-pop { 0%{transform:translate(-50%,-50%) scale(1);background:var(--pc,var(--brand));opacity:.38} 100%{transform:translate(-50%,-50%) scale(50);background:var(--pc,var(--brand));opacity:0} }
|
||||
/* Cola dark */
|
||||
.pg-cola { background: rgba(255,255,255,.022); border-top: 1px solid var(--border); }
|
||||
.cola-lbl { color: var(--brand); }
|
||||
.cola-sep { width: 1px; height: 12px; background: var(--border); flex-shrink: 0; }
|
||||
.cola-chip { background: rgba(255,255,255,.04); font-size: .68rem; }
|
||||
.cola-chip .cdot { width: 5px; height: 5px; border-radius: 50%; }
|
||||
.cola-empty { font-size: .68rem; color: var(--muted); }
|
||||
/* Footer dark */
|
||||
.pg-footer { background: rgba(0,0,0,.45); border-top: 1px solid var(--border); }
|
||||
.stat-item .val { font-size: clamp(.78rem,1.6vw,1rem); font-variant-numeric: tabular-nums; }
|
||||
.stat-sep { background: var(--border); height: 19px; }
|
||||
/* ══ ANNOUNCEMENT OVERLAY ════════════════════════════════════ */
|
||||
#ann-overlay {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
opacity: 0; pointer-events: none; transition: opacity .3s ease;
|
||||
}
|
||||
#ann-overlay.visible { opacity: 1; pointer-events: all; }
|
||||
.ann-bg { position: absolute; inset: 0; background: rgba(4,8,20,.92); backdrop-filter: blur(18px); transition: background .4s; }
|
||||
.ann-card {
|
||||
position: relative; z-index: 1; width: min(600px,88vw);
|
||||
background: rgba(255,255,255,.055); border: 1px solid rgba(255,255,255,.11);
|
||||
border-radius: 28px; padding: 2.8rem 3.5rem 2.4rem; text-align: center;
|
||||
box-shadow: 0 30px 90px rgba(0,0,0,.7), 0 0 70px var(--ac,#1565c0)44;
|
||||
transform: scale(.88) translateY(14px);
|
||||
transition: transform .4s cubic-bezier(.34,1.5,.64,1);
|
||||
}
|
||||
#ann-overlay.visible .ann-card { transform: scale(1) translateY(0); }
|
||||
.ann-card::before { content: ''; position: absolute; left: 0; top: 12%; bottom: 12%; width: 4px; border-radius: 0 4px 4px 0; background: var(--ac,var(--brand)); box-shadow: 0 0 28px var(--ac,var(--brand)); }
|
||||
.ann-ring { position: absolute; top: 50%; left: 50%; width: 10px; height: 10px; border-radius: 50%; transform: translate(-50%,-50%); pointer-events: none; }
|
||||
.ann-ring.pop { animation: ann-pop 1.1s ease-out forwards; }
|
||||
@keyframes ann-pop { 0%{transform:translate(-50%,-50%) scale(1);background:var(--ac,var(--brand));opacity:.4} 100%{transform:translate(-50%,-50%) scale(55);background:var(--ac,var(--brand));opacity:0} }
|
||||
.ann-label { font-size: clamp(.68rem,1.5vw,.88rem); font-weight: 700; text-transform: uppercase; letter-spacing: 3.5px; color: rgba(255,255,255,.42); margin-bottom: .5rem; }
|
||||
.ann-code { font-size: clamp(5rem,17vw,11rem); font-weight: 900; line-height: 1; letter-spacing: -4px; font-variant-numeric: tabular-nums; color: var(--ac,var(--brand)); text-shadow: 0 0 90px var(--ac,var(--brand))55; position: relative; z-index: 1; }
|
||||
.ann-pac { font-size: clamp(.85rem,2.1vw,1.25rem); font-weight: 700; color: rgba(255,255,255,.72); margin-top: .55rem; min-height: 1.5em; }
|
||||
.ann-chip { display: inline-flex; align-items: center; gap: .35rem; padding: .34rem 1.1rem; border-radius: 99px; font-size: clamp(.6rem,1.05vw,.8rem); font-weight: 700; margin-top: .7rem; border: 1px solid; }
|
||||
.ann-progress { width: 100%; height: 3px; background: rgba(255,255,255,.07); border-radius: 99px; margin-top: 1.7rem; overflow: hidden; }
|
||||
.ann-bar { height: 100%; width: 100%; border-radius: 99px; background: var(--ac,var(--brand)); box-shadow: 0 0 12px var(--ac,var(--brand)); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="pg-grid">
|
||||
<!-- ══ Announcement overlay ══════════════════════════════════════ -->
|
||||
<div id="ann-overlay">
|
||||
<div class="ann-bg" id="ann-bg"></div>
|
||||
<div class="ann-card">
|
||||
<div class="ann-ring" id="ann-ring"></div>
|
||||
<div class="ann-label" id="ann-label">Pase a Recepción</div>
|
||||
<div class="ann-code" id="ann-code">A01</div>
|
||||
<div class="ann-pac" id="ann-pac"></div>
|
||||
<div class="ann-chip" id="ann-chip"></div>
|
||||
<div class="ann-progress"><div class="ann-bar" id="ann-bar"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Main layout ════════════════════════════════════════════════ -->
|
||||
<div class="pg-wrap">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="pg-header">
|
||||
<div class="header-brand">
|
||||
<?php if ($_labLogo): ?>
|
||||
@@ -248,86 +362,58 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '')
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button class="btn-sonido" id="btn-sonido" onclick="activarSonido()">
|
||||
<div class="live-dot" title="En línea"></div>
|
||||
<button class="btn-son" id="btn-sonido" onclick="event.stopPropagation();activarSonido()">
|
||||
<i class="fas fa-volume-mute"></i> Sonido
|
||||
</button>
|
||||
<div class="reloj" id="reloj">--:--:--</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Cuerpo: Recepción | Muestras -->
|
||||
<div class="pg-body" id="pg-body">
|
||||
|
||||
<!-- Panel Recepción (dinámico: N escritorios) -->
|
||||
<div class="area-panel" id="panel-recepcion">
|
||||
<div class="area-header recepcion">
|
||||
<i class="fas fa-door-open"></i> Recepción
|
||||
</div>
|
||||
<div class="lugares-grid" id="rec-grid">
|
||||
<!-- Se genera en JS según escritorios configurados -->
|
||||
</div>
|
||||
<div class="pg-body">
|
||||
<div class="area-col">
|
||||
<div class="col-hdr rec"><i class="fas fa-door-open"></i> Recepción</div>
|
||||
<div class="slot-grid" id="rec-grid"></div>
|
||||
</div>
|
||||
|
||||
<!-- Divisor -->
|
||||
<div class="area-divider"></div>
|
||||
|
||||
<!-- Panel Muestras (múltiples lugares) -->
|
||||
<div class="area-panel">
|
||||
<div class="area-header muestras">
|
||||
<i class="fas fa-vials"></i> Toma de Muestras
|
||||
</div>
|
||||
<div class="lugares-grid" id="lugares-grid">
|
||||
<!-- Se genera en JS -->
|
||||
</div>
|
||||
<div class="v-split"></div>
|
||||
<div class="area-col">
|
||||
<div class="col-hdr mue"><i class="fas fa-vials"></i> Toma de Muestras</div>
|
||||
<div class="slot-grid" id="lugares-grid"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Cola en espera -->
|
||||
<div class="pg-cola" id="pg-cola">
|
||||
<span class="cola-lbl"><i class="fas fa-list-ol me-1"></i>EN ESPERA</span>
|
||||
<span class="cola-vacia-msg" id="cola-vacia">Cola vacía</span>
|
||||
<span class="cola-lbl"><i class="fas fa-list-ol" style="margin-right:.3rem"></i>EN ESPERA</span>
|
||||
<div class="cola-sep"></div>
|
||||
<span class="cola-empty" id="cola-vacia">Cola vacía</span>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="pg-footer">
|
||||
<div class="stat-item">
|
||||
<div class="val" id="st-espera">—</div>
|
||||
<div class="lbl">En espera</div>
|
||||
</div>
|
||||
<div class="stat-item"><div class="val" id="st-espera">—</div><div class="lbl">En espera</div></div>
|
||||
<div class="stat-sep"></div>
|
||||
<div class="stat-item">
|
||||
<div class="val" id="st-atencion">—</div>
|
||||
<div class="lbl">En atención</div>
|
||||
</div>
|
||||
<div class="stat-item"><div class="val" id="st-atencion">—</div><div class="lbl">En atención</div></div>
|
||||
<div class="stat-sep"></div>
|
||||
<div class="stat-item">
|
||||
<div class="val" id="st-finalizados">—</div>
|
||||
<div class="lbl">Atendidos hoy</div>
|
||||
</div>
|
||||
<div class="stat-item"><div class="val" id="st-finalizados">—</div><div class="lbl">Atendidos</div></div>
|
||||
<div class="stat-sep"></div>
|
||||
<div class="stat-item">
|
||||
<div class="val" id="st-total">—</div>
|
||||
<div class="lbl">Total del día</div>
|
||||
</div>
|
||||
<div class="stat-item"><div class="val" id="st-total">—</div><div class="lbl">Total día</div></div>
|
||||
<div class="stat-sep"></div>
|
||||
<div class="stat-item">
|
||||
<div class="val" id="st-tiempo">—</div>
|
||||
<div class="lbl">Tiempo prom.</div>
|
||||
</div>
|
||||
<div class="stat-item"><div class="val" id="st-tiempo">—</div><div class="lbl">T. promedio</div></div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const BASE_API = '<?= BASE_URL ?>modules/turnero/api/';
|
||||
|
||||
// ── Reloj ──────────────────────────────────────────────────────
|
||||
function actualizarReloj() {
|
||||
function tick() {
|
||||
const d = new Date();
|
||||
document.getElementById('reloj').textContent =
|
||||
new Date().toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
String(d.getHours()).padStart(2,'0') + ':' +
|
||||
String(d.getMinutes()).padStart(2,'0') + ':' +
|
||||
String(d.getSeconds()).padStart(2,'0');
|
||||
}
|
||||
actualizarReloj();
|
||||
setInterval(actualizarReloj, 1000);
|
||||
tick(); setInterval(tick, 1000);
|
||||
|
||||
// ── Audio ──────────────────────────────────────────────────────
|
||||
let audioCtx = null, sonidoActivo = false;
|
||||
@@ -343,9 +429,7 @@ function activarSonido() {
|
||||
const btn = document.getElementById('btn-sonido');
|
||||
if (btn) {
|
||||
btn.innerHTML = '<i class="fas fa-volume-up"></i> Sonido ON';
|
||||
btn.style.background = 'rgba(34,197,94,.25)';
|
||||
btn.style.borderColor = 'rgba(34,197,94,.5)';
|
||||
btn.onclick = null; btn.style.cursor = 'default';
|
||||
btn.classList.add('on');
|
||||
setTimeout(playBeep, 100);
|
||||
}
|
||||
}
|
||||
@@ -383,173 +467,207 @@ function anunciarTurno(codigo, destino) {
|
||||
}
|
||||
|
||||
// ── Estado previo ──────────────────────────────────────────────
|
||||
const lastCodRecepcion = {}; // { desk_id: codigo }
|
||||
const lastCodLugar = {}; // { lugar_id: codigo }
|
||||
// ── Announcement queue ────────────────────────────────────────
|
||||
const annQueue = [];
|
||||
let isAnnouncing = false;
|
||||
|
||||
// ── Escape HTML ────────────────────────────────────────────────
|
||||
function esc(str) {
|
||||
function queueAnnouncement(item) {
|
||||
if (annQueue.some(q => q._key === item._key)) return;
|
||||
annQueue.push(item);
|
||||
if (!isAnnouncing) processQueue();
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (annQueue.length === 0) { isAnnouncing = false; return; }
|
||||
isAnnouncing = true;
|
||||
showAnnouncement(annQueue.shift());
|
||||
}
|
||||
|
||||
function showAnnouncement({ codigo, destino, paciente, prio_codigo, prio_nombre, color }) {
|
||||
const ov = document.getElementById('ann-overlay');
|
||||
const bg = document.getElementById('ann-bg');
|
||||
const ring = document.getElementById('ann-ring');
|
||||
const code = document.getElementById('ann-code');
|
||||
const lbl = document.getElementById('ann-label');
|
||||
const pac = document.getElementById('ann-pac');
|
||||
const chip = document.getElementById('ann-chip');
|
||||
const bar = document.getElementById('ann-bar');
|
||||
const c = color || '#1565c0';
|
||||
|
||||
code.textContent = codigo;
|
||||
lbl.textContent = 'Pase a ' + destino;
|
||||
pac.textContent = paciente || '';
|
||||
chip.innerHTML = '<i class="fas fa-ticket-alt"></i> ' + esc(prio_codigo||'') + ' — ' + esc(prio_nombre||'');
|
||||
|
||||
ov.style.setProperty('--ac', c);
|
||||
bg.style.background = 'radial-gradient(ellipse at 50% 45%, ' + c + '2a 0%, rgba(4,8,20,.94) 65%)';
|
||||
chip.style.color = c;
|
||||
chip.style.borderColor = c + '55';
|
||||
chip.style.background = c + '18';
|
||||
|
||||
ov.classList.add('visible');
|
||||
ring.classList.remove('pop');
|
||||
void ring.offsetWidth;
|
||||
ring.classList.add('pop');
|
||||
|
||||
bar.style.transition = 'none';
|
||||
bar.style.width = '100%';
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
bar.style.transition = 'width 1.65s linear';
|
||||
bar.style.width = '0%';
|
||||
}));
|
||||
|
||||
anunciarTurno(codigo, destino);
|
||||
|
||||
setTimeout(() => {
|
||||
ov.classList.remove('visible');
|
||||
setTimeout(processQueue, 370);
|
||||
}, 1950);
|
||||
}
|
||||
|
||||
// ── State ───────────────────────────────────────────────
|
||||
const lastRec = {};
|
||||
const lastLugar = {};
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.appendChild(document.createTextNode(str));
|
||||
d.appendChild(document.createTextNode(String(s ?? '')));
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
// ── Aplicar color de prioridad a un panel ─────────────────────
|
||||
function aplicarColor(panelEl, ringEl, chipEl, color) {
|
||||
panelEl.style.setProperty('--prio-color', color);
|
||||
panelEl.style.background = color + '12';
|
||||
chipEl.style.background = color + '22';
|
||||
chipEl.style.color = color;
|
||||
if (ringEl) ringEl.style.setProperty('--prio-color', color);
|
||||
}
|
||||
function limpiarColor(panelEl, ringEl, chipEl) {
|
||||
panelEl.style.removeProperty('--prio-color');
|
||||
panelEl.style.background = '#fff';
|
||||
chipEl.style.background = '#f1f5f9';
|
||||
chipEl.style.color = '#94a3b8';
|
||||
if (ringEl) ringEl.style.removeProperty('--prio-color');
|
||||
function buildSlot(id, nombre, turno, tipo) {
|
||||
const has = !!turno;
|
||||
const c = has ? (turno.prioridad_color || '#1565c0') : null;
|
||||
const pcSt = c ? '--pc:' + c + ';' : '';
|
||||
const cls = has ? 'slot-body lit' : 'slot-body';
|
||||
const hCls = tipo === 'rec' ? 'slot-hdr rec' : 'slot-hdr mue';
|
||||
const icon = tipo === 'rec' ? 'fas fa-concierge-bell' : 'fas fa-flask';
|
||||
const cod = has ? esc(turno.codigo) : '—';
|
||||
const pac = has ? esc(turno.paciente_nombre || '') : '';
|
||||
const chip = has
|
||||
? "<i class='fas fa-ticket-alt'></i> " + esc(turno.prioridad_codigo) + ' — ' + esc(turno.prioridad_nombre)
|
||||
: "<i class='fas fa-hourglass-half'></i> En espera";
|
||||
const chipSt = has && c ? 'style="color:' + c + ';border-color:' + c + '44;background:' + c + '15"' : '';
|
||||
return '<div class="slot" id="slot-' + tipo + '-' + id + '">'
|
||||
+ '<div class="' + hCls + '"><span class="hdot"></span><i class="' + icon + '"></i> ' + esc(nombre) + '</div>'
|
||||
+ '<div class="' + cls + '" id="body-' + tipo + '-' + id + '" style="' + pcSt + '">'
|
||||
+ '<div class="ring-pop-el" id="ring-' + tipo + '-' + id + '"></div>'
|
||||
+ '<div class="lbl-dest">' + esc(nombre) + '</div>'
|
||||
+ '<div class="big-code" id="cod-' + tipo + '-' + id + '">' + cod + '</div>'
|
||||
+ '<div class="pac-name" id="pac-' + tipo + '-' + id + '">' + pac + '</div>'
|
||||
+ '<div class="prio-badge" id="chip-' + tipo + '-' + id + '" ' + chipSt + '>' + chip + '</div>'
|
||||
+ '</div></div>';
|
||||
}
|
||||
|
||||
// ── Render snapshot global ─────────────────────────────────────
|
||||
function updateSlot(id, turno, tipo) {
|
||||
const body = document.getElementById('body-' + tipo + '-' + id);
|
||||
const cod = document.getElementById('cod-' + tipo + '-' + id);
|
||||
const pac = document.getElementById('pac-' + tipo + '-' + id);
|
||||
const chip = document.getElementById('chip-' + tipo + '-' + id);
|
||||
if (!body) return false;
|
||||
const c = turno ? (turno.prioridad_color || '#1565c0') : null;
|
||||
if (turno) {
|
||||
body.className = 'slot-body lit';
|
||||
body.style.setProperty('--pc', c);
|
||||
cod.textContent = turno.codigo;
|
||||
pac.textContent = turno.paciente_nombre || '';
|
||||
chip.innerHTML = "<i class='fas fa-ticket-alt'></i> " + esc(turno.prioridad_codigo) + ' — ' + esc(turno.prioridad_nombre);
|
||||
chip.style.color = c; chip.style.borderColor = c + '44'; chip.style.background = c + '15';
|
||||
} else {
|
||||
body.className = 'slot-body';
|
||||
body.style.removeProperty('--pc');
|
||||
cod.textContent = '—';
|
||||
pac.textContent = '';
|
||||
chip.innerHTML = "<i class='fas fa-hourglass-half'></i> En espera";
|
||||
chip.style.color = chip.style.borderColor = chip.style.background = '';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Render ────────────────────────────────────────────────────
|
||||
function renderSnapshot(snap) {
|
||||
// ── Recepción (múltiples escritorios) ───────────────────
|
||||
const recList = snap.activos_recepcion || [];
|
||||
const mueList = snap.lugares || [];
|
||||
const recGrid = document.getElementById('rec-grid');
|
||||
const mueGrid = document.getElementById('lugares-grid');
|
||||
|
||||
recGrid.innerHTML = recList.map(d => {
|
||||
const t = d.turno;
|
||||
const color = t ? (t.prioridad_color || '#1565c0') : null;
|
||||
const colorStyle = color ? `style="--prio-color:${color};background:${color}12"` : '';
|
||||
const codStyle = color ? '' : 'style="color:#cbd5e1"';
|
||||
const chipStyle = color
|
||||
? `style="background:${color}22;color:${color}"`
|
||||
: 'style="background:#f1f5f9;color:#94a3b8"';
|
||||
const chipContent = t
|
||||
? `<i class='fas fa-ticket-alt'></i> ${esc(t.prioridad_codigo)} — ${esc(t.prioridad_nombre)}`
|
||||
: `<i class='fas fa-hourglass-half'></i> En espera`;
|
||||
const codText = t ? esc(t.codigo) : '—';
|
||||
const pacText = t ? esc(t.paciente_nombre || '') : '';
|
||||
|
||||
return `
|
||||
<div class="lugar-sub" id="sub-rec-${d.desk_id}">
|
||||
<div class="area-header" style="background:#1e40af;font-size:.7rem;padding:.35rem 1rem">
|
||||
<i class="fas fa-door-open"></i> ${esc(d.desk_nombre)}
|
||||
</div>
|
||||
<div class="area-turno" id="area-rec-${d.desk_id}" ${colorStyle}>
|
||||
<div class="ring" id="ring-rec-${d.desk_id}"></div>
|
||||
<div class="lbl-destino">Pase a ${esc(d.desk_nombre)}</div>
|
||||
<div class="codigo-num" id="cod-rec-${d.desk_id}" ${codStyle}>${codText}</div>
|
||||
<div class="pac-nombre">${pacText}</div>
|
||||
<div class="prio-chip" id="chip-rec-${d.desk_id}" ${chipStyle}>${chipContent}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Sonido/animación por desk
|
||||
// Recepción
|
||||
const curRecIds = [...recGrid.querySelectorAll('.slot')].map(el => +el.id.replace('slot-rec-',''));
|
||||
const newRecIds = recList.map(d => d.desk_id);
|
||||
if (curRecIds.length !== newRecIds.length || newRecIds.some(id => !curRecIds.includes(id))) {
|
||||
recGrid.innerHTML = recList.map(d => buildSlot(d.desk_id, d.desk_nombre, d.turno, 'rec')).join('');
|
||||
} else {
|
||||
recList.forEach(d => updateSlot(d.desk_id, d.turno, 'rec'));
|
||||
}
|
||||
recList.forEach(d => {
|
||||
const t = d.turno;
|
||||
if (!t) { lastCodRecepcion[d.desk_id] = null; return; }
|
||||
if (t.codigo !== lastCodRecepcion[d.desk_id]) {
|
||||
anunciarTurno(t.codigo, d.desk_nombre);
|
||||
const ring = document.getElementById(`ring-rec-${d.desk_id}`);
|
||||
if (ring) { ring.classList.remove('animar'); void ring.offsetWidth; ring.classList.add('animar'); }
|
||||
lastCodRecepcion[d.desk_id] = t.codigo;
|
||||
if (!t) { lastRec[d.desk_id] = null; return; }
|
||||
const clave = t.codigo + '|' + (t.llamado_at || '');
|
||||
if (clave !== lastRec[d.desk_id]) {
|
||||
queueAnnouncement({ _key: 'rec-' + d.desk_id + '-' + clave, codigo: t.codigo, destino: d.desk_nombre, paciente: t.paciente_nombre, prio_codigo: t.prioridad_codigo, prio_nombre: t.prioridad_nombre, color: t.prioridad_color });
|
||||
const ring = document.getElementById('ring-rec-' + d.desk_id);
|
||||
if (ring) { ring.classList.remove('do-pop'); void ring.offsetWidth; ring.classList.add('do-pop'); }
|
||||
lastRec[d.desk_id] = clave;
|
||||
}
|
||||
});
|
||||
|
||||
// ── Lugares (muestras) ──────────────────────────────────
|
||||
const lugares = snap.lugares || [];
|
||||
const grid = document.getElementById('lugares-grid');
|
||||
|
||||
// Construir HTML para todos los lugares
|
||||
grid.innerHTML = lugares.map(l => {
|
||||
const t = l.turno;
|
||||
const color = t ? (t.prioridad_color || '#15803d') : null;
|
||||
|
||||
const colorStyle = color ? `style="--prio-color:${color};background:${color}12"` : '';
|
||||
const codStyle = color ? '' : 'style="color:#cbd5e1"';
|
||||
const chipStyle = color
|
||||
? `style="background:${color}22;color:${color}"`
|
||||
: 'style="background:#f1f5f9;color:#94a3b8"';
|
||||
const chipContent = t
|
||||
? `<i class='fas fa-ticket-alt'></i> ${esc(t.prioridad_codigo)} — ${esc(t.prioridad_nombre)}`
|
||||
: `<i class='fas fa-hourglass-half'></i> En espera`;
|
||||
const codText = t ? esc(t.codigo) : '—';
|
||||
const pacText = t ? esc(t.paciente_nombre || '') : '';
|
||||
const ringId = `ring-lugar-${l.lugar_id}`;
|
||||
const panelId = `area-lugar-${l.lugar_id}`;
|
||||
const codId = `cod-lugar-${l.lugar_id}`;
|
||||
const chipId = `chip-lugar-${l.lugar_id}`;
|
||||
|
||||
return `
|
||||
<div class="lugar-sub" id="sub-lugar-${l.lugar_id}">
|
||||
<div class="area-header" style="background:#166534;font-size:.7rem;padding:.35rem 1rem">
|
||||
<i class="fas fa-flask"></i> ${esc(l.lugar_nombre)}
|
||||
</div>
|
||||
<div class="area-turno" id="${panelId}" ${colorStyle}>
|
||||
<div class="ring" id="${ringId}"></div>
|
||||
<div class="lbl-destino">Pase a ${esc(l.lugar_nombre)}</div>
|
||||
<div class="codigo-num" id="${codId}" ${codStyle}>${codText}</div>
|
||||
<div class="pac-nombre">${pacText}</div>
|
||||
<div class="prio-chip" id="${chipId}" ${chipStyle}>${chipContent}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Animaciones/sonido para lugares que cambiaron
|
||||
lugares.forEach(l => {
|
||||
const t = l.turno;
|
||||
if (!t) { lastCodLugar[l.lugar_id] = null; return; }
|
||||
if (t.codigo !== lastCodLugar[l.lugar_id]) {
|
||||
anunciarTurno(t.codigo, l.lugar_nombre);
|
||||
const ring = document.getElementById(`ring-lugar-${l.lugar_id}`);
|
||||
if (ring) {
|
||||
ring.classList.remove('animar');
|
||||
void ring.offsetWidth;
|
||||
ring.classList.add('animar');
|
||||
}
|
||||
lastCodLugar[l.lugar_id] = t.codigo;
|
||||
}
|
||||
});
|
||||
|
||||
// ── Cola en espera ───────────────────────────────────────
|
||||
const cola = snap.cola || [];
|
||||
const pgCola = document.getElementById('pg-cola');
|
||||
const vaciaMg = document.getElementById('cola-vacia');
|
||||
|
||||
// Limpiar chips anteriores (conservar el label y el mensaje)
|
||||
pgCola.querySelectorAll('.cola-chip').forEach(c => c.remove());
|
||||
|
||||
if (cola.length === 0) {
|
||||
if (vaciaMg) vaciaMg.style.display = '';
|
||||
// Muestras
|
||||
const curMueIds = [...mueGrid.querySelectorAll('.slot')].map(el => +el.id.replace('slot-mue-',''));
|
||||
const newMueIds = mueList.map(l => l.lugar_id);
|
||||
if (curMueIds.length !== newMueIds.length || newMueIds.some(id => !curMueIds.includes(id))) {
|
||||
mueGrid.innerHTML = mueList.map(l => buildSlot(l.lugar_id, l.lugar_nombre, l.turno, 'mue')).join('');
|
||||
} else {
|
||||
if (vaciaMg) vaciaMg.style.display = 'none';
|
||||
mueList.forEach(l => updateSlot(l.lugar_id, l.turno, 'mue'));
|
||||
}
|
||||
mueList.forEach(l => {
|
||||
const t = l.turno;
|
||||
if (!t) { lastLugar[l.lugar_id] = null; return; }
|
||||
const clave = t.codigo + '|' + (t.llamado_at || '');
|
||||
if (clave !== lastLugar[l.lugar_id]) {
|
||||
queueAnnouncement({ _key: 'mue-' + l.lugar_id + '-' + clave, codigo: t.codigo, destino: l.lugar_nombre, paciente: t.paciente_nombre, prio_codigo: t.prioridad_codigo, prio_nombre: t.prioridad_nombre, color: t.prioridad_color });
|
||||
const ring = document.getElementById('ring-mue-' + l.lugar_id);
|
||||
if (ring) { ring.classList.remove('do-pop'); void ring.offsetWidth; ring.classList.add('do-pop'); }
|
||||
lastLugar[l.lugar_id] = clave;
|
||||
}
|
||||
});
|
||||
|
||||
// Cola
|
||||
const cola = snap.cola || [];
|
||||
const pgCola = document.getElementById('pg-cola');
|
||||
pgCola.querySelectorAll('.cola-chip').forEach(c => c.remove());
|
||||
const vaciaMg = document.getElementById('cola-vacia');
|
||||
if (cola.length === 0) {
|
||||
vaciaMg.style.display = '';
|
||||
} else {
|
||||
vaciaMg.style.display = 'none';
|
||||
cola.forEach(t => {
|
||||
const chip = document.createElement('span');
|
||||
chip.className = 'cola-chip';
|
||||
chip.style.color = t.prioridad_color || '#1565c0';
|
||||
chip.style.borderColor = t.prioridad_color || '#1565c0';
|
||||
chip.innerHTML = `<span style="width:8px;height:8px;border-radius:50%;background:${t.prioridad_color || '#1565c0'};display:inline-block"></span> ${esc(t.codigo)}`;
|
||||
const c = t.prioridad_color || '#1565c0';
|
||||
chip.className = 'cola-chip';
|
||||
chip.style.color = c; chip.style.borderColor = c + '55'; chip.style.background = c + '12';
|
||||
chip.innerHTML = '<span class="cdot" style="background:' + c + ';box-shadow:0 0 5px ' + c + '88"></span>' + esc(t.codigo);
|
||||
pgCola.appendChild(chip);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Stats ────────────────────────────────────────────────
|
||||
// Stats
|
||||
const s = snap.stats || {};
|
||||
document.getElementById('st-espera').textContent = s.en_espera || 0;
|
||||
document.getElementById('st-atencion').textContent = s.en_atencion || 0;
|
||||
document.getElementById('st-finalizados').textContent = s.finalizados || 0;
|
||||
document.getElementById('st-total').textContent = s.total || 0;
|
||||
document.getElementById('st-espera').textContent = s.en_espera || 0;
|
||||
document.getElementById('st-atencion').textContent = s.en_atencion || 0;
|
||||
document.getElementById('st-finalizados').textContent = s.finalizados || 0;
|
||||
document.getElementById('st-total').textContent = s.total || 0;
|
||||
document.getElementById('st-tiempo').textContent = s.tiempo_promedio_atencion
|
||||
? s.tiempo_promedio_atencion.substring(0, 5) : '—';
|
||||
}
|
||||
|
||||
// ── Polling cada 4 s ──────────────────────────────────────────
|
||||
// ── Polling ───────────────────────────────────────────────────
|
||||
async function cargarSnapshot() {
|
||||
try {
|
||||
const res = await fetch(BASE_API + 'get_display_global.php', { cache: 'no-store' });
|
||||
const json = await res.json();
|
||||
if (json.ok) renderSnapshot(json);
|
||||
} catch (_) {}
|
||||
} catch(_) {}
|
||||
}
|
||||
|
||||
cargarSnapshot();
|
||||
|
||||
Reference in New Issue
Block a user