Tomas: wizard inline para crear tipo de examen con tomas configurables
- Botón "Añadir tipo de examen…" abre wizard directo en el formulario - Paso 1: nombre del examen + cantidad de tomas (1-12) con +/- - Cada toma: label, tipo (minutos / hora fija), valor - Al guardar llama api/lab/save_tomas_config.php y recarga la página - Auto-selecciona el nuevo examen tras recargar (sessionStorage) - Restaura filtrado de $_gruposConfig por examen seleccionado Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
be55a087aa
commit
f0fb0c7492
+161
-79
@@ -355,6 +355,45 @@ function esc2(mixed $v): string {
|
||||
if (is_array($v)) return htmlspecialchars(implode(', ', $v), ENT_QUOTES);
|
||||
return htmlspecialchars((string)($v ?? ''), ENT_QUOTES);
|
||||
}
|
||||
|
||||
function _addExamWizardHtml(string $cid): string {
|
||||
return '
|
||||
<div class="mt-2" id="aew-wrap-' . $cid . '">
|
||||
<button type="button" class="btn btn-link btn-sm p-0 text-muted" onclick="aewOpen(\'' . $cid . '\')">
|
||||
<i class="fas fa-plus-circle me-1"></i>Añadir tipo de examen…
|
||||
</button>
|
||||
<div id="aew-box-' . $cid . '" class="d-none mt-2 border rounded p-3 bg-light" style="max-width:480px">
|
||||
<!-- Paso 1: nombre -->
|
||||
<div id="aew-s1-' . $cid . '">
|
||||
<div class="fw-semibold small mb-2"><i class="fas fa-vials me-1 text-primary"></i>Nuevo tipo de examen</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label small mb-1">Nombre del examen</label>
|
||||
<input type="text" id="aew-name-' . $cid . '" class="form-control form-control-sm"
|
||||
placeholder="Ej: Curva de Insulina" maxlength="80">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label small mb-1">¿Cuántas tomas?</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="aewAddToma(\'' . $cid . '\',-1)"><i class="fas fa-minus"></i></button>
|
||||
<span id="aew-count-' . $cid . '" class="fw-bold" style="min-width:20px;text-align:center">1</span>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="aewAddToma(\'' . $cid . '\',1)"><i class="fas fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="aew-tomas-' . $cid . '" class="mb-2"></div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-success btn-sm" id="aew-save-' . $cid . '"
|
||||
onclick="aewSave(\'' . $cid . '\')">
|
||||
<i class="fas fa-check me-1"></i>Crear examen
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="aewClose(\'' . $cid . '\')">
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
<div id="aew-msg-' . $cid . '" class="small mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@@ -647,11 +686,22 @@ function esc2(mixed $v): string {
|
||||
|
||||
<?php
|
||||
// ── Panel de configuración de ciclos (se muestra antes de iniciar el protocolo) ──
|
||||
// Con campo tipo texto libre, todos los grupos se muestran siempre
|
||||
$_examOptions = [];
|
||||
foreach ($esquema as $_oc) {
|
||||
if (($_oc['id'] ?? '') === '_c8j2g16') { $_examOptions = $_oc['options'] ?? []; break; }
|
||||
}
|
||||
$_examSel = (array)($datosCliente['_c8j2g16'] ?? []);
|
||||
$_gruposConfig = [];
|
||||
foreach ($_mpGroupsFull as $_gk => $_gsecs) {
|
||||
if (empty($_gsecs)) continue;
|
||||
$_gruposConfig[$_gk] = ['title' => $_gsecs[0]['display'] ?? $_gk, 'secs' => $_gsecs];
|
||||
$__parts = array_values(array_filter(explode('|', $_gk)));
|
||||
$__isCond = !empty(array_intersect($__parts, $_examOptions));
|
||||
$__matchSel = !empty(array_intersect($__parts, $_examSel));
|
||||
if (!$__isCond || $__matchSel) {
|
||||
$__title = implode(' / ', array_intersect($__parts, $_examSel));
|
||||
if (!$__title) $__title = $_gsecs[0]['display'] ?? $_gk;
|
||||
$_gruposConfig[$_gk] = ['title' => $__title, 'secs' => $_gsecs];
|
||||
}
|
||||
}
|
||||
// Mostrar panel solo si al menos un grupo tiene >= 2 tomas (hay algo que configurar).
|
||||
// Si todos los grupos tienen 1 sola toma, el JS auto-guarda la config sin preguntar.
|
||||
@@ -995,26 +1045,7 @@ function esc2(mixed $v): string {
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($cid === '_c8j2g16'): ?>
|
||||
<div class="mt-2" id="add-exam-wrap-<?= esc2($cid) ?>">
|
||||
<button type="button" class="btn btn-link btn-sm p-0 text-muted"
|
||||
onclick="toggleAddExam('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-plus-circle me-1"></i>Añadir tipo de examen…
|
||||
</button>
|
||||
<div id="add-exam-box-<?= esc2($cid) ?>" class="d-none mt-1 d-flex gap-2">
|
||||
<input type="text" id="add-exam-input-<?= esc2($cid) ?>"
|
||||
class="form-control form-control-sm" style="max-width:260px"
|
||||
placeholder="Nombre del tipo de examen">
|
||||
<button type="button" class="btn btn-success btn-sm"
|
||||
onclick="guardarNuevoExamen('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-check me-1"></i>Guardar
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
onclick="toggleAddExam('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="add-exam-msg-<?= esc2($cid) ?>" class="small mt-1"></div>
|
||||
</div>
|
||||
<?= _addExamWizardHtml($cid) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
@@ -1036,26 +1067,7 @@ function esc2(mixed $v): string {
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if ($cid === '_c8j2g16'): ?>
|
||||
<div class="mt-2" id="add-exam-wrap-<?= esc2($cid) ?>">
|
||||
<button type="button" class="btn btn-link btn-sm p-0 text-muted"
|
||||
onclick="toggleAddExam('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-plus-circle me-1"></i>Añadir tipo de examen…
|
||||
</button>
|
||||
<div id="add-exam-box-<?= esc2($cid) ?>" class="d-none mt-1 d-flex gap-2">
|
||||
<input type="text" id="add-exam-input-<?= esc2($cid) ?>"
|
||||
class="form-control form-control-sm" style="max-width:260px"
|
||||
placeholder="Nombre del tipo de examen">
|
||||
<button type="button" class="btn btn-success btn-sm"
|
||||
onclick="guardarNuevoExamen('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-check me-1"></i>Guardar
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
onclick="toggleAddExam('<?= esc2($cid) ?>')">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="add-exam-msg-<?= esc2($cid) ?>" class="small mt-1"></div>
|
||||
</div>
|
||||
<?= _addExamWizardHtml($cid) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
@@ -1327,57 +1339,127 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
})();
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
/* ── Añadir tipo de examen inline ── */
|
||||
function toggleAddExam(cid) {
|
||||
var box = document.getElementById('add-exam-box-' + cid);
|
||||
/* ── Wizard: añadir tipo de examen ── */
|
||||
var _aewState = {}; // {cid: {count, rows}}
|
||||
|
||||
function aewOpen(cid) {
|
||||
var box = document.getElementById('aew-box-' + cid);
|
||||
if (!box) return;
|
||||
box.classList.toggle('d-none');
|
||||
if (!box.classList.contains('d-none')) {
|
||||
var inp = document.getElementById('add-exam-input-' + cid);
|
||||
if (inp) inp.focus();
|
||||
}
|
||||
box.classList.remove('d-none');
|
||||
_aewState[cid] = { count: 1 };
|
||||
aewRenderTomas(cid);
|
||||
var ni = document.getElementById('aew-name-' + cid);
|
||||
if (ni) ni.focus();
|
||||
}
|
||||
function guardarNuevoExamen(cid) {
|
||||
var inp = document.getElementById('add-exam-input-' + cid);
|
||||
var msg = document.getElementById('add-exam-msg-' + cid);
|
||||
if (!inp) return;
|
||||
var name = inp.value.trim();
|
||||
if (!name) { if (msg) msg.innerHTML = '<span class="text-danger">Ingrese un nombre.</span>'; return; }
|
||||
function aewClose(cid) {
|
||||
var box = document.getElementById('aew-box-' + cid);
|
||||
if (box) box.classList.add('d-none');
|
||||
}
|
||||
function aewAddToma(cid, delta) {
|
||||
if (!_aewState[cid]) return;
|
||||
var n = (_aewState[cid].count || 1) + delta;
|
||||
if (n < 1) n = 1; if (n > 12) n = 12;
|
||||
_aewState[cid].count = n;
|
||||
var el = document.getElementById('aew-count-' + cid);
|
||||
if (el) el.textContent = n;
|
||||
aewRenderTomas(cid);
|
||||
}
|
||||
function aewRenderTomas(cid) {
|
||||
var n = (_aewState[cid] || {}).count || 1;
|
||||
var cnt = document.getElementById('aew-tomas-' + cid);
|
||||
if (!cnt) return;
|
||||
// Guardar valores actuales antes de re-renderizar
|
||||
var prev = [];
|
||||
cnt.querySelectorAll('.aew-row').forEach(function(r) {
|
||||
prev.push({
|
||||
lbl: r.querySelector('.aew-lbl') ? r.querySelector('.aew-lbl').value : '',
|
||||
tipo: r.querySelector('.aew-tipo') ? r.querySelector('.aew-tipo').value : 'minutos',
|
||||
val: r.querySelector('.aew-val') ? r.querySelector('.aew-val').value : '',
|
||||
});
|
||||
});
|
||||
var html = '';
|
||||
for (var i = 0; i < n; i++) {
|
||||
var p = prev[i] || {};
|
||||
var lbl = p.lbl || (i === 0 ? 'Minuto 0' : 'Minuto ' + (i * 30));
|
||||
var tipo = p.tipo || 'minutos';
|
||||
var val = p.val !== undefined ? p.val : (i === 0 ? '0' : String(i * 30));
|
||||
html += '<div class="aew-row border rounded p-2 mb-1 d-flex gap-2 align-items-end flex-wrap">'
|
||||
+ '<div style="flex:2;min-width:120px"><label class="form-label small mb-1">Label toma ' + (i+1) + '</label>'
|
||||
+ '<input class="form-control form-control-sm aew-lbl" value="' + lbl.replace(/"/g,'"') + '" placeholder="Ej: Minuto 0"></div>'
|
||||
+ '<div style="flex:1;min-width:100px"><label class="form-label small mb-1">Tipo</label>'
|
||||
+ '<select class="form-select form-select-sm aew-tipo" onchange="aewTipoChange(this)">'
|
||||
+ '<option value="minutos"' + (tipo==='minutos'?' selected':'') + '>Minutos</option>'
|
||||
+ '<option value="hora_fija"' + (tipo==='hora_fija'?' selected':'') + '>Hora fija</option>'
|
||||
+ '</select></div>'
|
||||
+ '<div style="flex:1;min-width:80px"><label class="form-label small mb-1 aew-val-lbl">' + (tipo==='hora_fija'?'HH:MM':'Minutos') + '</label>'
|
||||
+ '<input class="form-control form-control-sm aew-val" value="' + String(val).replace(/"/g,'"') + '" placeholder="' + (tipo==='hora_fija'?'08:00':'0') + '"></div>'
|
||||
+ '</div>';
|
||||
}
|
||||
cnt.innerHTML = html;
|
||||
}
|
||||
function aewTipoChange(sel) {
|
||||
var row = sel.closest('.aew-row');
|
||||
var lbl = row.querySelector('.aew-val-lbl');
|
||||
var inp = row.querySelector('.aew-val');
|
||||
if (sel.value === 'hora_fija') { lbl.textContent = 'HH:MM'; inp.placeholder = '08:00'; }
|
||||
else { lbl.textContent = 'Minutos'; inp.placeholder = '0'; }
|
||||
}
|
||||
function aewSave(cid) {
|
||||
var name = (document.getElementById('aew-name-' + cid) || {}).value;
|
||||
if (name) name = name.trim();
|
||||
var msg = document.getElementById('aew-msg-' + cid);
|
||||
function showErr(e) { if (msg) msg.innerHTML = '<span class="text-danger"><i class="fas fa-exclamation-circle me-1"></i>' + e + '</span>'; }
|
||||
if (!name) { showErr('Ingrese el nombre del examen.'); return; }
|
||||
|
||||
var tomas = [];
|
||||
document.querySelectorAll('#aew-tomas-' + cid + ' .aew-row').forEach(function(row) {
|
||||
tomas.push({
|
||||
label: (row.querySelector('.aew-lbl') || {}).value || '',
|
||||
tipo: (row.querySelector('.aew-tipo') || {}).value || 'minutos',
|
||||
valor: (row.querySelector('.aew-val') || {}).value || '0',
|
||||
});
|
||||
});
|
||||
if (!tomas.length) { showErr('Configure al menos una toma.'); return; }
|
||||
for (var i = 0; i < tomas.length; i++) {
|
||||
if (!tomas[i].label.trim()) { showErr('Ingrese el label de la toma ' + (i+1) + '.'); return; }
|
||||
}
|
||||
|
||||
var btn = document.getElementById('aew-save-' + cid);
|
||||
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando…'; }
|
||||
if (msg) msg.innerHTML = '';
|
||||
|
||||
var base = window.location.href.split('/ver_formulario_enviado.php')[0];
|
||||
fetch(base + '/api/lab/add_exam_option.php', {
|
||||
fetch(base + '/api/lab/save_tomas_config.php', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({exam_name: name})
|
||||
body: JSON.stringify({ action: 'add_exam', exam_name: name, tomas: tomas })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) {
|
||||
// Añadir radio button al grupo y seleccionarlo
|
||||
var wrap = document.querySelector('[data-campo-id="' + cid + '"]');
|
||||
if (wrap) {
|
||||
var div = document.createElement('div');
|
||||
div.className = 'form-check';
|
||||
div.innerHTML = '<input class="form-check-input" type="radio" name="' + cid + '" value="' + name.replace(/"/g, '"') + '" checked>'
|
||||
+ '<label class="form-check-label">' + name.replace(/</g, '<') + '</label>';
|
||||
// Insertar antes del bloque add-exam-wrap
|
||||
var addWrap = document.getElementById('add-exam-wrap-' + cid);
|
||||
wrap.insertBefore(div, addWrap);
|
||||
// Disparar evento change para condiciones de visibilidad
|
||||
div.querySelector('input').dispatchEvent(new Event('change', {bubbles: true}));
|
||||
}
|
||||
inp.value = '';
|
||||
if (msg) msg.innerHTML = '<span class="text-success"><i class="fas fa-check me-1"></i>Añadido.</span>';
|
||||
setTimeout(function() {
|
||||
toggleAddExam(cid);
|
||||
if (msg) msg.innerHTML = '';
|
||||
}, 1500);
|
||||
if (msg) msg.innerHTML = '<span class="text-success"><i class="fas fa-check me-1"></i>Examen creado. Recargando…</span>';
|
||||
// Guardar selección en sessionStorage para auto-seleccionar al recargar
|
||||
try { sessionStorage.setItem('_aew_preselect', name); } catch(e) {}
|
||||
setTimeout(function() { location.reload(); }, 900);
|
||||
} else {
|
||||
if (msg) msg.innerHTML = '<span class="text-danger">' + (d.error || 'Error') + '</span>';
|
||||
showErr(d.error || 'Error desconocido');
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-check me-1"></i>Crear examen'; }
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
if (msg) msg.innerHTML = '<span class="text-danger">Error de conexión.</span>';
|
||||
showErr('Error de conexión.');
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-check me-1"></i>Crear examen'; }
|
||||
});
|
||||
}
|
||||
// Auto-seleccionar el examen recién creado tras recargar
|
||||
(function() {
|
||||
try {
|
||||
var pre = sessionStorage.getItem('_aew_preselect');
|
||||
if (!pre) return;
|
||||
sessionStorage.removeItem('_aew_preselect');
|
||||
var radio = document.querySelector('input[type="radio"][value="' + pre.replace(/"/g, '\\"') + '"]');
|
||||
if (radio) { radio.checked = true; radio.dispatchEvent(new Event('change', {bubbles:true})); }
|
||||
} catch(e) {}
|
||||
})();
|
||||
|
||||
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
|
||||
window._mpSavedFirmas = {};
|
||||
|
||||
Reference in New Issue
Block a user