fix(escrituras): no interrumpir edición con el auto-refresh

Antes el poll de 15s llamaba render() y reconstruía toda la tbody,
borrando el modo edición en filas existentes (el guard isEditing solo
cubría filas nuevas).

Ahora:
* loadAll() detecta si hay edición en curso (isEditing || editingRows.size)
  y guarda los datos en allRows sin rerenderizar.
* Muestra "Edición en curso · refresco pausado" en el sync-status.
* maybeFlushRefresh() aplica los cambios remotos pendientes al salir
  del modo edición (cancelar o guardar la última fila).
* saveExistingRow ya llama a render() vía saveRow, así que limpia la
  bandera explícitamente.
This commit is contained in:
Soporte ZH Consultores
2026-05-11 19:40:32 +00:00
parent 81a909a1ec
commit da08e01f28
+24 -4
View File
@@ -561,9 +561,15 @@ async function loadAll(showOverlay=false) {
const rows = await apiFetch('GET','list'); const rows = await apiFetch('GET','list');
if (rows === null) return; if (rows === null) return;
allRows = rows || []; allRows = rows || [];
setSyncState('ok','Sincronizado'); // No re-renderizar si el usuario está editando (fila nueva o existente).
// No re-renderizar si el usuario está escribiendo una fila nueva // Los datos quedan en memoria; se aplicarán al salir de edición.
if (!isEditing) render(); if (isEditing || editingRows.size > 0) {
pendingRefresh = true;
setSyncState('ok','Edición en curso · refresco pausado');
} else {
render();
setSyncState('ok','Sincronizado');
}
} catch(e) { } catch(e) {
setSyncState('error','Sin conexión'); setSyncState('error','Sin conexión');
toast('Error al cargar datos: '+e.message,'error'); toast('Error al cargar datos: '+e.message,'error');
@@ -572,6 +578,15 @@ async function loadAll(showOverlay=false) {
} }
} }
// Si el poll trajo datos mientras se estaba editando, aplicarlos al terminar
function maybeFlushRefresh() {
if (pendingRefresh && !isEditing && editingRows.size === 0) {
pendingRefresh = false;
render();
setSyncState('ok','Sincronizado');
}
}
async function saveRow(row) { async function saveRow(row) {
try { try {
setSyncState('syncing','Guardando…'); setSyncState('syncing','Guardando…');
@@ -805,7 +820,8 @@ function onKeydown(e) {
}); });
// ── ROW ACTIONS ────────────────────────────────────────────────────────── // ── ROW ACTIONS ──────────────────────────────────────────────────────────
let editingRows = new Set(); // track which rows are in edit mode let editingRows = new Set(); // track which rows are in edit mode
let pendingRefresh = false; // poll trajo datos mientras alguien editaba
function toggleEditRow(id, btn) { function toggleEditRow(id, btn) {
const tr = document.querySelector(`tr[data-id="${id}"]`); const tr = document.querySelector(`tr[data-id="${id}"]`);
@@ -830,6 +846,8 @@ function toggleEditRow(id, btn) {
btn.title = 'Editar fila'; btn.title = 'Editar fila';
btn.style.background = ''; btn.style.background = '';
btn.style.borderColor = ''; btn.style.borderColor = '';
// Aplicar cambios remotos pendientes si los hubo
maybeFlushRefresh();
} else { } else {
// Enter edit mode // Enter edit mode
editingRows.add(id); editingRows.add(id);
@@ -866,6 +884,8 @@ function saveExistingRow(id) {
tr.classList.remove('editing-row'); tr.classList.remove('editing-row');
const saveBtn = tr.querySelector('.ra-save'); const saveBtn = tr.querySelector('.ra-save');
if (saveBtn) saveBtn.classList.remove('pending'); if (saveBtn) saveBtn.classList.remove('pending');
// saveRow llama a render() al terminar, lo que aplica también cambios remotos
pendingRefresh = false;
saveRow(Object.assign({}, base, fields, { id })); saveRow(Object.assign({}, base, fields, { id }));
} }