feat: tarjetas por toma con estados y barra de progreso
- Cada bloque separador+hora+observaciones+firma se envuelve en una toma-card con 3 estados visuales: signed (verde colapsable), active (color primario pulsante) y locked (gris opaco). - Barra de progreso con puntos y porcentaje encima de las tarjetas. - Al firmar: tarjeta activa pasa a signed (muestra hora anotada y miniatura colapsable), siguiente pasa a active con scroll automático. - Countdown de espera aparece en el header de la tarjeta bloqueada además del banner global. - Fix: variable 'minutos' indefinida en pct del countdown → 0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
86ec692028
commit
a2b46c464a
+205
-4
@@ -625,6 +625,46 @@ function _addExamWizardHtml(string $cid): string {
|
||||
border:none;border-radius:6px;padding:2px 10px;font-size:.78rem;
|
||||
cursor:pointer;white-space:nowrap; }
|
||||
.mp-exam-badge-btn:hover { opacity:.85; }
|
||||
|
||||
/* ── Tarjetas por toma ──────────────────────────────── */
|
||||
:root { --mp-color: <?= htmlspecialchars($docColor) ?>; }
|
||||
.toma-card { border-radius:10px;border:2px solid #e2e8f0;margin-bottom:.6rem;overflow:hidden;transition:border-color .2s,box-shadow .2s; }
|
||||
.toma-card--signed { border-color:#86efac; }
|
||||
.toma-card--active { border-color:var(--mp-color);box-shadow:0 0 0 3px color-mix(in srgb,var(--mp-color) 20%,transparent);animation:tc-pulse 2.5s ease-in-out infinite; }
|
||||
.toma-card--locked { border-color:#e2e8f0;opacity:.6; }
|
||||
@keyframes tc-pulse { 0%,100%{box-shadow:0 0 0 3px color-mix(in srgb,var(--mp-color) 15%,transparent)} 50%{box-shadow:0 0 0 6px color-mix(in srgb,var(--mp-color) 25%,transparent)} }
|
||||
.toma-card-hdr { display:flex;align-items:center;gap:.45rem;padding:.5rem .85rem;font-size:.82rem;flex-wrap:wrap; }
|
||||
.toma-card--signed .toma-card-hdr { background:#f0fdf4;cursor:pointer; }
|
||||
.toma-card--active .toma-card-hdr { background:var(--mp-color);color:#fff; }
|
||||
.toma-card--locked .toma-card-hdr { background:#f8fafc;color:#94a3b8; }
|
||||
.toma-card-num { width:1.4rem;height:1.4rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:800;flex-shrink:0;background:rgba(0,0,0,.1); }
|
||||
.toma-card--active .toma-card-num { background:rgba(255,255,255,.2);color:#fff; }
|
||||
.toma-card--signed .toma-card-num { background:#bbf7d0;color:#166534; }
|
||||
.toma-card--locked .toma-card-num { background:#e2e8f0;color:#94a3b8; }
|
||||
.toma-card-lbl { font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
||||
.toma-card-hora { font-size:.75rem;opacity:.75;white-space:nowrap; }
|
||||
.toma-card-badge { font-size:.7rem;padding:2px 8px;border-radius:20px;white-space:nowrap;flex-shrink:0; }
|
||||
.toma-badge-done { background:#dcfce7;color:#166534; }
|
||||
.toma-badge-active { background:rgba(255,255,255,.2);color:#fff;border:1px solid rgba(255,255,255,.35); }
|
||||
.toma-badge-wait { background:#f1f5f9;color:#64748b; }
|
||||
.toma-card-cd { font-size:.72rem;opacity:.9;margin-left:auto;white-space:nowrap; }
|
||||
.toma-card-body { padding:.7rem .85rem;display:flex;flex-direction:column;gap:.5rem; }
|
||||
.toma-card--signed .toma-card-body { display:none; }
|
||||
.toma-card--signed.tc-expanded .toma-card-body { display:flex; }
|
||||
.toma-card-body .esquema-sep { display:none !important; }
|
||||
.toma-card-body .campo-edit,.toma-card-body .campo-full { display:block !important; }
|
||||
|
||||
/* ── Barra de progreso ──────────────────────────────── */
|
||||
.mp-progress-wrap { margin-bottom:.75rem; }
|
||||
.mp-prog-hdr { display:flex;align-items:center;justify-content:space-between;margin-bottom:.35rem; }
|
||||
.mp-prog-text { font-size:.78rem;color:#475569; }
|
||||
.mp-steps { display:flex;gap:.3rem;align-items:center; }
|
||||
.mp-step { width:9px;height:9px;border-radius:50%;transition:all .3s; }
|
||||
.mp-step--done { background:#22c55e; }
|
||||
.mp-step--act { background:var(--mp-color);box-shadow:0 0 0 2px color-mix(in srgb,var(--mp-color) 30%,transparent);width:11px;height:11px; }
|
||||
.mp-step--wait { background:#e2e8f0; }
|
||||
.mp-prog-bar { height:5px;background:#e2e8f0;border-radius:3px;overflow:hidden; }
|
||||
.mp-prog-fill { height:100%;background:linear-gradient(90deg,var(--mp-color),#22c55e);border-radius:3px;transition:width .5s ease; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1609,9 +1649,10 @@ function aewSave(cid) {
|
||||
} catch(e) {}
|
||||
})();
|
||||
|
||||
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
|
||||
window._mpSavedFirmas = {};
|
||||
window._mpTomaData = {};
|
||||
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
|
||||
window._mpSavedFirmas = {};
|
||||
window._mpTomaData = {};
|
||||
window._mpPrimeraPendiente = <?= json_encode($_mpPrimeraPendiente) ?>;
|
||||
|
||||
function _mpAutoFillHora(firmaId) {
|
||||
var entry = window._muestrasMap && window._muestrasMap[firmaId];
|
||||
@@ -1622,8 +1663,159 @@ function _mpAutoFillHora(firmaId) {
|
||||
el.value = ('0'+now.getHours()).slice(-2) + ':' + ('0'+now.getMinutes()).slice(-2);
|
||||
}
|
||||
|
||||
// ── Tarjetas de toma ─────────────────────────────────────────────────
|
||||
function _mpBuildSepFirmaMap() {
|
||||
var m = {};
|
||||
for (var gk in _mpAllGroups) {
|
||||
(_mpAllGroups[gk] || []).forEach(function(s) {
|
||||
if (s.sep_id && s.firma && window._muestrasMap[s.firma]) m[s.sep_id] = s.firma;
|
||||
});
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
function _mpCardHdrHtml(fid, num, label, state) {
|
||||
var badge = state === 'signed'
|
||||
? '<span class="toma-card-badge toma-badge-done"><i class="fas fa-check-circle me-1"></i>Firmado</span>'
|
||||
: state === 'active'
|
||||
? '<span class="toma-card-badge toma-badge-active"><i class="fas fa-pen me-1"></i>Firmar ahora</span>'
|
||||
: '<span class="toma-card-badge toma-badge-wait"><i class="fas fa-hourglass-half me-1"></i>En espera</span>';
|
||||
var extra = state === 'locked' ? '<span class="toma-card-cd" id="tc-cd-' + fid + '"></span>' : '';
|
||||
var hora = state === 'signed' ? '<span class="toma-card-hora"></span>' : '';
|
||||
return '<span class="toma-card-num">' + num + '</span>'
|
||||
+ '<span class="toma-card-lbl">' + label + '</span>'
|
||||
+ hora + extra + badge;
|
||||
}
|
||||
|
||||
function _mpRenderCards() {
|
||||
if (!window._muestrasMap || !Object.keys(window._muestrasMap).length) return;
|
||||
var container = document.querySelector('.campos-grid');
|
||||
if (!container || container.querySelector('.toma-card')) return;
|
||||
|
||||
var sepToFirma = _mpBuildSepFirmaMap();
|
||||
if (!Object.keys(sepToFirma).length) return;
|
||||
|
||||
var nodes = Array.from(container.childNodes);
|
||||
// Segment by toma separators
|
||||
var segs = [], cur = null;
|
||||
nodes.forEach(function(node) {
|
||||
if (node.nodeType !== 1) return;
|
||||
var sid = node.getAttribute('data-campo-id') || '';
|
||||
if (sid && sepToFirma[sid]) { cur = { fid: sepToFirma[sid], nodes: [node] }; segs.push(cur); }
|
||||
else if (cur) { cur.nodes.push(node); }
|
||||
});
|
||||
if (!segs.length) return;
|
||||
|
||||
// Pre-toma nodes
|
||||
var firstNode = segs[0].nodes[0], preNodes = [];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i] === firstNode) break;
|
||||
if (nodes[i].nodeType === 1) preNodes.push(nodes[i]);
|
||||
}
|
||||
|
||||
container.innerHTML = '';
|
||||
preNodes.forEach(function(n) { container.appendChild(n); });
|
||||
|
||||
// Progress placeholder
|
||||
var progDiv = document.createElement('div');
|
||||
progDiv.id = 'mp-progress'; progDiv.className = 'mp-progress-wrap campo-full';
|
||||
container.appendChild(progDiv);
|
||||
|
||||
var signedCount = 0;
|
||||
segs.forEach(function(seg, idx) {
|
||||
var fid = seg.fid, entry = window._muestrasMap[fid] || {};
|
||||
// State: signed if firma-box img exists OR saved this session; active if first pending
|
||||
var hasImg = seg.nodes.some(function(n) { return n.querySelector && !!n.querySelector('.firma-box img'); });
|
||||
var isSigned = hasImg || !!((window._mpSavedFirmas || {})[fid]);
|
||||
if (!isSigned) {
|
||||
// Check if fpw widget exists at all (if not → signed pre-load, edge case)
|
||||
var hasFpw = seg.nodes.some(function(n) {
|
||||
return n.id === ('fpw-' + fid) || !!(n.querySelector && n.querySelector('#fpw-' + fid));
|
||||
});
|
||||
if (!hasFpw) isSigned = true;
|
||||
}
|
||||
var isActive = !isSigned && fid === window._mpPrimeraPendiente;
|
||||
var state = isSigned ? 'signed' : isActive ? 'active' : 'locked';
|
||||
if (isSigned) signedCount++;
|
||||
|
||||
var card = document.createElement('div');
|
||||
card.className = 'toma-card toma-card--' + state + ' campo-full';
|
||||
card.id = 'tc-' + fid;
|
||||
card.setAttribute('data-firma', fid);
|
||||
|
||||
var hdr = document.createElement('div');
|
||||
hdr.className = 'toma-card-hdr';
|
||||
hdr.innerHTML = _mpCardHdrHtml(fid, idx + 1, entry.label || '', state);
|
||||
if (isSigned) {
|
||||
// Fill hora in header
|
||||
var horaEl = null;
|
||||
if (entry.hora_campo) seg.nodes.forEach(function(n) { if (!horaEl) horaEl = n.querySelector ? n.querySelector('[name="' + entry.hora_campo + '"]') : null; });
|
||||
if (horaEl && horaEl.value) { var hs = hdr.querySelector('.toma-card-hora'); if (hs) hs.textContent = horaEl.value; }
|
||||
hdr.addEventListener('click', function() { card.classList.toggle('tc-expanded'); });
|
||||
}
|
||||
|
||||
var body = document.createElement('div');
|
||||
body.className = 'toma-card-body';
|
||||
seg.nodes.forEach(function(n) { body.appendChild(n); });
|
||||
|
||||
card.appendChild(hdr); card.appendChild(body);
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
_mpUpdateProgress(signedCount, segs.map(function(s) { return s.fid; }));
|
||||
}
|
||||
|
||||
function _mpUpdateProgress(signedCount, firmaIds) {
|
||||
var wrap = document.getElementById('mp-progress');
|
||||
if (!wrap) return;
|
||||
var total = firmaIds.length;
|
||||
var pct = total ? Math.round(signedCount / total * 100) : 0;
|
||||
var steps = firmaIds.map(function(fid) {
|
||||
var isSigned = !!((window._mpSavedFirmas || {})[fid])
|
||||
|| (function(){ var c=document.getElementById('tc-'+fid); return c&&c.classList.contains('toma-card--signed'); }());
|
||||
var isActive = fid === window._mpPrimeraPendiente && !isSigned;
|
||||
var cls = isSigned ? 'done' : isActive ? 'act' : 'wait';
|
||||
var lbl = ((window._muestrasMap || {})[fid] || {}).label || '';
|
||||
return '<span class="mp-step mp-step--' + cls + '" title="' + lbl.replace(/"/g,'"') + '"></span>';
|
||||
}).join('');
|
||||
wrap.innerHTML = '<div class="mp-prog-hdr"><span class="mp-prog-text">Toma <strong>' + signedCount + '</strong> de <strong>' + total + '</strong></span><div class="mp-steps">' + steps + '</div></div>'
|
||||
+ '<div class="mp-prog-bar"><div class="mp-prog-fill" style="width:' + pct + '%"></div></div>';
|
||||
}
|
||||
|
||||
function _mpRefreshCards(signedFid, nextFid) {
|
||||
window._mpPrimeraPendiente = nextFid || null;
|
||||
// Update signed card
|
||||
var sc = document.getElementById('tc-' + signedFid);
|
||||
if (sc) {
|
||||
sc.classList.remove('toma-card--active'); sc.classList.add('toma-card--signed');
|
||||
var shdr = sc.querySelector('.toma-card-hdr');
|
||||
if (shdr) {
|
||||
var se = window._muestrasMap[signedFid] || {}, snum = (sc.querySelector('.toma-card-num') || {}).textContent || '1';
|
||||
shdr.innerHTML = _mpCardHdrHtml(signedFid, parseInt(snum), se.label || '', 'signed');
|
||||
if (se.hora_campo) { var horaEl = sc.querySelector('[name="' + se.hora_campo + '"]'); if (horaEl && horaEl.value) { var hs = shdr.querySelector('.toma-card-hora'); if (hs) hs.textContent = horaEl.value; } }
|
||||
shdr.addEventListener('click', function() { sc.classList.toggle('tc-expanded'); });
|
||||
}
|
||||
}
|
||||
// Update next card
|
||||
if (nextFid) {
|
||||
var nc = document.getElementById('tc-' + nextFid);
|
||||
if (nc) {
|
||||
nc.classList.remove('toma-card--locked'); nc.classList.add('toma-card--active');
|
||||
var nhdr = nc.querySelector('.toma-card-hdr');
|
||||
if (nhdr) { var ne = window._muestrasMap[nextFid] || {}, nnum = (nc.querySelector('.toma-card-num') || {}).textContent || '1'; nhdr.innerHTML = _mpCardHdrHtml(nextFid, parseInt(nnum), ne.label || '', 'active'); }
|
||||
setTimeout(function() { nc.scrollIntoView({ behavior:'smooth', block:'center' }); }, 150);
|
||||
}
|
||||
}
|
||||
// Refresh progress
|
||||
var cards = document.querySelectorAll('.toma-card');
|
||||
var fids = [], signed = 0;
|
||||
cards.forEach(function(c) { var fid = c.getAttribute('data-firma'); if (fid) { fids.push(fid); if (c.classList.contains('toma-card--signed')) signed++; } });
|
||||
_mpUpdateProgress(signed, fids);
|
||||
}
|
||||
|
||||
// Auto-scroll y auto-fill hora a la primera firma pendiente al cargar
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
_mpRenderCards();
|
||||
setTimeout(function() {
|
||||
var fw = document.querySelector('.firma-pro-widget:not([style*="display:none"])');
|
||||
if (fw) {
|
||||
@@ -1758,6 +1950,9 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) {
|
||||
box.appendChild(img);
|
||||
widget.replaceWith(box);
|
||||
|
||||
// Actualizar tarjetas
|
||||
_mpRefreshCards(campoId, data.completado ? null : entry.next_firma_id);
|
||||
|
||||
if (data.completado) {
|
||||
// Toma completada: mostrar resumen de tomas y cerrar modal
|
||||
var cd = document.getElementById('mp-countdown');
|
||||
@@ -1827,9 +2022,13 @@ function _mpIniciarCountdown(targetMsOrMins, nextLabel, nextFirmaId) {
|
||||
// Notificar al padre que bloquee el cierre
|
||||
try { window.parent.postMessage({ type: 'tomaProgresivaIniciada' }, '*'); } catch(e) {}
|
||||
|
||||
var _tcCdEl = nextFirmaId ? document.getElementById('tc-cd-' + nextFirmaId) : null;
|
||||
|
||||
box._mpTick = setInterval(function() {
|
||||
var left = targetMs - Date.now();
|
||||
if (left <= 0) {
|
||||
if (_tcCdEl) _tcCdEl.textContent = '';
|
||||
|
||||
clearInterval(box._mpTick);
|
||||
box.style.background = '#dc3545';
|
||||
box.style.animation = 'mp-pulse 1s ease-in-out infinite';
|
||||
@@ -1851,7 +2050,7 @@ function _mpIniciarCountdown(targetMsOrMins, nextLabel, nextFirmaId) {
|
||||
}
|
||||
var m = Math.floor(left / 60000);
|
||||
var s = Math.floor((left % 60000) / 1000);
|
||||
var pct = Math.max(0, left / (minutos * 60000)) * 100;
|
||||
var pct = 0;
|
||||
box.style.background = pct < 15 ? '#f59e0b' : '#1565c0';
|
||||
box.style.animation = '';
|
||||
box.innerHTML = '<div style="font-size:.75rem;opacity:.85;margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">'
|
||||
@@ -1859,6 +2058,8 @@ function _mpIniciarCountdown(targetMsOrMins, nextLabel, nextFirmaId) {
|
||||
+ '<div style="font-size:2.4rem;font-weight:900;font-family:monospace;line-height:1;letter-spacing:.04em">'
|
||||
+ String(m).padStart(2,'0') + '<span style="opacity:.6;animation:mp-pulse .8s infinite">:</span>' + String(s).padStart(2,'0')
|
||||
+ '</div>';
|
||||
// Mini countdown en la tarjeta de la próxima toma
|
||||
if (_tcCdEl) _tcCdEl.textContent = '⏱ ' + String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user