diff --git a/public/app.js b/public/app.js index 04cc20c..8f5cfe9 100644 --- a/public/app.js +++ b/public/app.js @@ -580,12 +580,17 @@ 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('Содержимое файла:', text.substring(0, 200)); // первые 200 символов для отладки + console.log('Содержимое файла (первые 500 символов):\n', text.substring(0, 500)); + console.log('Полное содержимое файла:\n', text); const rows = parseCSV(text); - console.log('Распарсенные строки:', rows); + console.log('Всего строк после парсинга:', rows.length); + console.log('Все распарсенные строки:', rows); if (rows.length === 0) { alert('CSV файл пуст'); @@ -593,9 +598,10 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) => } const headers = rows[0]; - console.log('Заголовки:', headers); + console.log('Заголовки CSV:', headers); const dataRows = rows.slice(1); + console.log('Строк данных (без заголовка):', dataRows.length); if (dataRows.length === 0) { alert('Нет данных для импорта (только заголовки)'); @@ -603,15 +609,25 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) => } // Преобразуем в массив объектов - const records = dataRows.map(row => { + const records = dataRows.map((row, idx) => { const obj = {}; headers.forEach((header, i) => { - obj[header] = row[i] || null; + const value = row[i] || null; + obj[header] = value; + console.log(` Строка ${idx + 1}, столбец "${header}": "${value}"`); }); return obj; }); - console.log('Импортируем записи:', records); + 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, @@ -619,17 +635,29 @@ document.getElementById('csvFileInput').addEventListener('change', async (e) => rows: records }); - alert(`✓ Импортировано строк: ${result.inserted}\nОшибок: ${result.errors}`); + 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('Ошибка импорта:', 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) { diff --git a/public/index.php b/public/index.php index 3f76b51..2b8f8fa 100644 --- a/public/index.php +++ b/public/index.php @@ -172,22 +172,63 @@ $app->post('/api/table/delete', function (Request $request, Response $response) // API: импорт CSV (массовая вставка) $app->post('/api/table/import-csv', function (Request $request, Response $response) use ($container) { - $payload = json_decode((string)$request->getBody(), true); + error_log("\n========== ЗАПРОС НА ИМПОРТ CSV =========="); + + $body = (string)$request->getBody(); + error_log("Тело запроса (первые 1000 символов): " . substr($body, 0, 1000)); + + $payload = json_decode($body, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + error_log("ОШИБКА JSON: " . json_last_error_msg()); + $response->getBody()->write(json_encode(['error' => 'Invalid JSON: ' . json_last_error_msg()])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + error_log("Декодированный payload: " . json_encode($payload, JSON_PRETTY_PRINT)); + $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); - $schema = $payload['schema']; - $table = $payload['table']; + $schema = $payload['schema'] ?? ''; + $table = $payload['table'] ?? ''; $rows = $payload['rows'] ?? []; - $metaArr = $meta->getTableMeta($schema, $table); - - $result = $ds->insertMultipleRows($schema, $table, $rows, $metaArr['columns']); - $response->getBody()->write(json_encode($result)); - return $response->withHeader('Content-Type', 'application/json'); + error_log("Schema: $schema"); + error_log("Table: $table"); + error_log("Количество строк: " . count($rows)); + + if (empty($schema) || empty($table)) { + error_log("ОШИБКА: пустая schema или table"); + $response->getBody()->write(json_encode(['error' => 'Schema and table are required'])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + try { + $metaArr = $meta->getTableMeta($schema, $table); + error_log("Метаданные таблицы получены, столбцов: " . count($metaArr['columns'])); + + $result = $ds->insertMultipleRows($schema, $table, $rows, $metaArr['columns']); + + error_log("Результат импорта: " . json_encode($result)); + + $response->getBody()->write(json_encode($result)); + return $response->withHeader('Content-Type', 'application/json'); + } catch (\Exception $e) { + error_log("КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage()); + error_log("Stack trace: " . $e->getTraceAsString()); + + $response->getBody()->write(json_encode([ + 'error' => $e->getMessage(), + 'inserted' => 0, + 'errors' => 1 + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } }); + // API: экспорт CSV $app->post('/api/table/export-csv', function (Request $request, Response $response) use ($container) { $payload = json_decode((string)$request->getBody(), true); diff --git a/src/DataService.php b/src/DataService.php index c9b9629..642203b 100644 --- a/src/DataService.php +++ b/src/DataService.php @@ -241,6 +241,11 @@ class DataService public function insertMultipleRows(string $schema, string $table, array $rows, array $columns): array { + error_log("=== НАЧАЛО ИМПОРТА CSV ==="); + error_log("Schema: $schema, Table: $table"); + error_log("Количество строк для импорта: " . count($rows)); + error_log("Структура первой строки: " . json_encode($rows[0] ?? [])); + if (empty($rows)) { return ['inserted' => 0, 'errors' => 0, 'message' => 'No rows provided']; } @@ -255,6 +260,8 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a $name = $c['COLUMN_NAME']; $extra = $c['EXTRA'] ?? ''; + error_log("Столбец: $name, Extra: $extra, Nullable: " . ($c['IS_NULLABLE'] ? 'YES' : 'NO') . ", Default: " . ($c['COLUMN_DEFAULT'] ?? 'NULL')); + // Пропускаем только auto_increment поля if (!str_contains($extra, 'auto_increment')) { $validColumns[$name] = [ @@ -262,13 +269,20 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a 'has_default' => !empty($c['COLUMN_DEFAULT']) || $c['COLUMN_DEFAULT'] === '0', 'default' => $c['COLUMN_DEFAULT'] ?? null ]; + } else { + error_log("Пропускаем auto_increment столбец: $name"); } } + + error_log("Валидные столбцы для импорта: " . json_encode(array_keys($validColumns))); $this->pdo->beginTransaction(); try { foreach ($rows as $index => $row) { + error_log("\n--- Обработка строки " . ($index + 1) . " ---"); + error_log("Данные строки: " . json_encode($row)); + try { $insertCols = []; $placeholders = []; @@ -281,22 +295,28 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a // Если столбец есть в CSV строке if (array_key_exists($name, $row)) { $value = $row[$name]; + error_log(" Столбец '$name' найден в CSV: '$value'"); // ✅ Обрабатываем "NULL" как NULL if (in_array($value, ['NULL', 'null', ''], true)) { + error_log(" Преобразуем '$value' в NULL"); $value = null; } } else { + error_log(" Столбец '$name' НЕ найден в CSV"); // ✅ Столбца нет в CSV - устанавливаем NULL или пропускаем // Если поле имеет значение по умолчанию - пропускаем (БД сама подставит) if ($info['has_default']) { + error_log(" Пропускаем (есть default): " . $info['default']); continue; } // Если поле nullable - ставим NULL if ($info['nullable']) { + error_log(" Устанавливаем NULL (nullable)"); $value = null; } else { // Поле обязательное и нет дефолта - ошибка + error_log(" ОШИБКА: обязательное поле без значения"); throw new \PDOException("Missing required field: $name"); } } @@ -304,9 +324,11 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a $insertCols[] = "`$name`"; $placeholders[] = ":$name"; $params[":$name"] = $value; + error_log(" Добавлено в INSERT: '$name' = " . ($value === null ? 'NULL' : "'$value'")); } if (empty($insertCols)) { + error_log("Нет столбцов для вставки, пропускаем строку"); continue; } @@ -317,18 +339,28 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a implode(',', $placeholders) ); + error_log("SQL: $sql"); + error_log("Параметры: " . json_encode($params)); + $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $inserted++; + error_log("✓ Строка успешно вставлена, ID: " . $this->pdo->lastInsertId()); } catch (\PDOException $e) { $errors++; - $errorMessages[] = "Строка " . ($index + 1) . ": " . $this->formatPDOError($e, $schema, $table); + $errorMsg = "Строка " . ($index + 1) . ": " . $this->formatPDOError($e, $schema, $table); + error_log("✗ ОШИБКА при вставке строки " . ($index + 1) . ": " . $e->getMessage()); + error_log(" SQL Code: " . $e->getCode()); + error_log(" SQL State: " . ($e->errorInfo[0] ?? 'unknown')); + $errorMessages[] = $errorMsg; } } $this->pdo->commit(); + error_log("=== ИМПОРТ ЗАВЕРШЁН: вставлено=$inserted, ошибок=$errors ==="); } catch (\Exception $e) { $this->pdo->rollBack(); + error_log("=== КРИТИЧЕСКАЯ ОШИБКА ИМПОРТА: " . $e->getMessage() . " ==="); throw new \RuntimeException('Import failed: ' . $e->getMessage()); }