diff --git a/public/app.js b/public/app.js index ca67cad..2e6d45b 100644 --- a/public/app.js +++ b/public/app.js @@ -282,33 +282,187 @@ async function selectTable(schema, tableName) { }; }, - // ✅ ИСПРАВЛЕНО: Простая и правильная обработка выделения +async function selectTable(schema, tableName) { + currentSchema = schema; + currentTable = tableName; + lastEditedRow = null; + selectedRowsData.clear(); + updateSelectionCounter(); + + if (enterHandler) { + document.removeEventListener('keydown', enterHandler); + enterHandler = null; + } + + currentMeta = await api( + `/api/table/meta?schema=${encodeURIComponent(schema)}&table=${encodeURIComponent(tableName)}` + ); + + const columns = [ + { + formatter: "rowSelection", + titleFormatter: "rowSelection", + hozAlign: "center", + headerSort: false, + width: 40, + cellClick: function(e, cell) { + cell.getRow().toggleSelect(); + } + }, + ...currentMeta.columns.map(col => ({ + title: col.COLUMN_NAME, + field: col.COLUMN_NAME, + editor: "input", + headerFilter: "input" + })) + ]; + + if (table) { + table.destroy(); + table = null; + } + + table = new Tabulator("#table", { + selectableRows: true, + columns: columns, + layout: "fitColumns", + resizableColumnFit: true, + + pagination: true, + paginationMode: "remote", + paginationSize: 50, + paginationSizeSelector: [25, 50, 100, 200], + + filterMode: "remote", + sortMode: "remote", + + ajaxURL: "/api/table/data", + ajaxConfig: "POST", + ajaxContentType: "json", + + ajaxParams: function () { + const headerFilters = this.getHeaderFilters ? this.getHeaderFilters() : []; + const filters = (headerFilters || []).map(f => ({ + field: f.field, + value: f.value + })).filter(f => f.value !== null && f.value !== ''); + + const sorters = this.getSorters ? this.getSorters() : []; + const sort = (sorters && sorters.length > 0) ? { + field: sorters[0].field, + dir: sorters[0].dir + } : null; + + return { + schema: currentSchema, + table: currentTable, + filters: filters, + sort: sort, + columns: currentMeta.columns, + page: this.getPage ? this.getPage() : 1, + pageSize: this.getPageSize ? this.getPageSize() : 50 + }; + }, + + ajaxResponse: function (url, params, response) { + return { + last_page: response.last_page || 1, + data: response.data || [] + }; + }, + + // ✅ ИСПРАВЛЕНО: Используем параметр data, который содержит ВСЕ выделенные строки rowSelectionChanged: function(data, rows) { - console.log('🔔 rowSelectionChanged:', { - dataLength: data.length, - rowsLength: rows.length, - selectedRowsDataSizeBefore: selectedRowsData.size + console.log('🔔 rowSelectionChanged called:', { + 'data (all selected)': data.length, + 'rows (changed)': rows.length, + 'selectedRowsData before': selectedRowsData.size }); - // Обрабатываем только изменившиеся строки - rows.forEach(row => { + // ВАЖНО: параметр data содержит массив данных ВСЕХ выделенных строк + // параметр rows содержит только Row объекты измененных строк + + // Получаем все выделенные строки напрямую из Tabulator + const allSelectedData = data; // это уже массив данных всех выделенных строк + + // Удаляем из selectedRowsData все строки текущей страницы + const currentPageRows = this.getRows(); + currentPageRows.forEach(row => { const rowData = row.getData(); const key = getRowKey(rowData); - const isSelected = row.isSelected(); - - console.log(` Row key: ${key}, isSelected: ${isSelected}`); - - if (isSelected) { - selectedRowsData.set(key, rowData); - } else { - selectedRowsData.delete(key); - } + selectedRowsData.delete(key); + console.log(` 🧹 Removed from selectedRowsData: ${key}`); }); - console.log(' ✅ selectedRowsData size after:', selectedRowsData.size); + // Добавляем все выделенные строки обратно + allSelectedData.forEach(rowData => { + const key = getRowKey(rowData); + selectedRowsData.set(key, rowData); + console.log(` ✅ Added to selectedRowsData: ${key}`); + }); + + console.log(` 📊 selectedRowsData after: ${selectedRowsData.size}`); updateSelectionCounter(); }, + dataLoaded: function(data) { + console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк'); + + if (selectedRowsData.size > 0) { + const rows = this.getRows(); + let restoredCount = 0; + + rows.forEach(row => { + const rowData = row.getData(); + const key = getRowKey(rowData); + + if (selectedRowsData.has(key)) { + row.select(); + restoredCount++; + } + }); + + console.log(' ✅ Восстановлено выделение для', restoredCount, 'строк на странице'); + } + }, + + cellEdited: function(cell) { + const row = cell.getRow(); + lastEditedRow = row.getData(); + row.getElement().style.backgroundColor = '#fffae6'; + }, + + headerFilterLiveFilterDelay: 800 + }); + + enterHandler = async function(e) { + if (e.key === 'Enter' && lastEditedRow && currentSchema && currentTable) { + e.preventDefault(); + + try { + const res = await api('/api/table/update', 'POST', { + schema: currentSchema, + table: currentTable, + row: lastEditedRow + }); + + lastEditedRow = null; + + table.getRows().forEach(r => { + r.getElement().style.backgroundColor = ''; + }); + + await table.replaceData(); + } catch (err) { + console.error('Ошибка сохранения:', err); + alert('Ошибка: ' + err.message); + } + } + }; + + document.addEventListener('keydown', enterHandler); +} + // После загрузки данных восстанавливаем выделение dataLoaded: function(data) { console.log('📄 dataLoaded, восстанавливаем выделение для', selectedRowsData.size, 'строк');