From 5b205742dc8bc00782e2a25af38fdaecc406ccf9 Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Wed, 21 Jan 2026 17:47:18 +0300 Subject: [PATCH] new save system --- public/app.js | 109 +++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/public/app.js b/public/app.js index ef5a198..b0ac082 100644 --- a/public/app.js +++ b/public/app.js @@ -221,9 +221,8 @@ async function selectTable(schema, tableName) { table = null; } - // ✅ Переменная для отслеживания несохраненных изменений - let pendingSave = null; - let saveTimeout = null; + // ✅ Map для отслеживания несохраненных строк (доступен глобально) + const dirtyRows = new Map(); // key = row position, value = {data, element, timeout} table = new Tabulator("#table", { selectableRows: true, @@ -293,73 +292,67 @@ async function selectTable(schema, tableName) { } }, - // ✅ При начале редактирования - cellEditing: function(cell) { - const row = cell.getRow(); - row.getElement().style.backgroundColor = '#fff9e6'; - }, - // ✅ При изменении ячейки cellEdited: function(cell) { const row = cell.getRow(); const rowData = row.getData(); + const rowPos = row.getPosition(); // Подсвечиваем измененную строку row.getElement().style.backgroundColor = '#fffae6'; - // Сохраняем данные для отложенного сохранения - pendingSave = { - cell: cell, - row: rowData, - rowElement: row - }; - - // ✅ Автосохранение через 1.5 секунды после последнего изменения - clearTimeout(saveTimeout); - saveTimeout = setTimeout(async () => { - if (pendingSave) { - await saveRowData(pendingSave.row, pendingSave.rowElement); - pendingSave = null; - } - }, 1500); - }, - - // ✅ При отмене редактирования (потеря фокуса, Esc) - cellEditCancelled: function(cell) { - // Если были несохраненные изменения - сохраняем их - if (pendingSave && pendingSave.cell === cell) { - clearTimeout(saveTimeout); - saveRowData(pendingSave.row, pendingSave.rowElement).then(() => { - pendingSave = null; - }); + // Отменяем предыдущий таймер для этой строки + if (dirtyRows.has(rowPos)) { + clearTimeout(dirtyRows.get(rowPos).timeout); } + + // ✅ Устанавливаем новый таймер на автосохранение (1 сек) + const timeout = setTimeout(async () => { + await saveRow(rowPos, rowData, row); + }, 1000); + + // Сохраняем информацию о грязной строке + dirtyRows.set(rowPos, { + data: rowData, + element: row, + timeout: timeout + }); + + console.log('📝 Изменено:', cell.getField(), '→', cell.getValue()); }, headerFilterLiveFilterDelay: 800 }); // ✅ Функция сохранения строки - async function saveRowData(rowData, rowElement) { + async function saveRow(rowPos, rowData, rowElement) { if (!currentSchema || !currentTable) return; + // Удаляем из списка несохраненных + dirtyRows.delete(rowPos); + try { + console.log('💾 Сохранение строки...', rowData); + await api('/api/table/update', 'POST', { schema: currentSchema, table: currentTable, row: rowData }); - // Убираем подсветку после успешного сохранения + // ✅ Зеленый фон = успешное сохранение if (rowElement && rowElement.getElement) { - rowElement.getElement().style.backgroundColor = '#e8f5e9'; // Зеленоватый фон + rowElement.getElement().style.backgroundColor = '#e8f5e9'; setTimeout(() => { - rowElement.getElement().style.backgroundColor = ''; - }, 1000); + if (rowElement.getElement) { + rowElement.getElement().style.backgroundColor = ''; + } + }, 1500); } - console.log('✓ Изменения сохранены:', rowData); + console.log('✅ Изменения сохранены'); } catch (err) { - console.error('Ошибка сохранения:', err); + console.error('❌ Ошибка сохранения:', err); // Красная подсветка при ошибке if (rowElement && rowElement.getElement) { @@ -370,14 +363,22 @@ async function selectTable(schema, tableName) { } } - // ✅ Обработчик Enter - сохраняет немедленно + // ✅ Обработчик Enter - сохраняет ВСЕ несохраненные строки немедленно enterHandler = async function(e) { - if (e.key === 'Enter' && pendingSave && currentSchema && currentTable) { + if (e.key === 'Enter' && dirtyRows.size > 0) { e.preventDefault(); - clearTimeout(saveTimeout); - await saveRowData(pendingSave.row, pendingSave.rowElement); - pendingSave = null; + console.log(`⏎ Enter: сохранение ${dirtyRows.size} измененных строк...`); + + // Сохраняем все грязные строки + const savePromises = []; + + dirtyRows.forEach((info, rowPos) => { + clearTimeout(info.timeout); + savePromises.push(saveRow(rowPos, info.data, info.element)); + }); + + await Promise.all(savePromises); // Снимаем фокус с ячейки if (document.activeElement) { @@ -387,9 +388,25 @@ async function selectTable(schema, tableName) { }; document.addEventListener('keydown', enterHandler); + + // ✅ Сохраняем несохраненные изменения при смене страницы/фильтрации + table.on("pageLoaded", async function() { + if (dirtyRows.size > 0) { + console.log('📄 Смена страницы: сохранение несохраненных изменений...'); + + const savePromises = []; + dirtyRows.forEach((info, rowPos) => { + clearTimeout(info.timeout); + savePromises.push(saveRow(rowPos, info.data, info.element)); + }); + + await Promise.all(savePromises); + } + }); } + document.getElementById('btnSelectAll').addEventListener('click', async () => { if (!currentSchema || !currentTable || !table) { alert('Сначала выберите таблицу');