fix(tramites): buscador conserva el foco al tipear
Cada tecla en el input de búsqueda disparaba renderPanel() que reescribía panel.innerHTML — incluyendo el input mismo — así que el navegador descartaba el foco (y la posición del cursor). Los usuarios tenían que hacer clic después de cada letra. Cambios: - Nuevo filterState por depto en memoria (no lee del DOM al filtrar) - renderPanel() reconstruye todo solo cuando cambia el conjunto de datos (carga inicial, guardar, borrar, cambio de tab) - Nuevo renderPanelBody() que solo repinta contadores + tbody - Los handlers de search/status/abogado/esfera actualizan filterState y llaman renderPanelBody, dejando el toolbar intacto Efecto: al tipear en el buscador, el input mantiene foco y cursor.
This commit is contained in:
+87
-27
@@ -372,49 +372,64 @@ function checkDeepLink(){
|
||||
|
||||
function renderAll(){['adm','corp','mig','lit'].forEach(renderPanel);}
|
||||
|
||||
function renderPanel(dept){
|
||||
const panel=document.getElementById('panel-'+dept);
|
||||
const rows=data[dept];
|
||||
const statuses=STATUS_MAP[dept];
|
||||
const search=(panel.querySelector('.search-input')||{value:''}).value.toLowerCase();
|
||||
const fStatus=(panel.querySelector('.f-status')||{value:''}).value;
|
||||
const fAbog=(panel.querySelector('.f-abog')||{value:''}).value;
|
||||
const fEsfera=(panel.querySelector('.f-esfera')||{value:''}).value;
|
||||
// Estado de filtros por depto (persiste entre re-renders — no vive en el DOM,
|
||||
// para que actualizar el <tbody> no destruya el input de búsqueda).
|
||||
const filterState = {
|
||||
adm: { search:'', status:'', abog:'', esfera:'' },
|
||||
corp: { search:'', status:'', abog:'', esfera:'' },
|
||||
mig: { search:'', status:'', abog:'', esfera:'' },
|
||||
lit: { search:'', status:'', abog:'', esfera:'' },
|
||||
};
|
||||
|
||||
let filtered=rows.filter(r=>{
|
||||
const txt=((r.nombre||r.tipoTramite||r.tipoProceso||'')+(r.cliente||'')+(r.abogado||'')+(r.id||'')).toLowerCase();
|
||||
if(search && !txt.includes(search)) return false;
|
||||
if(fStatus && r.estatus!==fStatus) return false;
|
||||
if(fAbog && r.abogado!==fAbog) return false;
|
||||
if(dept==='lit' && fEsfera && r.esfera!==fEsfera) return false;
|
||||
function computeFiltered(dept){
|
||||
const rows = data[dept];
|
||||
const f = filterState[dept];
|
||||
const q = f.search.toLowerCase();
|
||||
const filtered = rows.filter(r => {
|
||||
const txt = ((r.nombre||r.tipoTramite||r.tipoProceso||'')+(r.cliente||'')+(r.abogado||'')+(r.id||'')).toLowerCase();
|
||||
if (q && !txt.includes(q)) return false;
|
||||
if (f.status && r.estatus !== f.status) return false;
|
||||
if (f.abog && r.abogado !== f.abog) return false;
|
||||
if (dept === 'lit' && f.esfera && r.esfera !== f.esfera) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const total=rows.length;
|
||||
const activos=rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length;
|
||||
const culminados=rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length;
|
||||
|
||||
window.__tramFiltered = window.__tramFiltered || {};
|
||||
window.__tramFiltered[dept] = filtered;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// Reconstruye toda la pestaña. Se usa solo cuando cambia el conjunto de datos
|
||||
// (carga inicial, guardar/borrar, cambio de tab). NO se llama al teclear.
|
||||
function renderPanel(dept){
|
||||
const panel = document.getElementById('panel-'+dept);
|
||||
const rows = data[dept];
|
||||
const statuses = STATUS_MAP[dept];
|
||||
const f = filterState[dept];
|
||||
|
||||
const total = rows.length;
|
||||
const activos = rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length;
|
||||
const culminados = rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length;
|
||||
const filtered = computeFiltered(dept);
|
||||
|
||||
panel.innerHTML=`
|
||||
<div class="counts">
|
||||
<div class="counts" id="counts-${dept}">
|
||||
<div class="count-card"><b>${total}</b>Total</div>
|
||||
<div class="count-card"><b>${activos}</b>Activos</div>
|
||||
<div class="count-card"><b>${culminados}</b>Cerrados</div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input class="search-input" placeholder="Buscar..." oninput="renderPanel('${dept}')" value="${esc(search)}">
|
||||
<select class="f-status" onchange="renderPanel('${dept}')">
|
||||
<input class="search-input" placeholder="Buscar..." value="${esc(f.search)}">
|
||||
<select class="f-status">
|
||||
<option value="">— Estatus —</option>
|
||||
${statuses.map(s=>`<option${fStatus===s?' selected':''}>${s}</option>`).join('')}
|
||||
${statuses.map(s=>`<option${f.status===s?' selected':''}>${s}</option>`).join('')}
|
||||
</select>
|
||||
<select class="f-abog" onchange="renderPanel('${dept}')">
|
||||
<select class="f-abog">
|
||||
<option value="">— Abogado —</option>
|
||||
${ABOGADOS.map(a=>`<option${fAbog===a?' selected':''}>${a}</option>`).join('')}
|
||||
${ABOGADOS.map(a=>`<option${f.abog===a?' selected':''}>${a}</option>`).join('')}
|
||||
</select>
|
||||
${dept==='lit'?`<select class="f-esfera" onchange="renderPanel('${dept}')">
|
||||
${dept==='lit'?`<select class="f-esfera">
|
||||
<option value="">— Esfera —</option>
|
||||
<option${fEsfera==='Civil'?' selected':''}>Civil</option><option${fEsfera==='Penal'?' selected':''}>Penal</option>
|
||||
<option${f.esfera==='Civil'?' selected':''}>Civil</option><option${f.esfera==='Penal'?' selected':''}>Penal</option>
|
||||
</select>`:''}
|
||||
${CAN_CREATE ? `<button class="btn btn-primary" onclick="openModal('${dept}',null)">+ Nuevo</button>` : ''}
|
||||
${DonnaExport.buttonHtml('export-wrap-'+dept)}
|
||||
@@ -431,6 +446,51 @@ function renderPanel(dept){
|
||||
onExcel: () => tramExportExcel(dept),
|
||||
onPDF: () => tramExportPDF(dept),
|
||||
});
|
||||
|
||||
// Handlers de filtro: al cambiar solo repintan la tabla, no el toolbar.
|
||||
// Así el input de búsqueda conserva foco y posición del cursor al tipear.
|
||||
const searchEl = panel.querySelector('.search-input');
|
||||
if (searchEl) searchEl.addEventListener('input', e => {
|
||||
filterState[dept].search = e.target.value;
|
||||
renderPanelBody(dept);
|
||||
});
|
||||
const statusEl = panel.querySelector('.f-status');
|
||||
if (statusEl) statusEl.addEventListener('change', e => {
|
||||
filterState[dept].status = e.target.value;
|
||||
renderPanelBody(dept);
|
||||
});
|
||||
const abogEl = panel.querySelector('.f-abog');
|
||||
if (abogEl) abogEl.addEventListener('change', e => {
|
||||
filterState[dept].abog = e.target.value;
|
||||
renderPanelBody(dept);
|
||||
});
|
||||
const esferaEl = panel.querySelector('.f-esfera');
|
||||
if (esferaEl) esferaEl.addEventListener('change', e => {
|
||||
filterState[dept].esfera = e.target.value;
|
||||
renderPanelBody(dept);
|
||||
});
|
||||
}
|
||||
|
||||
// Solo actualiza contadores y tbody — deja el toolbar intacto.
|
||||
function renderPanelBody(dept){
|
||||
const rows = data[dept];
|
||||
const filtered = computeFiltered(dept);
|
||||
const counts = document.getElementById('counts-'+dept);
|
||||
if (counts) {
|
||||
const total = rows.length;
|
||||
const activos = rows.filter(r=>r.estatus!=='Culminado'&&r.estatus!=='Facturado').length;
|
||||
const culminados = rows.filter(r=>r.estatus==='Culminado'||r.estatus==='Facturado').length;
|
||||
counts.innerHTML =
|
||||
`<div class="count-card"><b>${total}</b>Total</div>`+
|
||||
`<div class="count-card"><b>${activos}</b>Activos</div>`+
|
||||
`<div class="count-card"><b>${culminados}</b>Cerrados</div>`;
|
||||
}
|
||||
const tbody = document.getElementById('tbody-'+dept);
|
||||
if (tbody) {
|
||||
tbody.innerHTML = filtered.length
|
||||
? filtered.map(r=>buildRow(dept,r)).join('')
|
||||
: `<tr><td colspan="20"><div class="empty-state">Sin registros. Haz clic en <b>+ Nuevo</b> para agregar.</div></td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── EXPORT (Trámites) ────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user