feat: soporte multi-archivo en modal Agendar Domicilio
This commit is contained in:
+128
-114
@@ -6475,6 +6475,13 @@ if (!isUserLoggedIn()) {
|
||||
}
|
||||
#modalAgendarDomicilio .addr-chip:hover { background:#0d6efd; color:#fff; }
|
||||
#modalAgendarDomicilio .field-err { display:none; font-size:.8rem; color:#dc3545; margin-top:3px; }
|
||||
|
||||
/* ── Chips de archivos adjuntos (multi-orden) ── */
|
||||
.labdom-orden-chip { position:relative; width:72px; flex-shrink:0; text-align:center; }
|
||||
.labdom-orden-thumb { width:72px; height:72px; border:1px solid #dee2e6; border-radius:6px; background:#f8f9fa; display:flex; align-items:center; justify-content:center; overflow:hidden; }
|
||||
.labdom-orden-name { display:block; font-size:.62rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:72px; margin-top:3px; }
|
||||
.labdom-orden-del { position:absolute; top:-5px; right:-5px; width:18px; height:18px; border-radius:50%; background:#dc3545; color:#fff; border:none; font-size:.7rem; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0; }
|
||||
.labdom-orden-del:hover { background:#c82333; }
|
||||
#modalAgendarDomicilio .is-invalid ~ .field-err,
|
||||
#modalAgendarDomicilio .is-invalid + .field-err { display:block; }
|
||||
|
||||
@@ -6589,30 +6596,16 @@ if (!isUserLoggedIn()) {
|
||||
<!-- ── FORMULARIO ── -->
|
||||
<div id="labdom-form-panel" class="modal-body pb-2">
|
||||
|
||||
<!-- Orden médica (adjunta / agregar / cambiar) -->
|
||||
<!-- Orden médica (múltiples archivos) -->
|
||||
<div class="mb-3">
|
||||
<div id="labdom-img-prev" class="d-flex align-items-center gap-3 p-2 bg-light rounded border" style="display:none">
|
||||
<div id="labdom-file-thumb" class="flex-shrink-0">
|
||||
<img id="labdom-img-el" src="" class="rounded border" style="max-height:80px;max-width:110px;object-fit:cover">
|
||||
<div id="labdom-file-icon" class="rounded border bg-white text-center" style="width:80px;height:80px;display:none;align-items:center;justify-content:center"></div>
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<p class="mb-1 fw-semibold small text-secondary"><i class="fas fa-paperclip me-1"></i>Orden médica adjunta</p>
|
||||
<small id="labdom-file-name" class="text-muted d-block mb-2" style="max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></small>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="labDomicilio._cambiarOrden()" title="Cambiar archivo">
|
||||
<i class="fas fa-exchange-alt me-1"></i>Cambiar
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger py-0 px-2" onclick="labDomicilio._eliminarOrden()" title="Eliminar orden">
|
||||
<i class="fas fa-trash me-1"></i>Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||
<span class="fw-semibold small text-secondary"><i class="fas fa-paperclip me-1"></i>Órdenes médicas <span class="fw-normal text-muted">(opcional)</span></span>
|
||||
<button type="button" id="labdom-btn-add-orden" class="btn btn-sm btn-outline-primary py-0 px-2" onclick="labDomicilio._agregarOrden()">
|
||||
<i class="fas fa-plus me-1"></i>Agregar
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" id="labdom-btn-add-orden" class="btn btn-sm btn-outline-primary w-100 py-1" onclick="labDomicilio._cambiarOrden()" style="display:none">
|
||||
<i class="fas fa-paperclip me-1"></i>Adjuntar orden médica <span class="fw-normal text-muted">(opcional)</span>
|
||||
</button>
|
||||
<input type="file" id="labdom-orden-input" accept="image/*,.pdf,.doc,.docx" style="display:none">
|
||||
<div id="labdom-ordenes-list" class="d-flex flex-wrap gap-2 mt-1"></div>
|
||||
<input type="file" id="labdom-orden-input" accept="image/*,.pdf,.doc,.docx" multiple style="display:none">
|
||||
</div>
|
||||
|
||||
<!-- ── TIPO (particular/seguro) — PRIMERO y llamativo ── -->
|
||||
@@ -6869,8 +6862,10 @@ const labDomicilio = (() => {
|
||||
// Estado interno
|
||||
let _msgId = null;
|
||||
let _localFile = null;
|
||||
let _newOrderFile = null; // nuevo archivo de orden cargado desde el modal
|
||||
let _convId = null;
|
||||
let _newOrderFiles = []; // archivos nuevos seleccionados por el usuario
|
||||
let _newOrderDataUrls = []; // data-URLs para previsualizar (índice paralelo)
|
||||
let _localFiles = []; // filenames ya en servidor (del mensaje WA original)
|
||||
let _convId = null;
|
||||
let _pacienteId = null;
|
||||
let _isNewPaciente = false; // true = paciente nuevo (no existe en BD aún)
|
||||
let _pacNombre = '';
|
||||
@@ -6962,9 +6957,11 @@ const labDomicilio = (() => {
|
||||
|
||||
/* Abre el modal desde el botón del mensaje imagen */
|
||||
abrir(msgId, localFile, convId) {
|
||||
_msgId = msgId || null;
|
||||
_localFile = localFile|| null;
|
||||
_convId = convId ? parseInt(convId) : null;
|
||||
_msgId = msgId || null;
|
||||
_localFiles = localFile ? [localFile] : [];
|
||||
_newOrderFiles = [];
|
||||
_newOrderDataUrls = [];
|
||||
_convId = convId ? parseInt(convId) : null;
|
||||
// Guardar también el userId actual para usarContacto cuando convId no esté disponible
|
||||
if (!_convId && typeof chatApp !== 'undefined' && chatApp.currentUserId) {
|
||||
_convId = '__user:' + chatApp.currentUserId;
|
||||
@@ -6984,10 +6981,12 @@ const labDomicilio = (() => {
|
||||
|
||||
/* Reset completo (nombre paciente + todos los campos) */
|
||||
_resetFull() {
|
||||
_pacienteId = null;
|
||||
_pacNombre = '';
|
||||
_histDirs = [];
|
||||
_newOrderFile = null;
|
||||
_pacienteId = null;
|
||||
_pacNombre = '';
|
||||
_histDirs = [];
|
||||
_newOrderFiles = [];
|
||||
_newOrderDataUrls = [];
|
||||
_localFiles = [];
|
||||
$('labdom-pac-busq').value = '';
|
||||
$('labdom-pac-list').style.display = 'none';
|
||||
$('labdom-pac-buscar').classList.remove('d-none');
|
||||
@@ -7009,70 +7008,82 @@ const labDomicilio = (() => {
|
||||
labDomicilio._resetDomicilio();
|
||||
},
|
||||
|
||||
/* Actualiza la previa de orden médica en el modal (imagen o icono de archivo) */
|
||||
/* Renderiza la lista de archivos de la orden médica */
|
||||
_updateOrdenPreview() {
|
||||
const prevDiv = document.getElementById('labdom-img-prev');
|
||||
const addBtn = document.getElementById('labdom-btn-add-orden');
|
||||
const imgEl = document.getElementById('labdom-img-el');
|
||||
const iconEl = document.getElementById('labdom-file-icon');
|
||||
const nameEl = document.getElementById('labdom-file-name');
|
||||
if (!prevDiv) return;
|
||||
if (_localFile || _newOrderFile) {
|
||||
prevDiv.style.display = '';
|
||||
if (addBtn) addBtn.style.display = 'none';
|
||||
const fileName = _newOrderFile ? _newOrderFile.name : (_localFile || '');
|
||||
if (nameEl) nameEl.textContent = fileName;
|
||||
const isImage = /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(fileName);
|
||||
if (isImage) {
|
||||
if (imgEl) {
|
||||
imgEl.style.display = '';
|
||||
if (_newOrderFile) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = ev => { imgEl.src = ev.target.result; };
|
||||
reader.readAsDataURL(_newOrderFile);
|
||||
} else {
|
||||
imgEl.src = `uploads/media/${_localFile}`;
|
||||
}
|
||||
}
|
||||
if (iconEl) iconEl.style.display = 'none';
|
||||
} else {
|
||||
if (imgEl) imgEl.style.display = 'none';
|
||||
if (iconEl) {
|
||||
const ext = (fileName || '').split('.').pop().toLowerCase();
|
||||
const iconMap = { pdf: 'fas fa-file-pdf text-danger', doc: 'fas fa-file-word text-primary', docx: 'fas fa-file-word text-primary', mp4: 'fas fa-file-video text-warning', mkv: 'fas fa-file-video text-warning', mp3: 'fas fa-file-audio text-success', ogg: 'fas fa-file-audio text-success', webm: 'fas fa-file-video text-warning' };
|
||||
iconEl.innerHTML = `<i class="${iconMap[ext] || 'fas fa-file text-secondary'}" style="font-size:2rem"></i>`;
|
||||
iconEl.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prevDiv.style.display = 'none';
|
||||
if (addBtn) addBtn.style.display = '';
|
||||
}
|
||||
const list = $('labdom-ordenes-list');
|
||||
if (!list) return;
|
||||
|
||||
const iconCls = ext => {
|
||||
const m = { pdf:'fas fa-file-pdf text-danger', doc:'fas fa-file-word text-primary', docx:'fas fa-file-word text-primary', mp4:'fas fa-file-video text-warning', mkv:'fas fa-file-video text-warning', mp3:'fas fa-file-audio text-success', ogg:'fas fa-file-audio text-success', webm:'fas fa-file-video text-warning' };
|
||||
return m[ext] || 'fas fa-file text-secondary';
|
||||
};
|
||||
const isImgExt = ext => /^(jpg|jpeg|png|gif|webp|bmp)$/.test(ext);
|
||||
const chip = (label, thumb, delCall) => `<div class="labdom-orden-chip" title="${escLab(label)}">
|
||||
<div class="labdom-orden-thumb">${thumb}</div>
|
||||
<span class="labdom-orden-name">${escLab(label.length > 13 ? label.slice(0,10)+'\u2026' : label)}</span>
|
||||
<button type="button" class="labdom-orden-del" onclick="${delCall}" title="Eliminar">×</button>
|
||||
</div>`;
|
||||
|
||||
let html = '';
|
||||
_localFiles.forEach((fname, i) => {
|
||||
const ext = (fname||'').split('.').pop().toLowerCase();
|
||||
const thumb = isImgExt(ext)
|
||||
? `<img src="uploads/media/${escLab(fname)}" style="width:100%;height:100%;object-fit:cover;border-radius:4px">`
|
||||
: `<i class="${iconCls(ext)}" style="font-size:1.6rem"></i>`;
|
||||
html += chip(fname, thumb, `labDomicilio._eliminarOrden('local',${i})`);
|
||||
});
|
||||
_newOrderFiles.forEach((file, i) => {
|
||||
const ext = (file.name||'').split('.').pop().toLowerCase();
|
||||
const dUrl = _newOrderDataUrls[i] || '';
|
||||
const thumb = (isImgExt(ext) && dUrl)
|
||||
? `<img src="${dUrl}" style="width:100%;height:100%;object-fit:cover;border-radius:4px">`
|
||||
: `<i class="${iconCls(ext)}" style="font-size:1.6rem"></i>`;
|
||||
html += chip(file.name, thumb, `labDomicilio._eliminarOrden('new',${i})`);
|
||||
});
|
||||
list.innerHTML = html;
|
||||
},
|
||||
|
||||
/* Abre el selector de archivo para cambiar o agregar la orden médica */
|
||||
_cambiarOrden() {
|
||||
/* Abre el selector de archivo (soporta múltiples) y los agrega a la lista */
|
||||
_agregarOrden() {
|
||||
const inp = document.getElementById('labdom-orden-input');
|
||||
if (!inp) return;
|
||||
const handler = (e) => {
|
||||
inp.removeEventListener('change', handler);
|
||||
const file = e.target.files && e.target.files[0];
|
||||
if (!file) return;
|
||||
_newOrderFile = file;
|
||||
_localFile = null;
|
||||
labDomicilio._updateOrdenPreview();
|
||||
const files = e.target.files ? Array.from(e.target.files) : [];
|
||||
if (!files.length) return;
|
||||
let pending = files.length;
|
||||
files.forEach(file => {
|
||||
const ext = (file.name||'').split('.').pop().toLowerCase();
|
||||
const isImage = /^(jpg|jpeg|png|gif|webp|bmp)$/.test(ext);
|
||||
if (isImage) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = ev => {
|
||||
_newOrderFiles.push(file);
|
||||
_newOrderDataUrls.push(ev.target.result);
|
||||
if (--pending === 0) labDomicilio._updateOrdenPreview();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
_newOrderFiles.push(file);
|
||||
_newOrderDataUrls.push('');
|
||||
if (--pending === 0) labDomicilio._updateOrdenPreview();
|
||||
}
|
||||
});
|
||||
inp.value = '';
|
||||
};
|
||||
inp.addEventListener('change', handler);
|
||||
inp.click();
|
||||
},
|
||||
|
||||
/* Elimina la orden médica adjunta */
|
||||
_eliminarOrden() {
|
||||
if (!confirm('¿Eliminar la orden médica adjunta?')) return;
|
||||
_localFile = null;
|
||||
_newOrderFile = null;
|
||||
_msgId = null;
|
||||
/* Elimina un archivo específico de la lista */
|
||||
_eliminarOrden(type, idx) {
|
||||
if (type === 'local') {
|
||||
_localFiles.splice(idx, 1);
|
||||
if (!_localFiles.length && !_newOrderFiles.length) _msgId = null;
|
||||
} else {
|
||||
_newOrderFiles.splice(idx, 1);
|
||||
_newOrderDataUrls.splice(idx, 1);
|
||||
}
|
||||
labDomicilio._updateOrdenPreview();
|
||||
},
|
||||
|
||||
@@ -7410,42 +7421,43 @@ const labDomicilio = (() => {
|
||||
notas_admin: fv('labdom-notas') || null,
|
||||
};
|
||||
|
||||
// Subir nuevo archivo de orden si el usuario cambió o agregó uno desde el modal
|
||||
if (_newOrderFile) {
|
||||
// ── Subir archivos nuevos de orden ─────────────────────────────
|
||||
for (const file of _newOrderFiles) {
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('file', _newOrderFile);
|
||||
fd.append('file', file);
|
||||
const upRes = await fetch('api/lab/upload_orden.php', { method: 'POST', body: fd });
|
||||
const upData = await upRes.json();
|
||||
if (upData.success && upData.local_file) {
|
||||
_localFile = upData.local_file;
|
||||
_newOrderFile = null;
|
||||
}
|
||||
} catch (_e) { /* silencioso — no bloquea el flujo */ }
|
||||
if (upData.success && upData.local_file) _localFiles.push(upData.local_file);
|
||||
} catch (_e) { /* silencioso */ }
|
||||
}
|
||||
_newOrderFiles = [];
|
||||
_newOrderDataUrls = [];
|
||||
|
||||
// Si hay orden médica (original o recién subida), crear registro y enlazar
|
||||
if (_localFile) {
|
||||
// ── Crear registro de orden por cada archivo ───────────────────
|
||||
if (_localFiles.length) {
|
||||
try {
|
||||
const convIdNum = (_convId && !String(_convId).startsWith('__user:'))
|
||||
? parseInt(_convId) : null;
|
||||
const orRes = await fetch('api/lab/crear_desde_whatsapp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
paciente_id: _pacienteId,
|
||||
local_file: _localFile,
|
||||
media_message_id: _msgId || null,
|
||||
conversation_id: convIdNum,
|
||||
examenes_solicitados: datos.examenes_solicitados,
|
||||
estado: 'pendiente',
|
||||
}),
|
||||
});
|
||||
const orData = await orRes.json();
|
||||
if (orData.success && orData.orden_id) {
|
||||
datos.orden_id = orData.orden_id;
|
||||
for (let _fi = 0; _fi < _localFiles.length; _fi++) {
|
||||
const orRes = await fetch('api/lab/crear_desde_whatsapp.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
paciente_id: _pacienteId,
|
||||
local_file: _localFiles[_fi],
|
||||
media_message_id: _fi === 0 ? (_msgId || null) : null,
|
||||
conversation_id: convIdNum,
|
||||
examenes_solicitados: datos.examenes_solicitados,
|
||||
estado: 'pendiente',
|
||||
}),
|
||||
});
|
||||
const orData = await orRes.json();
|
||||
if (orData.success && orData.orden_id && _fi === 0) {
|
||||
datos.orden_id = orData.orden_id;
|
||||
}
|
||||
}
|
||||
} catch (_) { /* no bloquea si falla la creación de la orden */ }
|
||||
} catch (_) { /* no bloquea */ }
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -7598,9 +7610,10 @@ const labDomicilio = (() => {
|
||||
pacienteId: _pacienteId,
|
||||
pacNombre: _pacNombre,
|
||||
histDirs: [..._histDirs],
|
||||
msgId: _msgId,
|
||||
localFile: _localFile,
|
||||
newOrderFile: _newOrderFile,
|
||||
msgId: _msgId,
|
||||
localFiles: [..._localFiles],
|
||||
newOrderFiles: [..._newOrderFiles],
|
||||
newOrderDataUrls: [..._newOrderDataUrls],
|
||||
cardVisible: !document.getElementById('labdom-pac-card')?.classList.contains('d-none'),
|
||||
pacInfo: {
|
||||
nombre: document.getElementById('labdom-pac-nombre')?.value || '',
|
||||
@@ -7687,9 +7700,10 @@ const labDomicilio = (() => {
|
||||
_pacienteId = sesion.pacienteId;
|
||||
_pacNombre = sesion.pacNombre;
|
||||
_histDirs = sesion.histDirs || [];
|
||||
_msgId = sesion.msgId;
|
||||
_localFile = sesion.localFile;
|
||||
_newOrderFile = sesion.newOrderFile || null;
|
||||
_msgId = sesion.msgId;
|
||||
_localFiles = sesion.localFiles || (sesion.localFile ? [sesion.localFile] : []);
|
||||
_newOrderFiles = sesion.newOrderFiles || (sesion.newOrderFile ? [sesion.newOrderFile] : []);
|
||||
_newOrderDataUrls = sesion.newOrderDataUrls || [];
|
||||
|
||||
// Restaurar campos del formulario
|
||||
const f = sesion.fields || {};
|
||||
|
||||
Reference in New Issue
Block a user