diff --git a/public/userarea/import_insert_batch.php b/public/userarea/import_insert_batch.php
new file mode 100644
index 0000000..5c6c087
--- /dev/null
+++ b/public/userarea/import_insert_batch.php
@@ -0,0 +1,173 @@
+ false, 'error' => 'Richiesta non valida']);
+ exit;
+}
+
+$template_id = intval($input['template_id']);
+$importReferenceCode = (string)$input['importreferencecode'];
+$newFilename = (string)$input['filename'];
+$columns = $input['columns']; // array
+$batchRows = $input['batch_rows']; // array di righe (ognuna array di celle)
+$batchExcelrows = $input['batch_excelrows']; // array di excelrow paralleli a batchRows
+
+$user_id = $iduserlogin ?? 1;
+
+function normalizeColName($s): string
+{
+ $s = (string)$s;
+ $s = str_replace(
+ ["\xC2\xA0", "\xE2\x80\xAF", "\xE2\x80\x87", "\xE2\x80\x89", "\xE2\x80\x8A", "\xEF\xBB\xBF"],
+ ' ',
+ $s
+ );
+ $s = preg_replace('/\s+/u', ' ', $s);
+ return trim((string)$s);
+}
+
+$db = DBHandlerSelect::getInstance();
+$pdo = $db->getConnection();
+
+// Recupera i mapping
+$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
+$stmt->execute([$template_id]);
+$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+if (empty($allMappings)) {
+ echo json_encode(['ok' => false, 'error' => 'Nessun mapping trovato per il template']);
+ exit;
+}
+
+// idclient di default (una volta sola)
+$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
+$template_stmt->execute([$template_id]);
+$tpl = $template_stmt->fetch(PDO::FETCH_ASSOC);
+$default_idclient = $tpl['idclient'] ?? null;
+
+// lims_user_id per il campo 244 (una volta sola)
+$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1");
+$stmtUser->execute([(int)$user_id]);
+$limsUserId = $stmtUser->fetchColumn();
+$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : '';
+
+$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1");
+$stmtMap->execute([(int)$template_id]);
+$mappingId244 = (int)$stmtMap->fetchColumn();
+
+$insertedIds = [];
+
+try {
+ $pdo->beginTransaction();
+
+ $insDatadb = $pdo->prepare("INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ $insDetail = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
+
+ foreach ($batchRows as $i => $row) {
+ $excelrow = $batchExcelrows[$i] ?? null;
+ if ($row === null || $excelrow === null) {
+ continue;
+ }
+
+ $insDatadb->execute([
+ $template_id,
+ $importReferenceCode,
+ $newFilename,
+ 'i',
+ $user_id,
+ null,
+ date('Y-m-d'),
+ $excelrow,
+ $default_idclient
+ ]);
+
+ $iddatadb = $pdo->lastInsertId();
+ $insertedIds[] = $iddatadb;
+
+ foreach ($allMappings as $mapping) {
+ $fieldValue = null;
+ if (!$mapping['is_manual']) {
+ $excelColumn = trim($mapping['excel_column']);
+ $excelColumnIndex = array_search(
+ normalizeColName($excelColumn),
+ array_map('normalizeColName', $columns)
+ );
+ if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
+ $fieldValue = $row[$excelColumnIndex];
+ } else {
+ $fieldValue = $mapping['manual_default'] ?? '';
+ }
+ switch ($mapping['data_type']) {
+ case 'INT':
+ $fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
+ break;
+ case 'DATE':
+ $fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
+ break;
+ case 'CHAR':
+ $fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
+ break;
+ case 'Testo':
+ case 'VARCHAR':
+ default:
+ $fieldValue = !empty($fieldValue) ? (string)$fieldValue : ($mapping['manual_default'] ?? '');
+ break;
+ }
+ } else {
+ $fieldValue = $mapping['manual_default'] ?? '';
+ if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
+ $fieldValue = date('Y-m-d');
+ }
+ }
+ if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
+ if ($mapping['auto_value'] === 'import_date') {
+ $fieldValue = date('Y-m-d');
+ } elseif ($mapping['auto_value'] === 'import_time') {
+ $fieldValue = date('H:i');
+ }
+ }
+
+ $insDetail->execute([$iddatadb, $mapping['id'], $fieldValue]);
+ }
+
+ // Campo 244 (Accettatore) con lims_user_id
+ if ($limsUserId !== '' && $mappingId244 > 0) {
+ $insDetail->execute([$iddatadb, $mappingId244, $limsUserId]);
+ }
+ }
+
+ $pdo->commit();
+
+ echo json_encode([
+ 'ok' => true,
+ 'inserted' => count($insertedIds)
+ ]);
+ exit;
+} catch (Exception $e) {
+ if ($pdo->inTransaction()) $pdo->rollBack();
+ error_log("[BATCH IMPORT] " . $e->getMessage());
+ echo json_encode([
+ 'ok' => false,
+ 'error' => $e->getMessage()
+ ]);
+ exit;
+}
diff --git a/public/userarea/import_xls2.php b/public/userarea/import_xls2.php
index e0b9f87..b3d94d0 100644
--- a/public/userarea/import_xls2.php
+++ b/public/userarea/import_xls2.php
@@ -344,8 +344,9 @@ error_log("Loaded template: " . print_r($template, true));
-
-
+
+
+ 0 selezionati
@@ -386,46 +387,187 @@ error_log("Loaded template: " . print_r($template, true));
-
-
+
+
+ 0 selezionati
+
+
`;
tableContainer.innerHTML = html;
const selectRowsForm = document.getElementById('selectRowsForm');
- selectRowsForm.addEventListener('submit', function(e) {
+ // Clic sulla cella (oltre che sulla checkbox) per selezionare/deselezionare la riga
+ document.querySelectorAll('#importPreviewTable tbody tr').forEach(tr => {
+ tr.style.cursor = 'pointer';
+ tr.addEventListener('click', function(e) {
+ // Se ho cliccato proprio sulla checkbox, lascio fare a lei (evita doppio toggle)
+ if (e.target.classList.contains('row-checkbox')) return;
+
+ const cb = this.querySelector('.row-checkbox');
+ if (!cb) return;
+ cb.checked = !cb.checked;
+ cb.dispatchEvent(new Event('change', {
+ bubbles: true
+ }));
+ });
+ });
+
+ selectRowsForm.addEventListener('submit', async function(e) {
+ e.preventDefault(); // gestiamo tutto via AJAX a batch
+
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
if (checkedBoxes.length === 0) {
- e.preventDefault();
alert('Seleziona almeno una riga.');
return;
}
+ // Raccoglie le righe selezionate (riga mappata + excelrow parallelo)
const selectedRows = [];
const selectedExcelRows = [];
- checkedBoxes.forEach((cb, newIndex) => {
+ checkedBoxes.forEach(cb => {
const originalIndex = parseInt(cb.value, 10);
-
if (data.rows && data.rows[originalIndex]) {
selectedRows.push(data.rows[originalIndex]);
+ selectedExcelRows.push(
+ (data.excel_data && data.excel_data[originalIndex]) ?
+ data.excel_data[originalIndex].excelrow :
+ null
+ );
}
-
- if (data.excel_data && data.excel_data[originalIndex]) {
- selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
- }
-
- // Reindex selected_rows so import_insert.php receives only the reduced rows array
- cb.value = newIndex;
});
- document.getElementById('selectedRowsData').value =
- encodeURIComponent(JSON.stringify(selectedRows));
+ const total = selectedRows.length;
+ if (total === 0) {
+ alert('Nessuna riga valida da importare.');
+ return;
+ }
- document.getElementById('selectedExcelRowsData').value =
- encodeURIComponent(JSON.stringify(selectedExcelRows));
+ // importreferencecode generato UNA volta, condiviso da tutti i batch
+ const importReferenceCode =
+ new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14) +
+ '-' + Math.random().toString(36).slice(2, 10);
+
+ const BATCH_SIZE = 20;
+
+ // Disabilita i pulsanti e mostra la barra di progresso
+ const btnTop = document.getElementById('proceedButtonTop');
+ const btnBottom = document.getElementById('proceedButtonBottom');
+ if (btnTop) btnTop.disabled = true;
+ if (btnBottom) btnBottom.disabled = true;
+
+ const progressHtml = `
+
+
+
Importazione in corso...
+ 0 / ${total}
+
+
+
+
+
`;
+ document.body.insertAdjacentHTML('beforeend', progressHtml);
+
+ const progressBar = document.getElementById('importProgressBar');
+ const progressText = document.getElementById('importProgressText');
+ const progressErr = document.getElementById('importProgressError');
+
+ let insertedTotal = 0;
+
+ // Invia un batch e ritorna la risposta JSON
+ async function sendBatch(startIndex) {
+ const batchRows = selectedRows.slice(startIndex, startIndex + BATCH_SIZE);
+ const batchExcelrows = selectedExcelRows.slice(startIndex, startIndex + BATCH_SIZE);
+
+ const resp = await fetch('import_insert_batch.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ template_id: data.template_id,
+ importreferencecode: importReferenceCode,
+ filename: data.filename,
+ columns: data.columns,
+ batch_rows: batchRows,
+ batch_excelrows: batchExcelrows
+ })
+ });
+ return resp.json();
+ }
+
+ // Cicla i batch in sequenza
+ for (let start = 0; start < total; start += BATCH_SIZE) {
+ let result;
+ try {
+ result = await sendBatch(start);
+ } catch (err) {
+ result = {
+ ok: false,
+ error: err.message
+ };
+ }
+
+ if (!result.ok) {
+ // Batch fallito: chiediamo all'utente se continuare
+ progressErr.textContent =
+ 'Errore su un blocco (righe ' + (start + 1) + '-' +
+ Math.min(start + BATCH_SIZE, total) + '): ' + (result.error || 'errore sconosciuto');
+ progressErr.style.display = 'block';
+
+ const continua = confirm(
+ 'Un blocco di righe non รจ stato importato.\n\n' +
+ 'Righe inserite finora: ' + insertedTotal + ' di ' + total + '.\n\n' +
+ 'Vuoi continuare con i blocchi successivi? (Annulla = interrompi)'
+ );
+
+ if (!continua) {
+ progressBar.classList.remove('progress-bar-animated');
+ progressBar.classList.add('bg-warning');
+ return;
+ }
+ // se continua, prosegue col prossimo blocco
+ continue;
+ }
+
+ insertedTotal += (result.inserted || 0);
+ const done = Math.min(start + BATCH_SIZE, total);
+ const pct = Math.round((done / total) * 100);
+ progressBar.style.width = pct + '%';
+ progressBar.textContent = pct + '%';
+ progressText.textContent = insertedTotal + ' / ' + total;
+ }
+
+ // Fine: barra verde e redirect a imported.php
+ progressBar.classList.remove('progress-bar-animated', 'progress-bar-striped');
+ progressBar.classList.add('bg-success');
+ progressText.textContent = insertedTotal + ' / ' + total + ' completate';
+
+ // PAUSA temporanea di 2 secondi per vedere la barra completa (da rimuovere dopo il test)
+ await new Promise(r => setTimeout(r, 2000));
+
+ window.location.href = 'imported.php?id=' +
+ encodeURIComponent(data.template_id) +
+ '&importref=' + encodeURIComponent(importReferenceCode);
});
const topTableScrollbar = document.getElementById('topTableScrollbar');
@@ -475,10 +617,17 @@ error_log("Loaded template: " . print_r($template, true));
const checkboxes = document.querySelectorAll('.row-checkbox');
function updateProceedButton() {
- const enabled = Array.from(checkboxes).some(cb => cb.checked);
+ const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
+ const enabled = checkedCount > 0;
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
+
+ const label = checkedCount + ' selezionati';
+ const countTop = document.getElementById('selectedCountTop');
+ const countBottom = document.getElementById('selectedCountBottom');
+ if (countTop) countTop.textContent = label;
+ if (countBottom) countBottom.textContent = label;
}
selectAllCheckbox.addEventListener('change', function() {