import insert json
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
require_once 'include/headscript.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Invalid request method']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$template_id = isset($input['template_id']) ? (int)$input['template_id'] : 0;
|
||||
$code = isset($input['code']) ? trim($input['code']) : '';
|
||||
|
||||
if ($template_id <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing or invalid template_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($code === '') {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing code']);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Replace this block with your real API call.
|
||||
* Expected response for import_json.php:
|
||||
* {
|
||||
* "success": true,
|
||||
* "reference": "CODE123",
|
||||
* "json": { ... real JSON payload ... }
|
||||
* }
|
||||
*/
|
||||
|
||||
$sampleJson = [
|
||||
'data' => [[
|
||||
'type' => 'trf_data_request',
|
||||
'id' => $code,
|
||||
'attributes' => [
|
||||
'trf_type' => 'apparel',
|
||||
'service_required' => 'regular',
|
||||
'submitter_information' => [
|
||||
'submitter_type' => 'supplier'
|
||||
]
|
||||
]
|
||||
]]
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'reference' => $code,
|
||||
'json' => $sampleJson
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
@@ -237,7 +237,18 @@
|
||||
const iconClass = getTemplateIcon(sourceType);
|
||||
|
||||
const btn = document.createElement("a");
|
||||
btn.href = `import_xls2.php?id=${template.id}`;
|
||||
|
||||
// Redirect based on template source type
|
||||
if (sourceType === 'XLS') {
|
||||
btn.href = `import_xls2.php?id=${template.id}`;
|
||||
} else if (sourceType === 'API' || sourceType === 'JSON') {
|
||||
btn.href = `import_json.php?id=${template.id}`;
|
||||
} else if (sourceType === 'PDF') {
|
||||
btn.href = `import_pdf.php?id=${template.id}`;
|
||||
} else {
|
||||
btn.href = `import_xls2.php?id=${template.id}`;
|
||||
}
|
||||
|
||||
btn.className = `btn ${sizeClass}`;
|
||||
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
|
||||
btn.style.color = template.button_text_color || '#ffffff';
|
||||
@@ -245,9 +256,9 @@
|
||||
btn.setAttribute("data-source-type", sourceType);
|
||||
|
||||
btn.innerHTML = `
|
||||
<i class="${iconClass} template-icon"></i>
|
||||
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
|
||||
`;
|
||||
<i class="${iconClass} template-icon"></i>
|
||||
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
|
||||
`;
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ $rows = json_decode(urldecode($_POST['rows'] ?? '[]'), true);
|
||||
$excelrows = json_decode(urldecode($_POST['excelrows'] ?? '[]'), true);
|
||||
|
||||
$newFilename = $_POST['filename'];
|
||||
$source_type = strtolower(trim($_POST['source_type'] ?? 'xls'));
|
||||
|
||||
$_SESSION['template_id'] = $template_id;
|
||||
$_SESSION['selected_rows'] = $selected_rows;
|
||||
@@ -37,6 +38,7 @@ error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_en
|
||||
error_log("Columns: " . json_encode($columns));
|
||||
error_log("Rows: " . json_encode($rows));
|
||||
error_log("Excelrows: " . json_encode($excelrows));
|
||||
error_log("Source type: " . $source_type);
|
||||
|
||||
$user_id = $iduserlogin ?? 1;
|
||||
|
||||
@@ -47,7 +49,22 @@ $pdo = $db->getConnection();
|
||||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||||
|
||||
// Recupera tutti i mapping dal template
|
||||
$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 = $pdo->prepare("
|
||||
SELECT
|
||||
id,
|
||||
excel_column,
|
||||
json_node,
|
||||
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);
|
||||
|
||||
@@ -67,12 +84,20 @@ foreach ($allMappings as $mapping) {
|
||||
|
||||
// Inserisci le righe selezionate in datadb
|
||||
$insertedIds = [];
|
||||
foreach ($selected_rows as $rowIndex) {
|
||||
$row = $rows[$rowIndex] ?? null;
|
||||
$excelrow = $excelrows[$rowIndex] ?? null;
|
||||
foreach ($selected_rows as $loopIndex => $rowIndex) {
|
||||
|
||||
if ($source_type === 'json') {
|
||||
// JSON import sends only selected rows in rows/excelrows
|
||||
$row = $rows[$loopIndex] ?? null;
|
||||
$excelrow = $excelrows[$loopIndex] ?? ('JSON-' . ($loopIndex + 1));
|
||||
} else {
|
||||
// XLS import keeps original row indexes
|
||||
$row = $rows[$rowIndex] ?? null;
|
||||
$excelrow = $excelrows[$rowIndex] ?? null;
|
||||
}
|
||||
|
||||
if ($row === null || $excelrow === null) {
|
||||
error_log("Errore: riga o excelrow mancante per rowIndex $rowIndex");
|
||||
error_log("Errore: riga o excelrow mancante. Source type: $source_type, loopIndex: $loopIndex, rowIndex: $rowIndex");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -104,14 +129,72 @@ foreach ($selected_rows as $rowIndex) {
|
||||
foreach ($allMappings as $mapping) {
|
||||
$fieldValue = null;
|
||||
if (!$mapping['is_manual']) {
|
||||
$excelColumn = trim($mapping['excel_column']);
|
||||
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
|
||||
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
|
||||
$fieldValue = $row[$excelColumnIndex];
|
||||
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
|
||||
$sourceColumn = '';
|
||||
|
||||
if ($source_type === 'json') {
|
||||
$sourceColumn = trim($mapping['json_node'] ?? '');
|
||||
} else {
|
||||
$sourceColumn = trim($mapping['excel_column'] ?? '');
|
||||
}
|
||||
|
||||
// Fallback: if JSON node is empty, try excel_column
|
||||
if ($sourceColumn === '') {
|
||||
$sourceColumn = trim($mapping['excel_column'] ?? '');
|
||||
}
|
||||
|
||||
$columnsTrimmed = array_map('trim', $columns);
|
||||
|
||||
$candidateColumns = [];
|
||||
|
||||
if ($sourceColumn !== '') {
|
||||
$candidateColumns[] = $sourceColumn;
|
||||
|
||||
if ($source_type === 'json') {
|
||||
// Common JSON path variants
|
||||
$candidateColumns[] = preg_replace('/^data\[\]\./', '', $sourceColumn);
|
||||
$candidateColumns[] = preg_replace('/^data\.0\./', '', $sourceColumn);
|
||||
$candidateColumns[] = str_replace('data[].', 'data.0.', $sourceColumn);
|
||||
$candidateColumns[] = str_replace('data.0.', 'data[].', $sourceColumn);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty and duplicate candidates
|
||||
$candidateColumns = array_values(array_unique(array_filter($candidateColumns, function ($value) {
|
||||
return trim((string)$value) !== '';
|
||||
})));
|
||||
|
||||
$sourceColumnIndex = false;
|
||||
$matchedColumn = '';
|
||||
|
||||
foreach ($candidateColumns as $candidateColumn) {
|
||||
$candidateColumn = trim($candidateColumn);
|
||||
$index = array_search($candidateColumn, $columnsTrimmed);
|
||||
|
||||
if ($index !== false) {
|
||||
$sourceColumnIndex = $index;
|
||||
$matchedColumn = $candidateColumn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($sourceColumnIndex !== false && isset($row[$sourceColumnIndex]) && $row[$sourceColumnIndex] !== '') {
|
||||
$fieldValue = $row[$sourceColumnIndex];
|
||||
|
||||
error_log(
|
||||
"Found source column. Original: '$sourceColumn', Matched: '$matchedColumn', Index: $sourceColumnIndex, Value: " .
|
||||
var_export($fieldValue, true)
|
||||
);
|
||||
} else {
|
||||
$fieldValue = $mapping['manual_default'] ?? '';
|
||||
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
|
||||
|
||||
error_log(
|
||||
"Source column not found or empty. Original: '$sourceColumn'. Candidates: " .
|
||||
json_encode($candidateColumns) .
|
||||
". Available columns: " .
|
||||
json_encode($columnsTrimmed) .
|
||||
". Using default: " .
|
||||
var_export($fieldValue, true)
|
||||
);
|
||||
}
|
||||
switch ($mapping['data_type']) {
|
||||
case 'INT':
|
||||
@@ -147,7 +230,7 @@ foreach ($selected_rows as $rowIndex) {
|
||||
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
||||
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
||||
}
|
||||
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
|
||||
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Source Column: " . ($sourceColumn ?? 'N/A') . ", Source Type: " . $source_type . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
|
||||
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
|
||||
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
|
||||
@@ -158,4 +241,3 @@ $_SESSION['inserted_ids'] = $insertedIds;
|
||||
|
||||
header("Location: imported.php?id=" . urlencode($template_id) . "&importref=" . urlencode($importReferenceCode));
|
||||
exit;
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,819 @@
|
||||
<?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">To LIMS (l)</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) {
|
||||
const normalizedPayload = normalizeJsonPayload(jsonPayload);
|
||||
const flattened = flattenJson(normalizedPayload);
|
||||
|
||||
if (INCLUDE_SOURCE_CODE_COLUMN) {
|
||||
flattened.source_code = reference;
|
||||
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: reference,
|
||||
sourceType: sourceType,
|
||||
flat: flattened
|
||||
});
|
||||
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function normalizeJsonPayload(payload) {
|
||||
if (
|
||||
UNWRAP_SINGLE_DATA_ITEM &&
|
||||
payload &&
|
||||
typeof payload === 'object' &&
|
||||
!Array.isArray(payload) &&
|
||||
Array.isArray(payload.data) &&
|
||||
payload.data.length === 1 &&
|
||||
payload.data[0] &&
|
||||
typeof payload.data[0] === 'object'
|
||||
) {
|
||||
return payload.data[0];
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -730,8 +730,8 @@
|
||||
{
|
||||
"IdSchemaCustomFields": 177,
|
||||
"ConteggioClienti": 0,
|
||||
"Nome": "Phoebe philo ACC",
|
||||
"Descrizione": "(scarpe, borse, cinture, occhiali, gioielleria)\r\n"
|
||||
"Nome": "Phoebe Philo ",
|
||||
"Descrizione": "\r\n\r\n"
|
||||
},
|
||||
{
|
||||
"IdSchemaCustomFields": 178,
|
||||
|
||||
@@ -78,6 +78,51 @@
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.badge-source-pdf {
|
||||
background-color: #fff3cd;
|
||||
color: #b58100;
|
||||
}
|
||||
|
||||
.type-filter-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.type-filter-btn {
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 7px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
opacity: 0.35;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.type-filter-btn.active {
|
||||
opacity: 1;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.type-filter-btn[data-type="XLS"] {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
.type-filter-btn[data-type="API"] {
|
||||
background-color: #198754;
|
||||
}
|
||||
|
||||
.type-filter-btn[data-type="PDF"] {
|
||||
background-color: #b58100;
|
||||
}
|
||||
|
||||
.type-filter-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#xlsTemplatesTable {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -136,79 +181,92 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="type-filter-bar">
|
||||
<span class="text-muted fw-semibold me-1">Filter by type:</span>
|
||||
|
||||
<button type="button" class="type-filter-btn active" data-type="XLS">
|
||||
XLS
|
||||
</button>
|
||||
|
||||
<button type="button" class="type-filter-btn active" data-type="API">
|
||||
JSON/API
|
||||
</button>
|
||||
|
||||
<button type="button" class="type-filter-btn active" data-type="PDF">
|
||||
PDF
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="xlsTemplatesTable" class="table table-striped table-bordered table-sm w-100">
|
||||
<table id="xlsTemplatesTable" class="table table-striped table-bordered align-middle w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($action, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||
<th><?= htmlspecialchars($nametemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||
<th>Actions</th>
|
||||
<th>Template Name</th>
|
||||
<th>Type</th>
|
||||
<th>Row</th>
|
||||
<th>Col</th>
|
||||
<th><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></th>
|
||||
<th>Description</th>
|
||||
<th>Client</th>
|
||||
<th>Button</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- DataTables will populate this section automatically -->
|
||||
</tbody>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--end page wrapper -->
|
||||
|
||||
<!--start overlay-->
|
||||
<div class="overlay toggle-icon"></div>
|
||||
<!--end overlay-->
|
||||
|
||||
<!--Start Back To Top Button-->
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<!--End Back To Top Button-->
|
||||
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<!--end page wrapper -->
|
||||
<!--end wrapper-->
|
||||
|
||||
<!--start overlay-->
|
||||
<div class="overlay toggle-icon"></div>
|
||||
<!--end overlay-->
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||
|
||||
<!--Start Back To Top Button-->
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<!--End Back To Top Button-->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
<!--end wrapper-->
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
|
||||
if (urlParams.get('cloned') === '1') {
|
||||
Swal.fire({
|
||||
title: "Template cloned",
|
||||
text: "The template was cloned successfully.",
|
||||
icon: "success",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.get('cloned') === '1') {
|
||||
Swal.fire({
|
||||
title: "Template cloned",
|
||||
text: "The template was cloned successfully.",
|
||||
icon: "success",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
|
||||
const cleanUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, cleanUrl);
|
||||
}
|
||||
$('#xlsTemplatesTable').DataTable({
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
ajax: 'load_templates.php',
|
||||
pageLength: 50,
|
||||
autoWidth: false,
|
||||
columns: [{
|
||||
data: 'id',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
title: "Actions",
|
||||
className: "table-actions text-center",
|
||||
render: function(data, type, row) {
|
||||
return `
|
||||
const cleanUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, cleanUrl);
|
||||
}
|
||||
const templatesTable = $('#xlsTemplatesTable').DataTable({
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
ajax: 'load_templates.php',
|
||||
pageLength: 50,
|
||||
autoWidth: false,
|
||||
columns: [{
|
||||
data: 'id',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
title: "Actions",
|
||||
className: "table-actions text-center",
|
||||
render: function(data, type, row) {
|
||||
return `
|
||||
<div class="d-flex justify-content-center gap-1">
|
||||
<a href="edit_template_xls.php?id=${data}" class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bx bx-edit-alt"></i>
|
||||
@@ -227,121 +285,169 @@
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'name',
|
||||
title: "Template Name",
|
||||
className: "name-cell"
|
||||
},
|
||||
{
|
||||
data: 'source_type',
|
||||
title: "Type",
|
||||
className: "text-center",
|
||||
render: function(data, type, row) {
|
||||
const sourceType = (data || 'XLS').toUpperCase();
|
||||
|
||||
if (type === 'display') {
|
||||
if (sourceType === 'API') {
|
||||
return '<span class="badge-source badge-source-api">API</span>';
|
||||
}
|
||||
return '<span class="badge-source badge-source-xls">XLS</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'name',
|
||||
title: "Template Name",
|
||||
className: "name-cell"
|
||||
},
|
||||
{
|
||||
data: 'source_type',
|
||||
title: "Type",
|
||||
className: "text-center",
|
||||
render: function(data, type, row) {
|
||||
let sourceType = (data || 'XLS').toUpperCase();
|
||||
|
||||
return sourceType;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'header_row',
|
||||
title: "Row",
|
||||
className: "text-center",
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
data: 'start_column',
|
||||
title: "Col",
|
||||
className: "text-center",
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
data: 'description',
|
||||
title: "Description",
|
||||
className: "description-cell",
|
||||
defaultContent: 'No description'
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
title: "Client",
|
||||
className: "client-cell",
|
||||
render: function(data, type, row) {
|
||||
const clientName = row.clientname || "No client";
|
||||
const clientId = row.idclient || "N/A";
|
||||
return `${clientName} <small class="text-muted">(ID: ${clientId})</small>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'button_label',
|
||||
title: "Button",
|
||||
className: "button-cell",
|
||||
defaultContent: 'Click Me'
|
||||
},
|
||||
{
|
||||
data: 'status',
|
||||
title: "Status",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
className: "text-center",
|
||||
render: function(status, type, row) {
|
||||
let checked = (status === "active") ? "checked" : "";
|
||||
return `
|
||||
// Treat JSON as API group for dashboard filter
|
||||
if (sourceType === 'JSON') {
|
||||
sourceType = 'API';
|
||||
}
|
||||
|
||||
if (type === 'display') {
|
||||
if (sourceType === 'API') {
|
||||
return '<span class="badge-source badge-source-api">JSON/API</span>';
|
||||
}
|
||||
|
||||
if (sourceType === 'PDF') {
|
||||
return '<span class="badge-source badge-source-pdf">PDF</span>';
|
||||
}
|
||||
|
||||
return '<span class="badge-source badge-source-xls">XLS</span>';
|
||||
}
|
||||
|
||||
return sourceType;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'header_row',
|
||||
title: "Row",
|
||||
className: "text-center",
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
data: 'start_column',
|
||||
title: "Col",
|
||||
className: "text-center",
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
data: 'description',
|
||||
title: "Description",
|
||||
className: "description-cell",
|
||||
defaultContent: 'No description'
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
title: "Client",
|
||||
className: "client-cell",
|
||||
render: function(data, type, row) {
|
||||
const clientName = row.clientname || "No client";
|
||||
const clientId = row.idclient || "N/A";
|
||||
return `${clientName} <small class="text-muted">(ID: ${clientId})</small>`;
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'button_label',
|
||||
title: "Button",
|
||||
className: "button-cell",
|
||||
defaultContent: 'Click Me'
|
||||
},
|
||||
{
|
||||
data: 'status',
|
||||
title: "Status",
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
className: "text-center",
|
||||
render: function(status, type, row) {
|
||||
let checked = (status === "active") ? "checked" : "";
|
||||
return `
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="toggle-status" data-id="${row.id}" ${checked}>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
}
|
||||
],
|
||||
dom: '<"card-header border-bottom p-3"<"d-flex align-items-center"<"card-title mb-0 flex-grow-1"f>>>rt<"card-footer border-top p-3"<"d-flex align-items-center"<"me-auto"l><"d-flex gap-2"ip>>>',
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
order: [
|
||||
[1, 'asc']
|
||||
],
|
||||
language: {
|
||||
search: "Cerca:",
|
||||
lengthMenu: "Mostra _MENU_ elementi",
|
||||
info: "Visualizzando da _START_ a _END_ di _TOTAL_ elementi",
|
||||
paginate: {
|
||||
first: "<?= isset($langdatatables['paginate_first']) ? $langdatatables['paginate_first'] : 'Primo' ?>",
|
||||
last: "<?= isset($langdatatables['paginate_last']) ? $langdatatables['paginate_last'] : 'Ultimo' ?>",
|
||||
next: "<?= isset($langdatatables['paginate_next']) ? $langdatatables['paginate_next'] : 'Successivo' ?>",
|
||||
previous: "<?= isset($langdatatables['paginate_previous']) ? $langdatatables['paginate_previous'] : 'Precedente' ?>"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
dom: '<"card-header border-bottom p-3"<"d-flex align-items-center"<"card-title mb-0 flex-grow-1"f>>>rt<"card-footer border-top p-3"<"d-flex align-items-center"<"me-auto"l><"d-flex gap-2"ip>>>',
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
order: [
|
||||
[1, 'asc']
|
||||
],
|
||||
language: {
|
||||
search: "Cerca:",
|
||||
lengthMenu: "Mostra _MENU_ elementi",
|
||||
info: "Visualizzando da _START_ a _END_ di _TOTAL_ elementi",
|
||||
paginate: {
|
||||
first: "<?= isset($langdatatables['paginate_first']) ? $langdatatables['paginate_first'] : 'Primo' ?>",
|
||||
last: "<?= isset($langdatatables['paginate_last']) ? $langdatatables['paginate_last'] : 'Ultimo' ?>",
|
||||
next: "<?= isset($langdatatables['paginate_next']) ? $langdatatables['paginate_next'] : 'Successivo' ?>",
|
||||
previous: "<?= isset($langdatatables['paginate_previous']) ? $langdatatables['paginate_previous'] : 'Precedente' ?>"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
const activeSourceTypes = {
|
||||
XLS: true,
|
||||
API: true,
|
||||
PDF: true
|
||||
};
|
||||
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "This action cannot be undone!",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#d33",
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
cancelButtonText: "Cancel"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `delete_template_xls.php?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
||||
if (settings.nTable.id !== 'xlsTemplatesTable') {
|
||||
return true;
|
||||
}
|
||||
|
||||
function confirmClone(id, templateName) {
|
||||
Swal.fire({
|
||||
title: "Clone template?",
|
||||
html: `
|
||||
const api = new $.fn.dataTable.Api(settings);
|
||||
const rowData = api.row(dataIndex).data();
|
||||
|
||||
let sourceType = ((rowData && rowData.source_type) ? rowData.source_type : 'XLS').toUpperCase();
|
||||
|
||||
if (sourceType === 'JSON') {
|
||||
sourceType = 'API';
|
||||
}
|
||||
|
||||
return activeSourceTypes[sourceType] === true;
|
||||
});
|
||||
|
||||
$('.type-filter-btn').on('click', function() {
|
||||
const type = $(this).data('type');
|
||||
|
||||
activeSourceTypes[type] = !activeSourceTypes[type];
|
||||
$(this).toggleClass('active', activeSourceTypes[type]);
|
||||
|
||||
const hasAtLeastOneActive = Object.values(activeSourceTypes).some(Boolean);
|
||||
|
||||
if (!hasAtLeastOneActive) {
|
||||
activeSourceTypes[type] = true;
|
||||
$(this).addClass('active');
|
||||
}
|
||||
|
||||
$('#xlsTemplatesTable').DataTable().draw();
|
||||
});
|
||||
});
|
||||
|
||||
function confirmDelete(id) {
|
||||
Swal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "This action cannot be undone!",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#d33",
|
||||
cancelButtonColor: "#3085d6",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
cancelButtonText: "Cancel"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `delete_template_xls.php?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function confirmClone(id, templateName) {
|
||||
Swal.fire({
|
||||
title: "Clone template?",
|
||||
html: `
|
||||
<div class="text-start">
|
||||
<p class="mb-2">You are about to clone this template:</p>
|
||||
<strong>${templateName}</strong>
|
||||
@@ -350,45 +456,45 @@
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#ffc107",
|
||||
cancelButtonColor: "#6c757d",
|
||||
confirmButtonText: "Yes, clone it",
|
||||
cancelButtonText: "Cancel"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `clone_template.php?id=${id}`;
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#ffc107",
|
||||
cancelButtonColor: "#6c757d",
|
||||
confirmButtonText: "Yes, clone it",
|
||||
cancelButtonText: "Cancel"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `clone_template.php?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", ".toggle-status", function() {
|
||||
let templateId = $(this).data("id");
|
||||
let newStatus = $(this).is(":checked") ? "active" : "inactive";
|
||||
$(document).on("change", ".toggle-status", function() {
|
||||
let templateId = $(this).data("id");
|
||||
let newStatus = $(this).is(":checked") ? "active" : "inactive";
|
||||
|
||||
$.ajax({
|
||||
url: "update_template_status.php",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
id: templateId,
|
||||
status: newStatus
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
console.log("Status updated successfully.");
|
||||
} else {
|
||||
console.error("Error updating status:", response.message);
|
||||
alert("Error updating status: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error("AJAX error:", xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
$.ajax({
|
||||
url: "update_template_status.php",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
id: templateId,
|
||||
status: newStatus
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
console.log("Status updated successfully.");
|
||||
} else {
|
||||
console.error("Error updating status:", response.message);
|
||||
alert("Error updating status: " + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.error("AJAX error:", xhr.responseText);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user