Update lab_formularios.php
This commit is contained in:
+138
-12
@@ -118,23 +118,47 @@ $puedeEscribir = $esAdmin && hasModuleWrite('lab_formularios');
|
||||
|
||||
<!-- ── ENVÍOS ─────────────────────────────────────────────── -->
|
||||
<div id="panel-envios" style="display:none">
|
||||
<!-- Filtros -->
|
||||
<div class="row g-2 mb-3 align-items-end">
|
||||
<div class="col-sm-3">
|
||||
<!-- Filtros fila 1 -->
|
||||
<div class="row g-2 mb-2 align-items-end">
|
||||
<div class="col-sm-2">
|
||||
<label class="form-label small mb-1">Desde</label>
|
||||
<input type="date" id="env-f-desde" class="form-control form-control-sm" oninput="envios.filtrar()">
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<div class="col-sm-2">
|
||||
<label class="form-label small mb-1">Hasta</label>
|
||||
<input type="date" id="env-f-hasta" class="form-control form-control-sm" oninput="envios.filtrar()">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label small mb-1">Formulario</label>
|
||||
<select id="env-f-formulario" class="form-select form-select-sm" onchange="envios.filtrar()">
|
||||
<option value="">Todos los formularios</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<label class="form-label small mb-1">Estado</label>
|
||||
<select id="env-f-estado" class="form-select form-select-sm" onchange="envios.filtrar()">
|
||||
<option value="">Todos los estados</option>
|
||||
<option value="pendiente">🕐 Pendiente</option>
|
||||
<option value="completado">✅ Completado</option>
|
||||
<option value="firmado">✍️ Firmado</option>
|
||||
<option value="expirado">⏰ Expirado</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Filtros fila 2 -->
|
||||
<div class="row g-2 mb-3 align-items-end">
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label small mb-1">Paciente</label>
|
||||
<input type="text" id="env-f-paciente" class="form-control form-control-sm"
|
||||
placeholder="Buscar paciente…" oninput="envios.filtrar()">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-outline-secondary btn-sm w-100" onclick="envios.limpiarFiltros()">
|
||||
<div class="col-sm-auto ms-auto">
|
||||
<button class="btn btn-success btn-sm" onclick="envios.exportarExcel()">
|
||||
<i class="fas fa-file-excel me-1"></i>Exportar Excel
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-auto">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="envios.limpiarFiltros()">
|
||||
<i class="fas fa-times me-1"></i>Limpiar
|
||||
</button>
|
||||
</div>
|
||||
@@ -484,6 +508,7 @@ const envios = {
|
||||
const d = await r.json();
|
||||
this._data = d.data || [];
|
||||
this._pagina = 1;
|
||||
this._poblarSelectFormulario();
|
||||
this._aplicarFiltros();
|
||||
},
|
||||
|
||||
@@ -493,22 +518,46 @@ const envios = {
|
||||
},
|
||||
|
||||
limpiarFiltros() {
|
||||
$('env-f-desde').value = '';
|
||||
$('env-f-hasta').value = '';
|
||||
$('env-f-paciente').value = '';
|
||||
$('env-f-desde').value = '';
|
||||
$('env-f-hasta').value = '';
|
||||
$('env-f-paciente').value = '';
|
||||
$('env-f-formulario').value = '';
|
||||
$('env-f-estado').value = '';
|
||||
this.filtrar();
|
||||
},
|
||||
|
||||
_poblarSelectFormulario() {
|
||||
const sel = $('env-f-formulario');
|
||||
if (!sel) return;
|
||||
const valorActual = sel.value;
|
||||
sel.innerHTML = '<option value="">Todos los formularios</option>';
|
||||
const vistos = new Set();
|
||||
this._data.forEach(e => {
|
||||
if (e.formulario_id && !vistos.has(e.formulario_id)) {
|
||||
vistos.add(e.formulario_id);
|
||||
const opt = document.createElement('option');
|
||||
opt.value = e.formulario_id;
|
||||
opt.textContent = e.form_nombre || ('Formulario #' + e.formulario_id);
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
});
|
||||
if (valorActual) sel.value = valorActual;
|
||||
},
|
||||
|
||||
_aplicarFiltros() {
|
||||
const desde = $('env-f-desde').value;
|
||||
const hasta = $('env-f-hasta').value;
|
||||
const paciente = $('env-f-paciente').value.trim().toLowerCase();
|
||||
const desde = $('env-f-desde').value;
|
||||
const hasta = $('env-f-hasta').value;
|
||||
const paciente = $('env-f-paciente').value.trim().toLowerCase();
|
||||
const formId = $('env-f-formulario').value;
|
||||
const estadoFilt = $('env-f-estado').value;
|
||||
|
||||
this._filtrado = this._data.filter(e => {
|
||||
const fecha = (e.created_at || '').slice(0, 10);
|
||||
if (desde && fecha < desde) return false;
|
||||
if (hasta && fecha > hasta) return false;
|
||||
if (paciente && !(e.paciente_nombre || '').toLowerCase().includes(paciente)) return false;
|
||||
if (formId && String(e.formulario_id) !== String(formId)) return false;
|
||||
if (estadoFilt && e.estado !== estadoFilt) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -521,6 +570,83 @@ const envios = {
|
||||
this._render();
|
||||
},
|
||||
|
||||
exportarExcel() {
|
||||
const rows = this._filtrado;
|
||||
if (!rows.length) { showToast('Sin datos para exportar'); return; }
|
||||
|
||||
// Columnas fijas
|
||||
const fixedCols = ['Formulario','Categoría','Paciente','Estado','Fecha envío','Fecha completado'];
|
||||
|
||||
// Recolectar todas las columnas dinámicas (campos del esquema) en orden de aparición
|
||||
const fieldMap = new Map(); // label → id (primer visto)
|
||||
rows.forEach(e => {
|
||||
try {
|
||||
const esquema = JSON.parse(e.esquema || '[]');
|
||||
esquema.forEach(c => {
|
||||
if (!['separador','firma','firma_profesional','parrafo',
|
||||
'parrafo_inline','lista_marcable'].includes(c.tipo)
|
||||
&& c.id && c.label && !fieldMap.has(c.label)) {
|
||||
fieldMap.set(c.label, c.id);
|
||||
}
|
||||
});
|
||||
} catch(_) {}
|
||||
});
|
||||
|
||||
const fieldCols = [...fieldMap.entries()]; // [[label, id], ...]
|
||||
const allCols = [...fixedCols, ...fieldCols.map(([l]) => l)];
|
||||
|
||||
const csvRows = [allCols];
|
||||
|
||||
rows.forEach(e => {
|
||||
let cliente = {}, prefill = {};
|
||||
try { cliente = JSON.parse(e.datos_cliente || '{}'); } catch(_) {}
|
||||
try { prefill = JSON.parse(e.datos_prefilled || '{}'); } catch(_) {}
|
||||
const todos = { ...prefill, ...cliente };
|
||||
|
||||
// Para este envío construir mapa label → id
|
||||
const labelToId = new Map();
|
||||
try {
|
||||
JSON.parse(e.esquema || '[]').forEach(c => {
|
||||
if (c.id && c.label) labelToId.set(c.label, c.id);
|
||||
});
|
||||
} catch(_) {}
|
||||
|
||||
const fixed = [
|
||||
e.form_nombre || '',
|
||||
e.categoria || '',
|
||||
e.paciente_nombre || '',
|
||||
e.estado || '',
|
||||
(e.created_at || '').slice(0, 16).replace('T', ' '),
|
||||
(e.completado_en || '').slice(0, 16).replace('T', ' '),
|
||||
];
|
||||
|
||||
const campos = fieldCols.map(([label, defaultId]) => {
|
||||
const id = labelToId.get(label) || defaultId;
|
||||
const val = todos[id];
|
||||
if (val === undefined || val === null) return '';
|
||||
return Array.isArray(val) ? val.join('; ') : String(val);
|
||||
});
|
||||
|
||||
csvRows.push([...fixed, ...campos]);
|
||||
});
|
||||
|
||||
// Generar CSV con BOM UTF-8 para Excel
|
||||
const csv = '\uFEFF' + csvRows.map(row =>
|
||||
row.map(v => '"' + String(v).replace(/"/g, '""') + '"').join(',')
|
||||
).join('\r\n');
|
||||
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'envios_formularios_' + new Date().toISOString().slice(0, 10) + '.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
showToast('✅ Exportando ' + rows.length + ' envíos…');
|
||||
},
|
||||
|
||||
_render() {
|
||||
const tb = $('tbody-envios');
|
||||
const total = this._filtrado.length;
|
||||
|
||||
Reference in New Issue
Block a user