feat: sesiones minimizadas múltiples — píldoras con nombre de chat y navegación

This commit is contained in:
Lizandro Guarnizo
2026-03-13 08:57:10 -05:00
parent e9c8cf7d9b
commit b8b4bffde2
+220 -111
View File
@@ -6432,42 +6432,60 @@ if (!isset($_SESSION['user_id'])) {
#modalAgendarDomicilio .is-invalid ~ .field-err,
#modalAgendarDomicilio .is-invalid + .field-err { display:block; }
/* ── Modo flotante (minimizado) ── */
#modalAgendarDomicilio.dom-flotante {
position: fixed !important;
top: auto !important;
left: auto !important;
bottom: 16px !important;
right: 16px !important;
width: auto !important;
margin: 0 !important;
z-index: 1055;
display: block !important;
pointer-events: auto;
/* ── Barra de sesiones minimizadas ── */
#labdom-pills-bar {
position: fixed;
bottom: 0;
left: 0;
z-index: 1060;
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 14px 8px;
pointer-events: none;
max-width: 80vw;
}
#modalAgendarDomicilio.dom-flotante .modal-dialog {
margin: 0;
width: 320px;
max-height: none;
transform: none !important;
}
#modalAgendarDomicilio.dom-flotante .modal-body,
#modalAgendarDomicilio.dom-flotante #labdom-form-panel,
#modalAgendarDomicilio.dom-flotante #labdom-footer,
#modalAgendarDomicilio.dom-flotante #labdom-ok-panel { display: none !important; }
#modalAgendarDomicilio.dom-flotante .modal-content {
box-shadow: 0 8px 32px rgba(0,0,0,.28);
border-radius: 12px;
}
#modalAgendarDomicilio.dom-flotante .modal-header {
cursor: move;
border-radius: 12px;
.labdom-pill {
pointer-events: all;
display: flex;
align-items: center;
gap: 5px;
background: linear-gradient(135deg, #1565c0, #0d47a1);
color: #fff;
border-radius: 24px;
padding: 5px 8px 5px 6px;
cursor: pointer;
font-size: .78rem;
box-shadow: 0 4px 16px rgba(0,0,0,.28);
max-width: 280px;
transition: background .15s, transform .1s, box-shadow .1s;
user-select: none;
animation: pillIn .18s ease;
}
/* Botón restaurar dentro del header minimizado */
.labdom-restore-hint {
font-size:.72rem; opacity:.8; white-space:nowrap;
.labdom-pill:hover { background: linear-gradient(135deg,#1976d2,#1565c0); transform: translateY(-2px); box-shadow: 0 6px 22px rgba(0,0,0,.32); }
@keyframes pillIn { from { transform:translateY(10px); opacity:0 } to { transform:translateY(0); opacity:1 } }
.labdom-pill-num {
background: rgba(255,255,255,.28);
border-radius: 50%;
width: 22px; height: 22px;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: .72rem; flex-shrink: 0;
}
.labdom-pill-body { display:flex; flex-direction:column; overflow:hidden; }
.labdom-pill-chat { font-weight: 600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 110px; font-size:.78rem; }
.labdom-pill-pac { font-size:.68rem; opacity:.85; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 110px; }
.labdom-pill-close {
background: rgba(255,255,255,.18);
border: none; color: #fff;
border-radius: 50%;
width: 17px; height: 17px;
display: flex; align-items:center; justify-content:center;
font-size: .6rem; cursor: pointer; padding: 0; flex-shrink: 0;
transition: background .12s;
line-height: 1;
}
.labdom-pill-close:hover { background: rgba(255,50,50,.55); }
/* Botón minimizar */
#labdom-btn-minimizar {
background: rgba(255,255,255,.18);
@@ -6493,7 +6511,7 @@ if (!isset($_SESSION['user_id'])) {
<small id="labdom-header-pac" class="opacity-75"></small>
</div>
<div class="d-flex align-items-center gap-2">
<button id="labdom-btn-minimizar" title="Minimizar — sigue agendando mientras copias datos"
<button id="labdom-btn-minimizar" title="Minimizar — guarda el formulario y crea una píldora en la barra inferior"
onclick="labDomicilio.minimizar()">
<i class="fas fa-window-minimize"></i> Minimizar
</button>
@@ -6773,6 +6791,10 @@ const labDomicilio = (() => {
let _histDirs = []; // direcciones únicas previas del paciente
let _bsModal = null;
// Sesiones minimizadas (múltiples chats en paralelo)
let _sesiones = []; // [{ id, chatUserId, chatName, chatPhone, convId, pacienteId, pacNombre, histDirs, msgId, localFile, cardVisible, pacInfo, fields }]
let _sesionIdCounter = 0;
// ── helpers DOM ──────────────────────────────────────────────────────────
const $ = id => document.getElementById(id);
const fv = id => $( id).value.trim();
@@ -7281,93 +7303,177 @@ const labDomicilio = (() => {
return el ? el.textContent.trim() : '';
},
/* ─── Minimizar / restaurar modal ───
* Al minimizar: oculta backdrop, convierte el modal en flotante
* sin perder ningún dato del formulario.
* Al restaurar: vuelve al estado normal de Bootstrap. */
/* ─────────────────────────────────────────────────────────────────
* SISTEMA DE SESIONES MINIMIZADAS
* Al minimizar: guarda estado del form en memoria y muestra una
* píldora en la barra inferior con el nombre del chat + paciente.
* Al hacer clic en la píldora: navega al chat y restaura el form.
* ───────────────────────────────────────────────────────────────── */
minimizar() {
const modalEl = document.getElementById('modalAgendarDomicilio');
const isMin = !modalEl.classList.contains('dom-flotante');
// Capturar nombre e identificadores del chat activo
const chatName = document.getElementById('chat-name-text')?.textContent?.trim()
|| document.getElementById('chat-name')?.textContent?.trim()
|| 'Chat';
const chatPhone = document.getElementById('chat-phone')?.textContent?.trim() || '';
const chatUserId = (typeof chatApp !== 'undefined') ? (chatApp.currentUserId || 0) : 0;
if (isMin) {
/* — Pasar a modo flotante — */
// 1. Quitar backdrop
document.querySelectorAll('.modal-backdrop').forEach(b => b.remove());
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
document.body.style.paddingRight = '';
// Construir objeto de sesión
const sesion = {
id: ++_sesionIdCounter,
chatUserId,
chatName,
chatPhone,
convId: _convId,
pacienteId: _pacienteId,
pacNombre: _pacNombre,
histDirs: [..._histDirs],
msgId: _msgId,
localFile: _localFile,
cardVisible: !document.getElementById('labdom-pac-card')?.classList.contains('d-none'),
pacInfo: {
nombre: document.getElementById('labdom-pac-nombre')?.textContent || '',
info: document.getElementById('labdom-pac-info')?.textContent || '',
tel: document.getElementById('labdom-pac-tel')?.textContent || '',
email: document.getElementById('labdom-pac-email')?.textContent || '',
eps: document.getElementById('labdom-pac-eps')?.textContent || '',
},
fields: {},
};
// 2. Desvincular de Bootstrap para posicionarlo libre
modalEl.removeAttribute('aria-modal');
modalEl.removeAttribute('role');
// Mantener visible pero fuera del flujo Bootstrap
modalEl.style.display = 'block';
modalEl.classList.add('dom-flotante');
// Capturar todos los campos del formulario
['labdom-fecha','labdom-hora','labdom-tipo','labdom-direccion','labdom-barrio',
'labdom-ciudad','labdom-indicaciones','labdom-examenes','labdom-notas',
'labdom-seguro-nombre','labdom-autorizacion','labdom-copago',
'labdom-valor-domicilio','labdom-valor-copago','labdom-pac-fnac',
'labdom-pac-correo','labdom-pac-busq'].forEach(id => {
const el = document.getElementById(id);
if (el) sesion.fields[id] = el.value;
});
const tcr = document.querySelector('input[name="labdom-tipo-cliente"]:checked');
sesion.fields['labdom-tipo-cliente'] = tcr ? tcr.value : 'particular';
// 3. Título del botón
const icon = document.querySelector('#labdom-btn-minimizar i');
if (icon) { icon.className = 'fas fa-window-restore'; }
const btn = document.getElementById('labdom-btn-minimizar');
if (btn) btn.title = 'Restaurar modal';
_sesiones.push(sesion);
// 4. Activar drag
labDomicilio._initDrag();
// Cerrar el modal completamente
if (_bsModal) _bsModal.hide();
} else {
/* — Restaurar modo normal — */
modalEl.classList.remove('dom-flotante');
// Limpiar posición manual que puso _initDrag
const dialog = modalEl.querySelector('.modal-dialog');
if (dialog) {
dialog.style.position = '';
dialog.style.left = '';
dialog.style.top = '';
dialog.style.margin = '';
}
// Re-mostrar con Bootstrap para restaurar backdrop
if (_bsModal) {
// Bootstrap detecta el estado por aria-modal
modalEl.setAttribute('aria-modal', 'true');
modalEl.setAttribute('role', 'dialog');
_bsModal.show();
}
const icon = document.querySelector('#labdom-btn-minimizar i');
if (icon) { icon.className = 'fas fa-window-minimize'; }
const btn = document.getElementById('labdom-btn-minimizar');
if (btn) btn.title = 'Minimizar — sigue agendando mientras copias datos';
}
// Renderizar píldoras
labDomicilio._renderPills();
},
/* ─── Drag de la ventana flotante ─── */
_initDrag() {
const dialog = document.querySelector('#modalAgendarDomicilio .modal-dialog');
const handle = document.querySelector('#modalAgendarDomicilio .modal-header');
if (!handle || handle._dragInit) return;
handle._dragInit = true;
let sx, sy, sl, st;
handle.addEventListener('mousedown', ev => {
if (ev.target.closest('button')) return;
const r = dialog.getBoundingClientRect();
sx = ev.clientX; sy = ev.clientY;
sl = r.left; st = r.top;
dialog.style.position = 'fixed';
dialog.style.margin = '0';
dialog.style.left = sl + 'px';
dialog.style.top = st + 'px';
ev.preventDefault();
const move = e => {
dialog.style.left = (sl + e.clientX - sx) + 'px';
dialog.style.top = (st + e.clientY - sy) + 'px';
};
const up = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
/* Renderiza la barra de píldoras de sesiones minimizadas */
_renderPills() {
let bar = document.getElementById('labdom-pills-bar');
if (!bar) {
bar = document.createElement('div');
bar.id = 'labdom-pills-bar';
document.body.appendChild(bar);
}
bar.innerHTML = _sesiones.map((s, i) => {
const num = i + 1;
return `
<div class="labdom-pill" onclick="labDomicilio._restaurarSesion(${s.id})"
title="Restaurar — ${escLab(s.chatName)}${s.pacNombre ? ' · ' + escLab(s.pacNombre) : ''}">
<span class="labdom-pill-num">${num}</span>
<span class="labdom-pill-body">
<span class="labdom-pill-chat"><i class="fas fa-comments me-1" style="font-size:.65rem;opacity:.75"></i>${escLab(s.chatName)}</span>
${s.pacNombre ? `<span class="labdom-pill-pac"><i class="fas fa-user me-1" style="font-size:.6rem;opacity:.7"></i>${escLab(s.pacNombre)}</span>` : `<span class="labdom-pill-pac" style="opacity:.65">Sin paciente aún</span>`}
</span>
<button type="button" class="labdom-pill-close"
onclick="event.stopPropagation();labDomicilio._descartarSesion(${s.id})"
title="Descartar esta sesión"></button>
</div>`;
}).join('');
},
/* Restaura una sesión minimizada (y navega al chat si hace falta) */
async _restaurarSesion(sesId) {
const sesion = _sesiones.find(s => s.id === sesId);
if (!sesion) return;
// Quitar de la lista ANTES de abrir el modal
_sesiones = _sesiones.filter(s => s.id !== sesId);
labDomicilio._renderPills();
// Navegar al chat correspondiente si no es el activo
const chatUserId = (typeof chatApp !== 'undefined') ? (chatApp.currentUserId || 0) : 0;
if (sesion.chatUserId && sesion.chatUserId != chatUserId && typeof chatApp !== 'undefined') {
const conv = chatApp.conversations?.find(c => c.user_id == sesion.chatUserId);
if (conv) {
await chatApp.openConversation(conv.user_id, conv.name || sesion.chatName, conv.phone_number || sesion.chatPhone);
await new Promise(r => setTimeout(r, 280));
}
}
// Restaurar estado interno
_convId = sesion.convId;
_pacienteId = sesion.pacienteId;
_pacNombre = sesion.pacNombre;
_histDirs = sesion.histDirs || [];
_msgId = sesion.msgId;
_localFile = sesion.localFile;
// Mostrar formulario
mostrarFormulario();
labDomicilio._resetFull(); // limpia el DOM
// Restaurar campos del formulario
const f = sesion.fields || {};
['labdom-fecha','labdom-hora','labdom-tipo','labdom-direccion','labdom-barrio',
'labdom-ciudad','labdom-indicaciones','labdom-examenes','labdom-notas',
'labdom-seguro-nombre','labdom-autorizacion','labdom-copago',
'labdom-valor-domicilio','labdom-valor-copago','labdom-pac-fnac',
'labdom-pac-correo','labdom-pac-busq'].forEach(id => {
const el = document.getElementById(id);
if (el && f[id] !== undefined) el.value = f[id];
});
const tcVal = f['labdom-tipo-cliente'] || 'particular';
const tcEl = document.querySelector(`input[name="labdom-tipo-cliente"][value="${tcVal}"]`);
if (tcEl) { tcEl.checked = true; labDomicilio._toggleSeguro(); }
// Restaurar tarjeta de paciente si había uno seleccionado
if (sesion.cardVisible && sesion.pacienteId) {
const $ = id => document.getElementById(id);
$('labdom-pac-buscar')?.classList.add('d-none');
$('labdom-pac-card')?.classList.remove('d-none');
$('labdom-btn-cambiar-pac')?.classList.remove('d-none');
if ($('labdom-pac-nombre')) $('labdom-pac-nombre').textContent = sesion.pacInfo.nombre;
if ($('labdom-pac-info')) $('labdom-pac-info').textContent = sesion.pacInfo.info;
if ($('labdom-header-pac')) $('labdom-header-pac').textContent = sesion.pacNombre;
if ($('labdom-pac-tel')) $('labdom-pac-tel').textContent = sesion.pacInfo.tel;
if ($('labdom-pac-email')) $('labdom-pac-email').textContent = sesion.pacInfo.email;
if (sesion.pacInfo.eps && $('labdom-pac-eps')) {
$('labdom-pac-eps').textContent = sesion.pacInfo.eps;
if ($('labdom-pac-eps-row')) $('labdom-pac-eps-row').style.display = '';
}
if (sesion.pacInfo.tel || sesion.pacInfo.email) {
$('labdom-pac-contacto')?.classList.remove('d-none');
}
if (sesion.histDirs?.length) {
$('labdom-hist-cont')?.classList.remove('d-none');
if ($('labdom-hist-chips')) {
$('labdom-hist-chips').innerHTML = sesion.histDirs.map((d, i) =>
`<button type="button" class="addr-chip" onclick="labDomicilio.usarDireccion(${i})">${escLab(d.direccion)}${d.barrio ? ', '+escLab(d.barrio):''}</button>`
).join('');
}
}
}
labDomicilio._calcTotal();
// Abrir el modal
if (!_bsModal) _bsModal = new bootstrap.Modal('#modalAgendarDomicilio', { backdrop: true, keyboard: true });
_bsModal.show();
},
/* Descartar sesión con confirmación */
_descartarSesion(sesId) {
const s = _sesiones.find(x => x.id === sesId);
const nam = s ? `"${s.chatName}"` : 'este agendamiento';
if (!confirm(`¿Descartar el agendamiento en curso de ${nam}? Se perderán los datos.`)) return;
_sesiones = _sesiones.filter(x => x.id !== sesId);
labDomicilio._renderPills();
},
};
@@ -7537,5 +7643,8 @@ const citasModal = (() => {
</div>
</div>
<!-- Barra de sesiones minimizadas (agendamientos en paralelo) -->
<div id="labdom-pills-bar"></div>
</body>
</html>