829 lines
34 KiB
PHP
829 lines
34 KiB
PHP
<?php
|
|
include('include/headscript.php');
|
|
|
|
// Check if a valid template ID has been provided
|
|
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
|
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
|
|
exit;
|
|
}
|
|
|
|
$id = intval($_GET['id']);
|
|
|
|
// Load template
|
|
$db = DBHandlerSelect::getInstance();
|
|
$pdo = $db->getConnection();
|
|
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$template) {
|
|
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
|
|
exit;
|
|
}
|
|
|
|
// Check mappings
|
|
$stmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
|
|
$stmt->execute([$id]);
|
|
$hasMappings = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
error_log("Loaded JSON import template: " . print_r($template, true));
|
|
?>
|
|
|
|
<!doctype html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
<?php include('cssinclude.php'); ?>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
.top-scrollbar {
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
width: 100%;
|
|
height: 18px;
|
|
margin-bottom: 8px;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.25rem;
|
|
background: #f8f9fa;
|
|
display: none;
|
|
}
|
|
|
|
.top-scrollbar-inner {
|
|
height: 1px;
|
|
}
|
|
|
|
.table-container {
|
|
overflow-x: auto;
|
|
max-width: 100%;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.table th,
|
|
.table td {
|
|
padding: 10px;
|
|
text-align: left;
|
|
border: 1px solid #dee2e6;
|
|
min-width: 120px;
|
|
max-width: 260px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.table th:first-child,
|
|
.table td:first-child {
|
|
min-width: 50px;
|
|
max-width: 50px;
|
|
}
|
|
|
|
.table th {
|
|
background-color: #f8f9fa;
|
|
position: relative;
|
|
cursor: col-resize;
|
|
user-select: none;
|
|
}
|
|
|
|
.table th .resize-handle {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 5px;
|
|
height: 100%;
|
|
cursor: col-resize;
|
|
background: transparent;
|
|
}
|
|
|
|
.table th .resize-handle:hover {
|
|
background: #007bff;
|
|
}
|
|
|
|
.loader {
|
|
display: none;
|
|
border: 4px solid #f3f3f3;
|
|
border-top: 4px solid #3498db;
|
|
border-radius: 50%;
|
|
width: 30px;
|
|
height: 30px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 10px auto;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.column-filters th {
|
|
background: #ffffff;
|
|
cursor: default;
|
|
}
|
|
|
|
.column-filters input {
|
|
width: 100%;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.json-code-input {
|
|
font-size: 1.15rem;
|
|
min-height: 48px;
|
|
}
|
|
|
|
.json-paste-area {
|
|
min-height: 260px;
|
|
font-family: Consolas, Monaco, monospace;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.json-help-box {
|
|
background: #f8f9fa;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 12px 14px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.source-badge {
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
<title><?= htmlspecialchars($template['name']) ?> - JSON Import - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="wrapper">
|
|
<?php include('include/navbar.php'); ?>
|
|
<?php include('include/topbar.php'); ?>
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
<?php include('top_stat_widget.php'); ?>
|
|
|
|
<div class="mb-3 text">
|
|
<a href="imported.php?id=<?= $id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
|
<a href="tolims.php?id=<?= $id ?>" class="btn btn-success me-2">To LIMS (l)</a>
|
|
<a href="bindings_manage.php?template_id=<?= $id ?>" class="btn btn-outline-secondary">Binding JSON -> LIMS</a>
|
|
</div>
|
|
|
|
<div class="card radius-10">
|
|
<div class="card-header">
|
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
<div>
|
|
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?> - JSON Import</h6>
|
|
<small>
|
|
Template ID: <?= $id ?>
|
|
</small>
|
|
</div>
|
|
<span class="badge bg-info text-dark">JSON mode</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<?php if (!$hasMappings): ?>
|
|
<div class="alert alert-warning" role="alert">
|
|
Nessun mapping trovato per questo template. Configura i mapping prima di procedere.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="json-help-box mb-3">
|
|
<strong>Flusso:</strong> inserisci/scansiona un codice per recuperare il JSON da API, oppure incolla manualmente un JSON nel secondo tab.
|
|
Ogni JSON aggiunto diventa una riga della tabella di preview. Quando hai finito, seleziona le righe e clicca <strong>Prosegui</strong>.
|
|
</div>
|
|
|
|
<ul class="nav nav-tabs" id="jsonImportTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="api-code-tab" data-bs-toggle="tab" data-bs-target="#api-code-pane" type="button" role="tab" aria-controls="api-code-pane" aria-selected="true">
|
|
Code / Barcode
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="paste-json-tab" data-bs-toggle="tab" data-bs-target="#paste-json-pane" type="button" role="tab" aria-controls="paste-json-pane" aria-selected="false">
|
|
Paste JSON
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content border border-top-0 p-3 mb-3" id="jsonImportTabsContent">
|
|
<div class="tab-pane fade show active" id="api-code-pane" role="tabpanel" aria-labelledby="api-code-tab" tabindex="0">
|
|
<form id="jsonCodeForm" class="row g-3 align-items-end">
|
|
<div class="col-lg-8">
|
|
<label for="json_code" class="form-label">Code / Barcode</label>
|
|
<input type="text" class="form-control json-code-input" id="json_code" name="json_code" placeholder="Write or scan code" autocomplete="off" <?= !$hasMappings ? 'disabled' : '' ?>>
|
|
<small class="text-muted">Lo scanner barcode normalmente scrive qui il codice e invia Enter.</small>
|
|
</div>
|
|
<div class="col-lg-4 d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary flex-fill" <?= !$hasMappings ? 'disabled' : '' ?>>Load JSON</button>
|
|
<button type="button" class="btn btn-outline-secondary" id="clearCodeBtn">Clear</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="paste-json-pane" role="tabpanel" aria-labelledby="paste-json-tab" tabindex="0">
|
|
<form id="pasteJsonForm">
|
|
<div class="mb-3">
|
|
<label for="manual_json_reference" class="form-label">Reference / filename</label>
|
|
<input type="text" class="form-control" id="manual_json_reference" placeholder="Optional reference, e.g. manual-json-001">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="manual_json" class="form-label">Paste JSON</label>
|
|
<textarea class="form-control json-paste-area" id="manual_json" placeholder='{"data":[{"id":"MM000620","attributes":{"trf_type":"apparel"}}]}' <?= !$hasMappings ? 'disabled' : '' ?>></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" <?= !$hasMappings ? 'disabled' : '' ?>>Add pasted JSON</button>
|
|
<button type="button" class="btn btn-outline-secondary" id="clearManualJsonBtn">Clear</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="loader" id="loader"></div>
|
|
<div id="errorContainer" class="alert alert-danger mt-3" style="display:none;"></div>
|
|
<div id="successContainer" class="alert alert-success mt-3" style="display:none;"></div>
|
|
|
|
<div id="tableContainer"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overlay toggle-icon"></div>
|
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
|
<?php include('include/footer.php'); ?>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
|
|
<?php include('jsinclude.php'); ?>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const TEMPLATE_ID = <?= (int)$id ?>;
|
|
const API_ENDPOINT = 'get_json_by_code.php';
|
|
const INCLUDE_SOURCE_CODE_COLUMN = true;
|
|
const UNWRAP_SINGLE_DATA_ITEM = true;
|
|
|
|
const jsonCodeForm = document.getElementById('jsonCodeForm');
|
|
const pasteJsonForm = document.getElementById('pasteJsonForm');
|
|
const jsonCodeInput = document.getElementById('json_code');
|
|
const manualJsonInput = document.getElementById('manual_json');
|
|
const manualJsonReferenceInput = document.getElementById('manual_json_reference');
|
|
const clearCodeBtn = document.getElementById('clearCodeBtn');
|
|
const clearManualJsonBtn = document.getElementById('clearManualJsonBtn');
|
|
const loader = document.getElementById('loader');
|
|
const errorContainer = document.getElementById('errorContainer');
|
|
const successContainer = document.getElementById('successContainer');
|
|
const tableContainer = document.getElementById('tableContainer');
|
|
|
|
let jsonRows = [];
|
|
let columns = [];
|
|
|
|
if (jsonCodeInput && !jsonCodeInput.disabled) {
|
|
jsonCodeInput.focus();
|
|
}
|
|
|
|
jsonCodeForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const code = jsonCodeInput.value.trim();
|
|
|
|
if (!code) {
|
|
showError('Inserisci o scansiona un codice.');
|
|
return;
|
|
}
|
|
|
|
loadJsonFromApi(code);
|
|
});
|
|
|
|
pasteJsonForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const rawJson = manualJsonInput.value.trim();
|
|
const reference = manualJsonReferenceInput.value.trim() || 'manual-json-' + (jsonRows.length + 1);
|
|
|
|
if (!rawJson) {
|
|
showError('Incolla un JSON prima di aggiungerlo.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const parsedJson = JSON.parse(rawJson);
|
|
addJsonRow(parsedJson, reference, 'paste');
|
|
manualJsonInput.value = '';
|
|
manualJsonReferenceInput.value = '';
|
|
showSuccess('JSON incollato aggiunto correttamente.');
|
|
} catch (err) {
|
|
showError('JSON non valido: ' + err.message);
|
|
}
|
|
});
|
|
|
|
clearCodeBtn.addEventListener('click', function() {
|
|
jsonCodeInput.value = '';
|
|
jsonCodeInput.focus();
|
|
});
|
|
|
|
clearManualJsonBtn.addEventListener('click', function() {
|
|
manualJsonInput.value = '';
|
|
manualJsonReferenceInput.value = '';
|
|
});
|
|
|
|
function loadJsonFromApi(code) {
|
|
hideMessages();
|
|
loader.style.display = 'block';
|
|
|
|
fetch(API_ENDPOINT, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
template_id: TEMPLATE_ID,
|
|
code: code
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('HTTP status ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(responseData => {
|
|
loader.style.display = 'none';
|
|
|
|
if (responseData.error) {
|
|
showError(responseData.error);
|
|
return;
|
|
}
|
|
|
|
let jsonPayload = responseData;
|
|
let reference = code;
|
|
|
|
// Supported endpoint response format:
|
|
// { success: true, json: {...}, reference: "..." }
|
|
if (responseData.json !== undefined) {
|
|
jsonPayload = responseData.json;
|
|
reference = responseData.reference || responseData.filename || responseData.code || code;
|
|
}
|
|
|
|
addJsonRow(jsonPayload, reference, 'api');
|
|
jsonCodeInput.value = '';
|
|
jsonCodeInput.focus();
|
|
showSuccess('JSON recuperato e aggiunto correttamente.');
|
|
})
|
|
.catch(error => {
|
|
loader.style.display = 'none';
|
|
showError('Errore durante il recupero del JSON: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function addJsonRow(jsonPayload, reference, sourceType) {
|
|
// Ogni elemento di data[] diventa una riga (non colonne data.0.*, data.1.*).
|
|
const records = extractRecords(jsonPayload);
|
|
|
|
records.forEach((record, recordIndex) => {
|
|
const flattened = flattenJson(record);
|
|
|
|
const rowReference = records.length > 1 ?
|
|
reference + '#' + (recordIndex + 1) :
|
|
reference;
|
|
|
|
if (INCLUDE_SOURCE_CODE_COLUMN) {
|
|
flattened.source_code = rowReference;
|
|
flattened.source_type = sourceType;
|
|
}
|
|
|
|
const newColumns = Object.keys(flattened).filter(col => !columns.includes(col));
|
|
columns = columns.concat(newColumns);
|
|
|
|
jsonRows.push({
|
|
excelrow: 'JSON-' + (jsonRows.length + 1),
|
|
reference: rowReference,
|
|
sourceType: sourceType,
|
|
flat: flattened
|
|
});
|
|
});
|
|
|
|
renderTable();
|
|
}
|
|
|
|
// Record da trasformare in righe: gli oggetti in data[], altrimenti il payload stesso.
|
|
function extractRecords(payload) {
|
|
if (
|
|
payload &&
|
|
typeof payload === 'object' &&
|
|
!Array.isArray(payload) &&
|
|
Array.isArray(payload.data) &&
|
|
payload.data.length > 0
|
|
) {
|
|
const items = payload.data.filter(item => item && typeof item === 'object');
|
|
if (items.length > 0) {
|
|
return items;
|
|
}
|
|
}
|
|
|
|
return [payload];
|
|
}
|
|
|
|
function flattenJson(value, prefix = '', result = {}) {
|
|
if (value === null || value === undefined) {
|
|
result[prefix || 'value'] = '';
|
|
return result;
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
if (value.length === 0) {
|
|
result[prefix || 'value'] = '';
|
|
return result;
|
|
}
|
|
|
|
value.forEach((item, index) => {
|
|
const newPrefix = prefix ? prefix + '.' + index : String(index);
|
|
flattenJson(item, newPrefix, result);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
const keys = Object.keys(value);
|
|
if (keys.length === 0) {
|
|
result[prefix || 'value'] = '';
|
|
return result;
|
|
}
|
|
|
|
keys.forEach(key => {
|
|
const newPrefix = prefix ? prefix + '.' + key : key;
|
|
flattenJson(value[key], newPrefix, result);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
result[prefix || 'value'] = String(value);
|
|
return result;
|
|
}
|
|
|
|
function buildImportData() {
|
|
const rows = jsonRows.map(row => columns.map(col => row.flat[col] ?? ''));
|
|
const excelData = rows.map((rowData, index) => ({
|
|
excelrow: jsonRows[index].excelrow,
|
|
data: rowData
|
|
}));
|
|
|
|
return {
|
|
template_id: TEMPLATE_ID,
|
|
columns: columns,
|
|
rows: rows,
|
|
excel_data: excelData,
|
|
filename: 'json_import_' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
};
|
|
}
|
|
|
|
function renderTable() {
|
|
if (jsonRows.length === 0) {
|
|
tableContainer.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
const data = buildImportData();
|
|
|
|
let html = `
|
|
<form id="selectRowsForm" action="import_insert.php" method="POST">
|
|
<input type="hidden" name="template_id" value="${escapeHtml(data.template_id)}">
|
|
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
|
|
<input type="hidden" name="rows" id="selectedRowsData" value="">
|
|
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
|
|
<input type="hidden" name="filename" value="${escapeHtml(data.filename)}">
|
|
<input type="hidden" name="source_type" value="json">
|
|
|
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
|
<div>
|
|
<strong>JSON rows loaded:</strong> ${jsonRows.length}
|
|
<span class="badge bg-secondary source-badge ms-2">Columns: ${data.columns.length}</span>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-outline-danger btn-sm" id="clearAllRowsBtn">Clear all rows</button>
|
|
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="top-scrollbar" id="topTableScrollbar">
|
|
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
|
|
</div>
|
|
|
|
<div class="table-container" id="mainTableContainer">
|
|
<table class="table table-striped table-bordered" id="importPreviewTable">
|
|
<thead>
|
|
<tr>
|
|
<th><input type="checkbox" id="selectAll"> Select</th>
|
|
${data.columns.map(col => `<th title="${escapeHtml(col)}">${escapeHtml(readableColumnLabel(col))}<div class="resize-handle"></div></th>`).join('')}
|
|
<th>Action</th>
|
|
</tr>
|
|
<tr class="column-filters">
|
|
<th></th>
|
|
${data.columns.map((col, i) => `
|
|
<th>
|
|
<input type="text"
|
|
class="form-control form-control-sm column-filter"
|
|
data-col-index="${i}"
|
|
placeholder="Filter...">
|
|
</th>
|
|
`).join('')}
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${data.excel_data.map((row, index) => `
|
|
<tr data-row-index="${index}">
|
|
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${escapeHtml(row.excelrow)}"></td>
|
|
${row.data.map(cell => `<td title="${escapeHtml(cell)}">${escapeHtml(cell)}</td>`).join('')}
|
|
<td><button type="button" class="btn btn-sm btn-outline-danger remove-row-btn" data-row-index="${index}">Remove</button></td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary mt-3" id="proceedButtonBottom" disabled>Prosegui</button>
|
|
</form>
|
|
`;
|
|
|
|
tableContainer.innerHTML = html;
|
|
bindTableEvents(data);
|
|
}
|
|
|
|
function bindTableEvents(data) {
|
|
const selectRowsForm = document.getElementById('selectRowsForm');
|
|
const clearAllRowsBtn = document.getElementById('clearAllRowsBtn');
|
|
const removeRowButtons = document.querySelectorAll('.remove-row-btn');
|
|
|
|
selectRowsForm.addEventListener('submit', function(e) {
|
|
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
|
|
|
|
if (checkedBoxes.length === 0) {
|
|
e.preventDefault();
|
|
alert('Seleziona almeno una riga.');
|
|
return;
|
|
}
|
|
|
|
const selectedRows = [];
|
|
const selectedExcelRows = [];
|
|
|
|
checkedBoxes.forEach((cb, newIndex) => {
|
|
const originalIndex = parseInt(cb.value, 10);
|
|
|
|
if (data.rows && data.rows[originalIndex]) {
|
|
selectedRows.push(data.rows[originalIndex]);
|
|
}
|
|
|
|
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));
|
|
|
|
document.getElementById('selectedExcelRowsData').value =
|
|
encodeURIComponent(JSON.stringify(selectedExcelRows));
|
|
});
|
|
|
|
clearAllRowsBtn.addEventListener('click', function() {
|
|
if (!confirm('Vuoi rimuovere tutte le righe JSON caricate?')) return;
|
|
jsonRows = [];
|
|
columns = [];
|
|
renderTable();
|
|
showSuccess('Righe JSON rimosse.');
|
|
jsonCodeInput.focus();
|
|
});
|
|
|
|
removeRowButtons.forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const rowIndex = parseInt(this.dataset.rowIndex, 10);
|
|
jsonRows.splice(rowIndex, 1);
|
|
rebuildColumnsFromRows();
|
|
renderTable();
|
|
});
|
|
});
|
|
|
|
const topTableScrollbar = document.getElementById('topTableScrollbar');
|
|
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
|
|
const mainTableContainer = document.getElementById('mainTableContainer');
|
|
const importPreviewTable = document.getElementById('importPreviewTable');
|
|
|
|
function updateTopTableScrollbar() {
|
|
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
|
|
|
|
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
|
|
|
|
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
|
|
topTableScrollbar.style.display = 'block';
|
|
} else {
|
|
topTableScrollbar.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
let syncingTop = false;
|
|
let syncingBottom = false;
|
|
|
|
if (topTableScrollbar && mainTableContainer) {
|
|
topTableScrollbar.addEventListener('scroll', function() {
|
|
if (syncingBottom) return;
|
|
syncingTop = true;
|
|
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
|
|
syncingTop = false;
|
|
});
|
|
|
|
mainTableContainer.addEventListener('scroll', function() {
|
|
if (syncingTop) return;
|
|
syncingBottom = true;
|
|
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
|
|
syncingBottom = false;
|
|
});
|
|
}
|
|
|
|
updateTopTableScrollbar();
|
|
setTimeout(updateTopTableScrollbar, 100);
|
|
setTimeout(updateTopTableScrollbar, 300);
|
|
window.addEventListener('resize', updateTopTableScrollbar);
|
|
|
|
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
|
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
|
const selectAllCheckbox = document.getElementById('selectAll');
|
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
|
|
|
function updateProceedButton() {
|
|
const enabled = Array.from(document.querySelectorAll('.row-checkbox')).some(cb => cb.checked);
|
|
|
|
if (proceedButtonTop) proceedButtonTop.disabled = !enabled;
|
|
if (proceedButtonBottom) proceedButtonBottom.disabled = !enabled;
|
|
}
|
|
|
|
selectAllCheckbox.addEventListener('change', function() {
|
|
const visibleRows = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
.filter(row => row.style.display !== 'none');
|
|
|
|
visibleRows.forEach(row => {
|
|
const checkbox = row.querySelector('.row-checkbox');
|
|
if (checkbox) {
|
|
checkbox.checked = this.checked;
|
|
}
|
|
});
|
|
|
|
updateProceedButton();
|
|
});
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.addEventListener('change', function() {
|
|
const visibleCheckboxes = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
.filter(row => row.style.display !== 'none')
|
|
.map(row => row.querySelector('.row-checkbox'))
|
|
.filter(cb => cb !== null);
|
|
|
|
selectAllCheckbox.checked =
|
|
visibleCheckboxes.length > 0 &&
|
|
visibleCheckboxes.every(cb => cb.checked);
|
|
|
|
updateProceedButton();
|
|
});
|
|
});
|
|
|
|
const thElements = document.querySelectorAll('#importPreviewTable th');
|
|
thElements.forEach((th, index) => {
|
|
if (index === 0) return;
|
|
const resizeHandle = th.querySelector('.resize-handle');
|
|
if (resizeHandle) {
|
|
resizeHandle.addEventListener('mousedown', (e) => {
|
|
e.preventDefault();
|
|
const startX = e.clientX;
|
|
const startWidth = th.offsetWidth;
|
|
|
|
const onMouseMove = (e) => {
|
|
const newWidth = Math.max(60, startWidth + (e.clientX - startX));
|
|
th.style.width = `${newWidth}px`;
|
|
th.style.minWidth = `${newWidth}px`;
|
|
th.style.maxWidth = `${newWidth}px`;
|
|
|
|
const cells = document.querySelectorAll(`#importPreviewTable td:nth-child(${index + 1})`);
|
|
cells.forEach(cell => {
|
|
cell.style.width = `${newWidth}px`;
|
|
cell.style.minWidth = `${newWidth}px`;
|
|
cell.style.maxWidth = `${newWidth}px`;
|
|
});
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
};
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
});
|
|
}
|
|
});
|
|
|
|
const rows = document.querySelectorAll('#importPreviewTable tbody tr');
|
|
const filterInputs = document.querySelectorAll('.column-filter');
|
|
const activeFilters = {};
|
|
|
|
function applyColumnFilters() {
|
|
rows.forEach(row => {
|
|
let visible = true;
|
|
|
|
for (const [colIndexStr, filterValue] of Object.entries(activeFilters)) {
|
|
const colIndex = parseInt(colIndexStr, 10);
|
|
const cell = row.cells[colIndex + 1];
|
|
const cellText = (cell?.textContent || '').toLowerCase();
|
|
const searchText = (filterValue || '').toLowerCase().trim();
|
|
|
|
if (searchText && !cellText.includes(searchText)) {
|
|
visible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
row.style.display = visible ? '' : 'none';
|
|
});
|
|
|
|
const visibleCheckboxes = Array.from(document.querySelectorAll('#importPreviewTable tbody tr'))
|
|
.filter(row => row.style.display !== 'none')
|
|
.map(row => row.querySelector('.row-checkbox'))
|
|
.filter(cb => cb !== null);
|
|
|
|
selectAllCheckbox.checked =
|
|
visibleCheckboxes.length > 0 &&
|
|
visibleCheckboxes.every(cb => cb.checked);
|
|
|
|
updateProceedButton();
|
|
}
|
|
|
|
filterInputs.forEach(input => {
|
|
input.addEventListener('input', function() {
|
|
const colIndex = this.dataset.colIndex;
|
|
activeFilters[colIndex] = this.value;
|
|
applyColumnFilters();
|
|
});
|
|
});
|
|
|
|
updateProceedButton();
|
|
}
|
|
|
|
function rebuildColumnsFromRows() {
|
|
columns = [];
|
|
jsonRows.forEach(row => {
|
|
Object.keys(row.flat).forEach(col => {
|
|
if (!columns.includes(col)) {
|
|
columns.push(col);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function readableColumnLabel(columnName) {
|
|
if (!columnName) return 'Column without name';
|
|
return columnName;
|
|
}
|
|
|
|
function showError(message) {
|
|
successContainer.style.display = 'none';
|
|
errorContainer.textContent = message;
|
|
errorContainer.style.display = 'block';
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
errorContainer.style.display = 'none';
|
|
successContainer.textContent = message;
|
|
successContainer.style.display = 'block';
|
|
setTimeout(() => {
|
|
successContainer.style.display = 'none';
|
|
}, 3500);
|
|
}
|
|
|
|
function hideMessages() {
|
|
errorContainer.style.display = 'none';
|
|
successContainer.style.display = 'none';
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return String(value ?? '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|