';
// Загружаем доступные значения для каждого FK
const fkOptions = {};
for (const fk of fkFields) {
try {
const result = await api(
`/api/fk-values?schema=${encodeURIComponent(fk.ref_schema)}&` +
`table=${encodeURIComponent(fk.ref_table)}&` +
`column=${encodeURIComponent(fk.ref_column)}`
);
fkOptions[fk.name] = result.values || [];
} catch (err) {
console.error(`Ошибка загрузки FK для ${fk.name}:`, err);
fkOptions[fk.name] = [];
}
}
// Создаём поля для каждого FK
fkFields.forEach(fk => {
const options = fkOptions[fk.name];
html += `
`;
if (options.length > 0) {
// Если есть значения - показываем select
html += `';
} else {
// Если нет значений - показываем input
html += `
⚠️ Нет доступных значений в ${fk.ref_table}
`;
}
html += '
';
});
html += `
`;
dialog.innerHTML = html;
modal.appendChild(dialog);
document.body.appendChild(modal);
// Обработка кнопок
return new Promise((resolve) => {
document.getElementById('fkCancel').addEventListener('click', () => {
document.body.removeChild(modal);
resolve(null);
});
document.getElementById('fkSubmit').addEventListener('click', () => {
const values = {};
let allFilled = true;
for (const fk of fkFields) {
const input = document.getElementById(`fk_${fk.name}`);
const value = input.value.trim();
if (!value) {
alert(`Поле "${fk.name}" обязательно для заполнения!`);
allFilled = false;
break;
}
values[fk.name] = value;
}
if (allFilled) {
document.body.removeChild(modal);
resolve(values);
}
});
});
}
// Вспомогательная функция для экранирования HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
document.getElementById('btnUpdate').addEventListener('click', async () => {
if (!table) return;
let selected = table.getSelectedData();
let rowToSave = selected.length === 1 ? selected[0] : lastEditedRow;
if (!rowToSave) {
alert('Кликните по строке или отредактируйте ячейку');
return;
}
try {
const res = await api('/api/table/update', 'POST', {
schema: currentSchema, table: currentTable, row: rowToSave
});
lastEditedRow = null;
await table.replaceData();
alert(`✓ Обновлено строк: ${res.updated}`);
} catch (e) {
console.error(e);
alert('Ошибка обновления: ' + e.message);
}
});
document.getElementById('btnDelete').addEventListener('click', async () => {
if (!table || !currentSchema || !currentTable) return;
const selected = table.getSelectedData();
if (selected.length === 0) {
alert('Ничего не выбрано');
return;
}
if (!confirm(`Удалить ${selected.length} строк(и)?`)) return;
try {
for (const row of selected) {
await api('/api/table/delete', 'POST', { schema: currentSchema, table: currentTable, row });
}
await table.replaceData();
} catch (e) {
console.error(e);
alert('Ошибка удаления: ' + e.message);
}
});
// ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ CSV ==========
// Функция автоопределения разделителя
function detectDelimiter(text) {
const firstLine = text.split('\n')[0];
const semicolonCount = (firstLine.match(/;/g) || []).length;
const commaCount = (firstLine.match(/,/g) || []).length;
// Если есть точки с запятой и их больше, чем запятых, используем ;
if (semicolonCount > 0 && semicolonCount >= commaCount) {
return ';';
}
return ',';
}
// Улучшенный парсер CSV с поддержкой ; и , разделителей
function parseCSV(text) {
// Автоопределение разделителя
const delimiter = detectDelimiter(text);
console.log('Обнаружен разделитель CSV:', delimiter);
const lines = [];
let currentLine = [];
let currentField = '';
let inQuotes = false;
for (let i = 0; i < text.length; i++) {
const char = text[i];
const nextChar = text[i + 1];
if (char === '"') {
if (inQuotes && nextChar === '"') {
currentField += '"';
i++; // пропускаем следующую кавычку
} else {
inQuotes = !inQuotes;
}
} else if (char === delimiter && !inQuotes) {
currentLine.push(currentField.trim());
currentField = '';
} else if ((char === '\n' || char === '\r') && !inQuotes) {
if (char === '\r' && nextChar === '\n') {
i++; // пропускаем \n после \r
}
if (currentField || currentLine.length > 0) {
currentLine.push(currentField.trim());
lines.push(currentLine);
currentLine = [];
currentField = '';
}
} else {
currentField += char;
}
}
// Добавляем последнюю строку
if (currentField || currentLine.length > 0) {
currentLine.push(currentField.trim());
lines.push(currentLine);
}
return lines;
}
// ========== ИМПОРТ CSV ==========
document.getElementById('btnImportCSV').addEventListener('click', () => {
if (!currentSchema || !currentTable) {
alert('Сначала выберите таблицу');
return;
}
document.getElementById('csvFileInput').click();
});
document.getElementById('csvFileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
console.log('=== НАЧАЛО ИМПОРТА CSV ===');
console.log('Файл:', file.name, 'Размер:', file.size, 'байт');
try {
const text = await file.text();
console.log('Содержимое файла (первые 500 символов):\n', text.substring(0, 500));
console.log('Полное содержимое файла:\n', text);
const rows = parseCSV(text);
console.log('Всего строк после парсинга:', rows.length);
console.log('Все распарсенные строки:', rows);
if (rows.length === 0) {
alert('CSV файл пуст');
return;
}
const headers = rows[0];
console.log('Заголовки CSV:', headers);
const dataRows = rows.slice(1);
console.log('Строк данных (без заголовка):', dataRows.length);
if (dataRows.length === 0) {
alert('Нет данных для импорта (только заголовки)');
return;
}
// Преобразуем в массив объектов
const records = dataRows.map((row, idx) => {
const obj = {};
headers.forEach((header, i) => {
const value = row[i] || null;
obj[header] = value;
console.log(` Строка ${idx + 1}, столбец "${header}": "${value}"`);
});
return obj;
});
console.log('Финальные записи для отправки на сервер:', JSON.stringify(records, null, 2));
console.log('Количество записей:', records.length);
// Отправляем на сервер
console.log('Отправка запроса на сервер:', {
schema: currentSchema,
table: currentTable,
rowCount: records.length
});
const result = await api('/api/table/import-csv', 'POST', {
schema: currentSchema,
table: currentTable,
rows: records
});
console.log('Ответ от сервера:', result);
if (result.errorMessages && result.errorMessages.length > 0) {
console.error('Ошибки импорта:', result.errorMessages);
alert(`Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}\n\nОшибки:\n${result.errorMessages.join('\n')}`);
} else {
alert(`✓ Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}`);
}
await table.replaceData();
// Очищаем input
e.target.value = '';
} catch (err) {
console.error('=== КРИТИЧЕСКАЯ ОШИБКА ИМПОРТА ===');
console.error('Тип ошибки:', err.name);
console.error('Сообщение:', err.message);
console.error('Stack:', err.stack);
alert('Ошибка импорта: ' + err.message);
}
});
// ========== ЭКСПОРТ CSV ==========
document.getElementById('btnExportCSV').addEventListener('click', async () => {
if (!currentSchema || !currentTable || !table) {
alert('Сначала выберите таблицу');
return;
}
try {
// Получаем текущие фильтры и сортировку
const headerFilters = table.getHeaderFilters ? table.getHeaderFilters() : [];
const filters = (headerFilters || []).map(f => ({ field: f.field, value: f.value }));
const sorters = table.getSorters ? table.getSorters() : [];
const sort = (sorters && sorters.length > 0) ? { field: sorters[0].field, dir: sorters[0].dir } : null;
const result = await api('/api/table/export-csv', 'POST', {
schema: currentSchema,
table: currentTable,
filters: filters,
sort: sort,
columns: currentMeta.columns
});
// Создаем CSV текст
const csv = result.csv;
// Скачиваем файл
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }); // BOM для Excel
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `${currentTable}_${new Date().toISOString().slice(0,10)}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
console.log(`Экспортировано ${result.rowCount} строк`);
} catch (err) {
console.error('Ошибка экспорта:', err);
alert('Ошибка экспорта: ' + err.message);
}
});