Compare commits

..

No commits in common. "main" and "feature/milestone3" have entirely different histories.

39 changed files with 989 additions and 4538 deletions

5
.gitignore vendored
View File

@ -40,13 +40,10 @@ yarn-error.log
/public/userarea/logaspi/ /public/userarea/logaspi/
/public/userarea/logs/api/ /public/userarea/logs/api/
/public/userarea/logs/api/** /public/userarea/logs/api/**
/public/userarea/logs/
/public/userarea/logs/**
/public/userarea/photostrf/ /public/userarea/photostrf/
/public/userarea/class/*.log /public/userarea/class/*.log
/public/userarea/class/curl_auth_debug.log /public/userarea/class/curl_auth_debug.log
/public/userarea/class/curl_request_debug.log /public/userarea/class/curl_request_debug.log
/public/userarea/schema_dettagli_response.json
# File XLSX temporanei importati # File XLSX temporanei importati
/public/userarea/imported_trf/*.xlsx /public/userarea/imported_trf/*.xlsx
@ -58,5 +55,3 @@ yarn-error.log
# Ignora tutti i log ovunque # Ignora tutti i log ovunque
*.log *.log
public/userarea/cache/

View File

@ -3,9 +3,6 @@ ob_start();
session_start(); session_start();
require_once '../../vendor/autoload.php'; require_once '../../vendor/autoload.php';
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []]; $response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
try { try {

View File

@ -255,39 +255,4 @@ class VisualLimsApiClient
{ {
return $this->baseUrl; return $this->baseUrl;
} }
/**
* Recupera contenuto binario - Adattato per https://bvcpsitaly-elims.com/limsapi
*/
public function getRaw($endpoint)
{
$token = $this->getToken();
// IMPORTANTE: usa /odata/ e NON /api/odata/
$url = "{$this->baseUrl}/odata/{$endpoint}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer {$token}",
"Accept: */*"
]);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($response === false) {
throw new Exception("Errore cURL: " . $curl_error);
}
if ($http_code !== 200) {
throw new Exception("HTTP {$http_code} su endpoint: " . $url);
}
return $response;
}
} }

View File

@ -3,9 +3,6 @@ require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
use Dotenv\Dotenv; use Dotenv\Dotenv;
Dotenv::createImmutable(dirname(__DIR__, 3))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
class DBHandlerSelect class DBHandlerSelect
{ {
private static $instance = null; private static $instance = null;

View File

@ -1,131 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$data = json_decode(file_get_contents('php://input'), true);
$sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0;
$targetList = $data['target_iddatadb_list'] ?? [];
$targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) {
return $v > 0 && $v !== $sourceIddatadb;
})));
if ($sourceIddatadb <= 0 || empty($targetIds)) {
echo json_encode([
'success' => false,
'message' => 'Missing source or target records'
]);
exit;
}
try {
$pdo->beginTransaction();
// 1. Load source parts
$stmtParts = $pdo->prepare("
SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry
FROM identification_parts
WHERE iddatadb = ?
ORDER BY part_number ASC, id ASC
");
$stmtParts->execute([$sourceIddatadb]);
$sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC);
if (empty($sourceParts)) {
$pdo->rollBack();
echo json_encode([
'success' => false,
'message' => 'No parts found for source record'
]);
exit;
}
// 2. Prepare statements
$stmtInsertPart = $pdo->prepare("
INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
VALUES
(:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())
");
$stmtLoadCF = $pdo->prepare("
SELECT field_id, value_id, value_text
FROM identification_parts_customfields
WHERE part_id = ?
ORDER BY id ASC
");
$stmtInsertCF = $pdo->prepare("
INSERT INTO identification_parts_customfields
(part_id, field_id, value_id, value_text, created_at, updated_at)
VALUES
(:part_id, :field_id, :value_id, :value_text, NOW(), NOW())
");
$details = [];
$totalClonedParts = 0;
// 3. Clone source parts to each target record
foreach ($targetIds as $targetIddatadb) {
$clonedCountForTarget = 0;
foreach ($sourceParts as $part) {
$stmtInsertPart->execute([
':iddatadb' => $targetIddatadb,
':part_number' => $part['part_number'],
':part_description' => $part['part_description'],
':mix' => $part['mix'] ?? 'N',
':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null,
':note' => $part['note'] ?? null,
':dateexpiry' => $part['dateexpiry'] ?? null,
]);
$newPartId = (int)$pdo->lastInsertId();
// Load source custom fields for this part
$stmtLoadCF->execute([(int)$part['id']]);
$customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC);
foreach ($customFields as $cf) {
$stmtInsertCF->execute([
':part_id' => $newPartId,
':field_id' => (int)$cf['field_id'],
':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null,
':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null,
]);
}
$clonedCountForTarget++;
$totalClonedParts++;
}
$details[] = [
'target_iddatadb' => $targetIddatadb,
'cloned_parts' => $clonedCountForTarget
];
}
$pdo->commit();
echo json_encode([
'success' => true,
'source_iddatadb' => $sourceIddatadb,
'cloned_targets' => count($targetIds),
'total_cloned_parts' => $totalClonedParts,
'details' => $details
]);
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
echo json_encode([
'success' => false,
'message' => 'Clone failed: ' . $e->getMessage()
]);
}

View File

@ -17,8 +17,6 @@ try {
exit(1); exit(1);
} }
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Recupera le variabili d'ambiente // Recupera le variabili d'ambiente
$dbHost = $_ENV['DB_HOST']; $dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE']; $dbName = $_ENV['DB_DATABASE'];

View File

@ -1,112 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
ini_set('display_errors', '0');
error_reporting(E_ALL);
/**
* Uso:
* download_rapporto_file.php?id=123
*/
try {
$api = VisualLimsApiClient::getInstance();
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if (!$id) {
throw new Exception("ID RapportoFile mancante");
}
$debugDir = __DIR__ . '/logs/lims_get_commessa/';
if (!is_dir($debugDir)) {
mkdir($debugDir, 0755, true);
}
/**
* Recupero il record RapportoFile.
*/
$data = $api->get("RapportoFile({$id})", []);
file_put_contents(
$debugDir . "rapporto_file_{$id}.json",
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
/**
* Provo vari nomi campo possibili per il contenuto PDF.
* Dopo il primo test potremo adattarlo al nome esatto.
*/
$possibleContentFields = [
'File',
'file',
'Content',
'content',
'FileContent',
'fileContent',
'Contenuto',
'contenuto',
'Bytes',
'bytes'
];
$base64 = null;
foreach ($possibleContentFields as $field) {
if (!empty($data[$field])) {
$base64 = $data[$field];
break;
}
}
if (!$base64) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'message' => 'Record RapportoFile recuperato, ma non ho trovato un campo base64 del PDF.',
'hint' => 'Apri logs/lims_get_commessa/rapporto_file_' . $id . '.json e controlla il nome reale del campo file.',
'data' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Se il contenuto ha prefisso data URI, lo pulisco.
*/
if (strpos($base64, 'base64,') !== false) {
$parts = explode('base64,', $base64);
$base64 = end($parts);
}
$pdfBinary = base64_decode($base64, true);
if ($pdfBinary === false) {
throw new Exception("Il contenuto trovato non è un base64 valido");
}
$filename = $data['NomeFile']
?? $data['nomeFile']
?? $data['FileName']
?? $data['fileName']
?? "rapporto_{$id}.pdf";
if (!str_ends_with(strtolower($filename), '.pdf')) {
$filename .= '.pdf';
}
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
header('Content-Length: ' . strlen($pdfBinary));
echo $pdfBinary;
} catch (Exception $e) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -25,41 +25,6 @@ if (!$template) {
$stmt = $pdo->prepare("SELECT * FROM routine"); $stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute(); $stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC); $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
$buttonBgPalette = [
'#0d6efd' => 'Blue',
'#6610f2' => 'Indigo',
'#6f42c1' => 'Purple',
'#d63384' => 'Pink',
'#dc3545' => 'Red',
'#fd7e14' => 'Orange',
'#ffc107' => 'Yellow',
'#198754' => 'Green',
'#20c997' => 'Teal',
'#0dcaf0' => 'Cyan',
'#212529' => 'Dark',
'#6c757d' => 'Gray',
'#495057' => 'Slate',
'#795548' => 'Brown',
'#2f5d50' => 'Sage Green',
];
$buttonTextPalette = [
'#ffffff' => 'White',
'#6c757d' => 'Gray',
'#000000' => 'Black',
];
$currentButtonBgColor = strtolower($template['button_bg_color'] ?? '#007bff');
$currentButtonTextColor = strtolower($template['button_text_color'] ?? '#ffffff');
if (!array_key_exists($currentButtonBgColor, array_change_key_case($buttonBgPalette, CASE_LOWER))) {
$currentButtonBgColor = '#0d6efd';
}
if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonTextPalette, CASE_LOWER))) {
$currentButtonTextColor = '#ffffff';
}
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@ -70,64 +35,6 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" /> <link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<?php include('cssinclude.php'); ?> <?php include('cssinclude.php'); ?>
<style>
.color-palette {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.color-option {
position: relative;
cursor: pointer;
}
.color-option input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.color-swatch {
width: 42px;
height: 42px;
border-radius: 10px;
border: 2px solid #d6dbe0;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
transition: all 0.18s ease;
}
.color-option input:checked+.color-swatch {
border: 3px solid #111827;
transform: scale(1.08);
box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.18);
}
.color-swatch .check-mark {
display: none;
font-size: 18px;
font-weight: 700;
color: #ffffff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.color-option input:checked+.color-swatch .check-mark {
display: inline-block;
}
.color-name {
display: block;
font-size: 11px;
text-align: center;
margin-top: 4px;
color: #555;
max-width: 55px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
@ -216,46 +123,12 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Background Color</label> <label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
<div class="color-palette">
<?php foreach ($buttonBgPalette as $colorValue => $colorLabel): ?>
<?php $isChecked = strtolower($colorValue) === $currentButtonBgColor; ?>
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
<input
type="radio"
name="button_bg_color"
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
<?= $isChecked ? 'checked' : ''; ?>>
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
<span class="check-mark"></span>
</span>
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
</label>
<?php endforeach; ?>
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Button Text Color</label> <label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
<div class="color-palette">
<?php foreach ($buttonTextPalette as $colorValue => $colorLabel): ?>
<?php $isChecked = strtolower($colorValue) === $currentButtonTextColor; ?>
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
<input
type="radio"
name="button_text_color"
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
<?= $isChecked ? 'checked' : ''; ?>>
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
<span class="check-mark"></span>
</span>
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
</label>
<?php endforeach; ?>
</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -385,16 +258,6 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
sourceType.addEventListener('change', updateSourceFields); sourceType.addEventListener('change', updateSourceFields);
updateSourceFields(); updateSourceFields();
function formatClientLabel(client) {
const nome = client.Nominativo || "Name not available";
const id = client.IdCliente || "";
const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
const suffix = (codiceCliente.split("_")[1] || "").trim();
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
return `${nome.trim()} - ${shortCode} (ID: ${id})`;
}
async function loadClients() { async function loadClients() {
try { try {
clientLoadingStatus.style.display = 'inline'; clientLoadingStatus.style.display = 'inline';
@ -417,8 +280,9 @@ if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonText
select.innerHTML = '<option value="">Select a client...</option>'; select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => { data.value.forEach(client => {
const nome = client.Nominativo || "Name not available";
const id = client.IdCliente || ""; const id = client.IdCliente || "";
const option = new Option(formatClientLabel(client), id); const option = new Option(`${nome.trim()} (ID: ${id})`, id);
if (parseInt(id) === parseInt(selectedClientId)) { if (parseInt(id) === parseInt(selectedClientId)) {
option.selected = true; option.selected = true;

View File

@ -328,6 +328,3 @@
2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"} 2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"} 2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"} 2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function

View File

@ -542,23 +542,7 @@ try {
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT); "RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt"; $logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
$writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})"); $writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
// 🔹 STEP 10.1: Save final CodiceCommessa into datadb.commessaweb
// After ImportaCommessa, the API returns the final LIMS job code in CodiceCommessa.
// Example: CodiceCommessa = 2614795, CodiceCommessaWeb = 26C0103.
$finalCodiceCommessa = trim((string)($commessaAfterPatch['CodiceCommessa'] ?? ''));
if ($finalCodiceCommessa !== '') {
$stmt = $pdo->prepare("
UPDATE datadb
SET commessaweb = :commessaweb,
status = 'l'
WHERE iddatadb = :iddatadb
");
$stmt->execute([
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
'iddatadb' => $iddatadb
]);
}
// 🔹 STEP 11: Prepare final response // 🔹 STEP 11: Prepare final response
$finalCommessa = [ $finalCommessa = [
"Cliente" => $clienteId, "Cliente" => $clienteId,
@ -573,7 +557,7 @@ try {
echo json_encode([ echo json_encode([
"success" => true, "success" => true,
"idcommessaweb" => $commessaId, "idcommessaweb" => $commessaId,
"commessaweb" => $finalCodiceCommessa ?: $commessaWebCode, "commessaweb" => $commessaWebCode,
"commessaWeb" => $finalCommessa, "commessaWeb" => $finalCommessa,
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug "commessaWebApiResponse" => $commessaWeb, // Incluso per debug
"totalCampioni" => count($campioni), "totalCampioni" => count($campioni),

View File

@ -1,95 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Campione hardcoded del tuo esempio
$idCampione = 749027;
// Analisi che stai provando ad assegnare
$targetRecordKey = '11;1218320';
$targetIdAnalisi = '1218320';
// Endpoint: recupera il campione espandendo le analisi abilitate
$endpoint = "Campione($idCampione)";
// ATTENZIONE:
// nella tua classe normalmente le opzioni vengono trasformate in query string.
// Qui serve: ?$expand=AnalisiAbilitate
$options = [
'$expand' => 'AnalisiAbilitate'
];
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$query = urldecode($query); // rende leggibile $expand invece di %24expand
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url_analisi_abilitate.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Recupera AnalisiAbilitate dalla risposta
$analisiAbilitate = $data['AnalisiAbilitate'] ?? [];
// Alcune API OData possono restituire collection dentro "value"
if (isset($analisiAbilitate['value']) && is_array($analisiAbilitate['value'])) {
$analisiAbilitate = $analisiAbilitate['value'];
}
// Cerca se il RecordKey / IdAnalisi che stai usando è effettivamente assegnabile
$matches = [];
foreach ($analisiAbilitate as $analisi) {
$recordKey = isset($analisi['RecordKey']) ? (string)$analisi['RecordKey'] : '';
$idAnalisi = isset($analisi['IdAnalisi']) ? (string)$analisi['IdAnalisi'] : '';
if ($recordKey === $targetRecordKey || $idAnalisi === $targetIdAnalisi) {
$matches[] = $analisi;
}
}
// Output diagnostico
$output = [
'success' => true,
'idCampione' => $idCampione,
'request_url' => $full_url,
'targetRecordKey' => $targetRecordKey,
'targetIdAnalisi' => $targetIdAnalisi,
'enabled_analyses_count' => is_array($analisiAbilitate) ? count($analisiAbilitate) : 0,
'target_found' => count($matches) > 0,
'target_matches' => $matches,
'analisi_abilitate' => $analisiAbilitate,
'raw_response' => $data
];
// Salva il JSON in locale
file_put_contents(
__DIR__ . '/analisi_abilitate_campione_749027_response.json',
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_analisi_abilitate.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -1,88 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Campione hardcoded del tuo esempio
$idCampione = 749027;
// Matrice attesa dal log STEP 6
$expectedMatriceId = 6430;
// Endpoint con expand della Matrice
$endpoint = "Campione($idCampione)";
$options = [
'$expand' => 'Matrice'
];
// Debug URL
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$queryReadable = urldecode($query);
$full_url = $base_url . $endpoint . ($queryReadable ? '?' . $queryReadable : '');
file_put_contents(
__DIR__ . '/last_url_check_matrice.txt',
$full_url . PHP_EOL,
FILE_APPEND
);
// Chiamata API
$data = $api->get($endpoint, $options);
// Recupero Matrice dalla response
$matrice = $data['Matrice'] ?? null;
$actualMatriceId = null;
if (is_array($matrice)) {
// Provo i nomi più probabili
$actualMatriceId = $matrice['IdMatrice']
?? $matrice['idMatrice']
?? $matrice['Id']
?? $matrice['ID']
?? null;
}
$matrice_ok = ((int)$actualMatriceId === (int)$expectedMatriceId);
$output = [
'success' => true,
'idCampione' => $idCampione,
'expectedMatriceId' => $expectedMatriceId,
'actualMatriceId' => $actualMatriceId,
'matrice_ok' => $matrice_ok,
'request_url' => $full_url,
'matrice' => $matrice,
'raw_response' => $data
];
// Salva JSON completo
file_put_contents(
__DIR__ . '/check_matrice_campione_749027_response.json',
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
file_put_contents(
__DIR__ . '/error_log_check_matrice.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -1,256 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
/**
* Uso:
* rapporto_full_by_codice.php?codice=2621521
* rapporto_full_by_codice.php?codice=2621521&download=1
*/
try {
$api = VisualLimsApiClient::getInstance();
$codiceRapporto = trim($_GET['codice'] ?? '');
$downloadPdf = isset($_GET['download']) && $_GET['download'] == '1';
if ($codiceRapporto === '') {
throw new Exception("Parametro codice mancante. Usa ?codice=2621521");
}
$debugDir = __DIR__ . '/logs/lims_rapporti/';
$pdfDir = __DIR__ . '/pdf/rapporti/';
if (!is_dir($debugDir)) mkdir($debugDir, 0755, true);
if (!is_dir($pdfDir)) mkdir($pdfDir, 0755, true);
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
/**
* STEP 1 - Search IdRapporto
*/
$searchParams = [
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
'$top' => 1
];
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
$searchData = $api->get($searchEndpoint);
$rapporti = $searchData['value'] ?? [];
if (empty($rapporti)) {
echo json_encode([
'success' => false,
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
'codice_rapporto' => $codiceRapporto
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
$rapportoBase = $rapporti[0];
$idRapporto = intval($rapportoBase['IdRapporto'] ?? 0);
if (!$idRapporto) {
throw new Exception("IdRapporto non trovato nella risposta.");
}
/**
* STEP 2 - Dettaglio Rapporto
*/
$clienteExpandError = null;
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=Cliente,RapportiFiles,CampioniDatiRapporto";
try {
$detailData = $api->get($detailEndpoint);
} catch (Exception $e) {
$clienteExpandError = $e->getMessage();
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=RapportiFiles,CampioniDatiRapporto";
$detailData = $api->get($detailEndpoint);
}
file_put_contents(
$debugDir . "rapporto_{$codiceRapportoSafe}_light.json",
json_encode($detailData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
);
$cliente = $detailData['Cliente'] ?? null;
$rapportiFiles = $detailData['RapportiFiles'] ?? [];
$campioniDatiRapporto = $detailData['CampioniDatiRapporto'] ?? [];
// ====================== CLIENTE ======================
$clienteInfo = [
'found' => is_array($cliente),
'id_cliente' => $cliente['IdCliente'] ?? $cliente['Id'] ?? null,
'codice_cliente' => $cliente['CodiceCliente'] ?? $cliente['Codice'] ?? null,
'nome_cliente' => $cliente['Nominativo'] ?? $cliente['RagioneSociale'] ?? $cliente['Nome'] ?? $cliente['Descrizione'] ?? null,
'partita_iva' => $cliente['PartitaIva'] ?? null,
'codice_fiscale' => $cliente['CodiceFiscale'] ?? null
];
// ====================== FILE PDF ======================
$selectedRapportoFile = null;
$idRapportoFile = null;
$pdfFileName = $codiceRapporto . '.pdf';
$pdfFullPath = $pdfDir . $pdfFileName;
if (is_array($rapportiFiles) && count($rapportiFiles) > 0) {
foreach ($rapportiFiles as $file) {
$fileName = $file['FileName'] ?? '';
$tipoRapporto = $file['TipoRapporto'] ?? '';
$categoria = $file['Categoria'] ?? '';
if (
stripos($fileName, '.pdf') !== false ||
stripos($tipoRapporto, 'Rapporto') !== false ||
stripos($categoria, 'Rapporti') !== false
) {
$selectedRapportoFile = $file;
$idRapportoFile = $file['IdRapportoFile'] ?? null;
break;
}
}
if (!$selectedRapportoFile && count($rapportiFiles) > 0) {
$selectedRapportoFile = $rapportiFiles[0];
$idRapportoFile = $rapportiFiles[0]['IdRapportoFile'] ?? null;
}
}
// ====================== DOWNLOAD PDF - DEBUG AVANZATO ======================
$pdfDownloaded = false;
$pdfError = null;
$endpointUsato = null;
if ($downloadPdf && $idRapportoFile) {
$tentativi = [
"RapportoFile({$idRapportoFile})/\$value",
"RapportoFile({$idRapportoFile})/Contenuto/\$value",
"RapportoFile({$idRapportoFile})/File/\$value",
"Rapporto({$idRapporto})/RapportiFiles({$idRapportoFile})/\$value",
"Rapporto({$idRapporto})/RapportoFile/\$value",
"GetRapportoFile?IdRapportoFile={$idRapportoFile}",
"RapportoFile/Download?Id={$idRapportoFile}",
"Rapporto/DownloadFile?IdRapporto={$idRapporto}"
];
foreach ($tentativi as $ep) {
try {
$pdfContent = $api->getRaw($ep);
if (strlen($pdfContent) > 2000) { // PDF decente deve essere più grande
file_put_contents($pdfFullPath, $pdfContent);
$pdfDownloaded = true;
$endpointUsato = $ep;
$pdfError = "SUCCESSO con: " . $ep;
break;
} else {
$pdfError = "Contenuto troppo piccolo con: " . $ep;
}
} catch (Exception $e) {
$pdfError = "Fallito con " . $ep . "" . $e->getMessage();
}
}
}
// ====================== CAMPIONI + RATING (ripristinato dal tuo originale) ======================
$campioniSintesi = [];
$ratingFinale = null;
$ratingSource = null;
$hasIrregolare = false;
$hasPositiveResults = false;
if (is_array($campioniDatiRapporto)) {
foreach ($campioniDatiRapporto as $campione) {
$giudizioRapporto = $campione['GiudizioRapporto'] ?? null;
$giudizioCertificato = $campione['GiudizioCertificato'] ?? null;
$esitoGiudizioLMR = $campione['EsitoGiudizioLMR'] ?? null;
$esitoGiudizioImpiego = $campione['EsitoGiudizioImpiego'] ?? null;
$risultatiIrregolari = ($campione['RisultatiIrregolari'] ?? false) === true;
$risultatiPositivi = ($campione['RisultatiPositivi'] ?? false) === true;
$campioniSintesi[] = [
'id_campione_dati_rapporto' => $campione['IdCampioneDatiRapporto'] ?? null,
'codice_campione' => $campione['CodiceCampione'] ?? null,
'stato_campione' => $campione['StatoCampione'] ?? null,
'stato_campione_web' => $campione['StatoCampioneWeb'] ?? null,
'matrice' => $campione['Matrice'] ?? null,
'macro_matrice' => $campione['MacroMatrice'] ?? null,
'giudizio_rapporto' => $giudizioRapporto,
'giudizio_certificato' => $giudizioCertificato,
'esito_giudizio_lmr' => $esitoGiudizioLMR,
'esito_giudizio_impiego' => $esitoGiudizioImpiego,
'risultati_positivi' => $risultatiPositivi,
'risultati_irregolari' => $risultatiIrregolari
];
if (!$ratingFinale && !empty($giudizioRapporto)) {
$ratingFinale = $giudizioRapporto;
$ratingSource = 'CampioniDatiRapporto.GiudizioRapporto';
}
if (!$ratingFinale && !empty($giudizioCertificato)) {
$ratingFinale = $giudizioCertificato;
$ratingSource = 'CampioniDatiRapporto.GiudizioCertificato';
}
if ($risultatiIrregolari) $hasIrregolare = true;
if ($risultatiPositivi) $hasPositiveResults = true;
}
}
// Fallback rating
if (!$ratingFinale) {
if ($hasIrregolare) {
$ratingFinale = 'Irregolare';
$ratingSource = 'RisultatiIrregolari';
} elseif ($hasPositiveResults || count($campioniSintesi) > 0) {
$ratingFinale = 'Regolare';
$ratingSource = 'fallback';
}
}
// ====================== RISPOSTA FINALE ======================
echo json_encode([
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $idRapporto,
'search_endpoint' => $searchEndpoint,
'detail_endpoint' => $detailEndpoint,
'cliente_expand_error' => $clienteExpandError,
'rapporto_base' => $rapportoBase,
'cliente' => $clienteInfo,
'rating_finale' => $ratingFinale,
'rating_source' => $ratingSource,
'rapporti_files_count' => count($rapportiFiles),
'selected_rapporto_file' => $selectedRapportoFile,
'pdf_file_name' => $pdfFileName,
'pdf_path' => $pdfDownloaded ? $pdfFullPath : null,
'pdf_downloaded' => $pdfDownloaded,
'pdf_error' => $pdfError,
'campioni_count' => count($campioniSintesi),
'campioni_sintesi' => $campioniSintesi,
'download_requested' => $downloadPdf
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -1,118 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$commessa = trim($_GET['commessa'] ?? '');
if ($commessa === '') {
throw new Exception("Parametro commessa mancante");
}
$debugDir = __DIR__ . '/logs/lims_rapporto/';
if (!is_dir($debugDir)) {
mkdir($debugDir, 0755, true);
}
/**
* STEP 1
* Provo a cercare il rapporto con filtri leggeri.
* NIENTE expand pesanti.
*/
$attempts = [];
$filters = [
'CodiceCommessa' => "CodiceCommessa eq '{$commessa}'",
'Commessa_CodiceCommessa' => "Commessa/CodiceCommessa eq '{$commessa}'",
'Commessa_IdCommessa' => is_numeric($commessa) ? "Commessa/IdCommessa eq {$commessa}" : null,
'Codice' => "Codice eq '{$commessa}'"
];
foreach ($filters as $label => $filter) {
if (!$filter) {
continue;
}
try {
$options = [
'$filter' => $filter,
'$top' => 10
];
$data = $api->get('Rapporto', $options);
$attempts[$label] = [
'success' => true,
'filter' => $filter,
'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
'data' => $data
];
file_put_contents(
$debugDir . "rapporto_{$commessa}_{$label}.json",
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
);
} catch (Exception $e) {
$attempts[$label] = [
'success' => false,
'filter' => $filter,
'error' => $e->getMessage()
];
}
}
/**
* STEP 2
* Prendo solo eventuali rapporti trovati.
*/
$rapportiFound = [];
foreach ($attempts as $label => $attempt) {
if (!$attempt['success']) {
continue;
}
$items = $attempt['data']['value'] ?? [];
if (!is_array($items)) {
continue;
}
foreach ($items as $item) {
$rapportiFound[] = [
'matched_by' => $label,
'rapporto' => $item
];
}
}
echo json_encode([
'success' => true,
'input_commessa' => $commessa,
'message' => 'Ricerca leggera su Rapporto completata. Se trovi un rapporto, poi recuperiamo RapportiFiles solo per quello.',
'rapporti_found_count' => count($rapportiFound),
'rapporti_found' => $rapportiFound,
'attempts_summary' => array_map(function ($a) {
return [
'success' => $a['success'],
'filter' => $a['filter'],
'records' => $a['records'] ?? null,
'error' => $a['error'] ?? null
];
}, $attempts)
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -2,140 +2,25 @@
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php'; require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json');
ini_set('display_errors', '0'); ini_set('display_errors', '0');
error_reporting(E_ALL); error_reporting(E_ALL);
try { try {
$api = VisualLimsApiClient::getInstance(); $api = VisualLimsApiClient::getInstance();
$rapporto_id = 533329;
// Esempi: // Costruzione manuale dell'endpoint con espansione annidata
// rapporto_by_codice_expand_step.php?codice=2541111&step=base $endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
// rapporto_by_codice_expand_step.php?codice=2541111&step=files
// rapporto_by_codice_expand_step.php?codice=2541111&step=campioni
// rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
$codiceRapporto = trim($_GET['codice'] ?? ''); // Non passiamo options, già incluso nell'endpoint
$step = trim($_GET['step'] ?? 'base'); $data = $api->get($endpoint);
if ($codiceRapporto === '') { file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
throw new Exception("Parametro codice mancante. Usa ?codice=2541111"); echo json_encode($data);
}
$allowedSteps = [
'base' => '',
'files' => 'RapportiFiles',
'allegati' => 'RapportiAllegati',
'campioni' => 'CampioniDatiRapporto',
'files_campioni' => 'RapportiFiles,CampioniDatiRapporto'
];
if (!array_key_exists($step, $allowedSteps)) {
throw new Exception("Step non valido. Usa: " . implode(', ', array_keys($allowedSteps)));
}
// Escape OData per eventuali apostrofi
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
/*
* STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
* Query leggera, con $select e $top=1.
*/
$searchParams = [
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
'$top' => 1
];
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
$searchData = $api->get($searchEndpoint);
$items = $searchData['value'] ?? [];
if (!is_array($items) || count($items) === 0) {
echo json_encode([
'success' => false,
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
'codice_rapporto' => $codiceRapporto,
'search_endpoint' => $searchEndpoint,
'search_data' => $searchData
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
$rapportoBase = $items[0];
$rapportoId = intval($rapportoBase['IdRapporto'] ?? 0);
if (!$rapportoId) {
throw new Exception("IdRapporto non trovato nella risposta.");
}
/*
* STEP 2 - Se step=base, restituisco solo la ricerca base.
*/
if ($step === 'base') {
echo json_encode([
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $rapportoId,
'step' => $step,
'search_endpoint' => $searchEndpoint,
'rapporto_base' => $rapportoBase
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
/*
* STEP 3 - Espande SOLO il rapporto trovato.
*/
$expandValue = $allowedSteps[$step];
$detailParams = [
'$expand' => $expandValue
];
$detailEndpoint = "Rapporto({$rapportoId})?" . http_build_query($detailParams);
file_put_contents(
__DIR__ . '/last_rapporto_by_codice_expand_endpoint.txt',
'[' . date('Y-m-d H:i:s') . '] SEARCH: ' . $searchEndpoint . PHP_EOL .
'[' . date('Y-m-d H:i:s') . '] DETAIL: ' . $detailEndpoint . PHP_EOL,
FILE_APPEND
);
$detailData = $api->get($detailEndpoint);
file_put_contents(
__DIR__ . "/rapporto_codice_{$codiceRapportoSafe}_{$step}.json",
json_encode([
'search' => $searchData,
'detail' => $detailData
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
);
echo json_encode([
'success' => true,
'codice_rapporto' => $codiceRapporto,
'id_rapporto' => $rapportoId,
'step' => $step,
'search_endpoint' => $searchEndpoint,
'detail_endpoint' => $detailEndpoint,
'rapporto_base' => $rapportoBase,
'data' => $detailData
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) { } catch (Exception $e) {
file_put_contents( file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
__DIR__ . '/error_log.txt',
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
FILE_APPEND
);
http_response_code(500); http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} }

File diff suppressed because it is too large Load Diff

View File

@ -10,125 +10,65 @@
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style> <style>
/* Main buttons container */ /* Layout flessibile per gestire dimensioni diverse */
.template-buttons { #templateButtons {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12px; gap: 10px;
justify-content: flex-start; justify-content: flex-start;
padding: 10px 0; /* Allinea a sinistra */
padding: 20px;
} }
/* Button sizes */ /* Definizione delle dimensioni */
/* Definizione delle dimensioni */
.btn-small { .btn-small {
font-size: 12px; font-size: 12px;
padding: 6px 12px; padding: 6px 12px;
min-width: 100px; min-width: 100px;
min-height: 36px; min-height: 30px;
display: inline-flex; display: flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
gap: 8px; /* Aggiunto */
border-radius: 8px;
text-align: center;
} }
.btn-medium { .btn-medium {
font-size: 16px; font-size: 16px;
padding: 10px 20px; padding: 10px 20px;
min-width: 140px; min-width: 130px;
min-height: 48px; min-height: 45px;
display: inline-flex; display: flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
gap: 8px; /* Aggiunto */
border-radius: 10px;
text-align: center;
} }
.btn-large { .btn-large {
font-size: 20px; font-size: 20px;
padding: 14px 28px; padding: 14px 28px;
min-width: 190px; min-width: 180px;
min-height: 64px; min-height: 60px;
display: inline-flex; display: flex;
/* Aggiunto */
justify-content: center; justify-content: center;
/* Aggiunto */
align-items: center; align-items: center;
gap: 10px; /* Aggiunto */
border-radius: 12px;
text-align: center;
} }
.template-icon { /* Stile della barra di ricerca */
font-size: 18px;
line-height: 1;
}
.btn-large .template-icon {
font-size: 22px;
}
.btn-small .template-icon {
font-size: 15px;
}
/* Search box */
#searchInput { #searchInput {
width: 100%; width: 100%;
padding: 10px 14px; padding: 10px;
font-size: 16px; font-size: 16px;
margin-bottom: 18px; margin-bottom: 15px;
border: 1px solid #d9d9d9; border: 1px solid #ccc;
border-radius: 8px; border-radius: 5px;
outline: none;
}
#searchInput:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.15rem rgba(13, 110, 253, 0.15);
}
/* Tabs */
.custom-tabs {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 20px;
gap: 6px;
}
.custom-tabs .nav-link {
border: none;
border-radius: 10px 10px 0 0;
color: #555;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
}
.custom-tabs .nav-link:hover {
background: #f8f9fa;
color: #0d6efd;
}
.custom-tabs .nav-link.active {
background: #0d6efd;
color: #fff;
}
.tab-pane {
min-height: 140px;
}
.empty-message {
color: #6c757d;
font-style: italic;
padding: 10px 0;
}
.loading-message {
color: #6c757d;
padding: 10px 0;
} }
</style> </style>
</head> </head>
@ -147,54 +87,14 @@
<h6 class="mb-0">Active Templates</h6> <h6 class="mb-0">Active Templates</h6>
</div> </div>
<div class="card-body"> <div class="card-body">
<!-- Barra di ricerca -->
<input type="text" id="searchInput" placeholder="Search template..."> <input type="text" id="searchInput" placeholder="Search template...">
<div id="templateButtons"></div>
<ul class="nav nav-tabs custom-tabs" id="templateTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="xls-tab" data-bs-toggle="tab" data-bs-target="#xls-pane" type="button" role="tab" aria-controls="xls-pane" aria-selected="true">
<i class="bx bx-spreadsheet template-icon"></i>
XLS
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="api-tab" data-bs-toggle="tab" data-bs-target="#api-pane" type="button" role="tab" aria-controls="api-pane" aria-selected="false">
<i class="bx bx-transfer-alt template-icon"></i>
JSON/API
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pdf-tab" data-bs-toggle="tab" data-bs-target="#pdf-pane" type="button" role="tab" aria-controls="pdf-pane" aria-selected="false">
<i class="bx bx-file-pdf template-icon"></i>
PDF
</button>
</li>
</ul>
<div class="tab-content" id="templateTabsContent">
<div class="tab-pane fade show active" id="xls-pane" role="tabpanel" aria-labelledby="xls-tab">
<div id="templateButtonsXLS" class="template-buttons">
<div class="loading-message">Loading XLS templates...</div>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="api-pane" role="tabpanel" aria-labelledby="api-tab">
<div id="templateButtonsAPI" class="template-buttons">
<div class="loading-message">Loading API templates...</div>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="pdf-pane" role="tabpanel" aria-labelledby="pdf-tab">
<div id="templateButtonsPDF" class="template-buttons">
<div class="loading-message">Loading PDF templates...</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div> <div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
@ -204,137 +104,46 @@
<script> <script>
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
const allTemplates = [];
const containers = {
XLS: document.getElementById("templateButtonsXLS"),
API: document.getElementById("templateButtonsAPI"),
PDF: document.getElementById("templateButtonsPDF")
};
function getSizeClass(buttonSize) {
if (buttonSize === "small") return "btn-small";
if (buttonSize === "large") return "btn-large";
return "btn-medium";
}
function getTemplateIcon(sourceType) {
switch ((sourceType || '').toUpperCase()) {
case 'XLS':
return 'bx bx-spreadsheet';
case 'API':
return 'bx bx-transfer-alt';
case 'PDF':
return 'bx bx-file-pdf';
default:
return 'bx bx-file';
}
}
function createButton(template) {
const sizeClass = getSizeClass(template.button_size);
const sourceType = (template.source_type || '').toUpperCase();
const iconClass = getTemplateIcon(sourceType);
const btn = document.createElement("a");
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';
btn.setAttribute("data-label", (template.button_label || '').toLowerCase());
btn.setAttribute("data-source-type", sourceType);
btn.innerHTML = `
<i class="${iconClass} template-icon"></i>
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
`;
return btn;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function clearContainers() {
Object.values(containers).forEach(container => {
container.innerHTML = '';
});
}
function renderTemplates(searchValue = '') {
clearContainers();
const grouped = {
XLS: [],
API: [],
PDF: []
};
allTemplates.forEach(template => {
const sourceType = (template.source_type || '').toUpperCase();
const label = (template.button_label || '').toLowerCase();
if (searchValue && !label.includes(searchValue)) {
return;
}
if (grouped[sourceType]) {
grouped[sourceType].push(template);
}
});
Object.keys(grouped).forEach(type => {
const container = containers[type];
if (!container) return;
if (grouped[type].length === 0) {
container.innerHTML = `<div class="empty-message">No templates found in this section.</div>`;
return;
}
grouped[type].forEach(template => {
container.appendChild(createButton(template));
});
});
}
fetch("load_active_templates.php") fetch("load_active_templates.php")
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
console.error("Error loading templates:", data.message); console.error("Error loading templates:", data.message);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Error loading templates.</div>`;
});
return; return;
} }
if (!Array.isArray(data.data)) { let templateButtons = document.getElementById("templateButtons");
clearContainers(); templateButtons.innerHTML = '';
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Invalid response format.</div>`;
});
return;
}
allTemplates.push(...data.data); data.data.forEach(template => {
renderTemplates(); let sizeClass = "btn-medium"; // Default
if (template.button_size === "small") sizeClass = "btn-small";
if (template.button_size === "large") sizeClass = "btn-large";
let btn = document.createElement("a");
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color;
btn.style.color = template.button_text_color;
btn.textContent = template.button_label;
btn.setAttribute("data-label", template.button_label.toLowerCase()); // Attributo per ricerca
templateButtons.appendChild(btn);
});
}) })
.catch(error => { .catch(error => console.error("Fetch error:", error));
console.error("Fetch error:", error);
clearContainers();
Object.values(containers).forEach(container => {
container.innerHTML = `<div class="empty-message">Fetch error while loading templates.</div>`;
});
});
// Funzione per la ricerca live
document.getElementById("searchInput").addEventListener("input", function() { document.getElementById("searchInput").addEventListener("input", function() {
const searchValue = this.value.toLowerCase().trim(); let searchValue = this.value.toLowerCase();
renderTemplates(searchValue); document.querySelectorAll("#templateButtons a").forEach(btn => {
let label = btn.getAttribute("data-label");
if (label.includes(searchValue)) {
btn.style.display = "inline-block";
} else {
btn.style.display = "none";
}
});
}); });
}); });
</script> </script>

View File

@ -1,6 +1,10 @@
<?php <?php
include('include/headscript.php'); include('include/headscript.php');
// ✅ FIX timezone (Rome)
ini_set('date.timezone', 'Europe/Rome');
date_default_timezone_set('Europe/Rome');
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) { if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida")); header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
exit; exit;

View File

@ -40,22 +40,6 @@ error_log("Loaded template: " . print_r($template, true));
<?php include('cssinclude.php'); ?> <?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <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 { .table-container {
overflow-x: auto; overflow-x: auto;
max-width: 100%; max-width: 100%;
@ -340,12 +324,8 @@ error_log("Loaded template: " . print_r($template, true));
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button> <button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
</div> </div>
<div class="top-scrollbar" id="topTableScrollbar"> <div class="table-container">
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div> <table class="table table-striped table-bordered">
</div>
<div class="table-container" id="mainTableContainer">
<table class="table table-striped table-bordered" id="importPreviewTable">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" id="selectAll"> Seleziona</th> <th><input type="checkbox" id="selectAll"> Seleziona</th>
@ -383,47 +363,6 @@ error_log("Loaded template: " . print_r($template, true));
`; `;
tableContainer.innerHTML = html; tableContainer.innerHTML = html;
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 proceedButtonTop = document.getElementById('proceedButtonTop');
const proceedButtonBottom = document.getElementById('proceedButtonBottom'); const proceedButtonBottom = document.getElementById('proceedButtonBottom');
const selectAllCheckbox = document.getElementById('selectAll'); const selectAllCheckbox = document.getElementById('selectAll');

View File

@ -1,6 +1,10 @@
<?php <?php
include('include/headscript.php'); include('include/headscript.php');
// ✅ FIX timezone (Rome)
ini_set('date.timezone', 'Europe/Rome');
date_default_timezone_set('Europe/Rome');
$template_id = intval($_GET['id'] ?? 0); $template_id = intval($_GET['id'] ?? 0);
if (!$template_id) { if (!$template_id) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante")); header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
@ -198,13 +202,13 @@ foreach ($importedData as $index => $row) {
'status' => $row['status'] ?? 'i', 'status' => $row['status'] ?? 'i',
'idclient' => $row['idclient'] ?? $default_idclient, 'idclient' => $row['idclient'] ?? $default_idclient,
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null, 'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
'tested_component' => $row['tested_component'] ?? '',
'commessaweb' => $row['commessaweb'] ?? null, 'commessaweb' => $row['commessaweb'] ?? null,
'user_name' => $row['user_name'] ?? '', 'user_name' => $row['user_name'] ?? '',
'importreferencecode' => $row['importreferencecode'] ?? '', 'importreferencecode' => $row['importreferencecode'] ?? '',
'filename_import' => $row['filename_import'] ?? '', 'filename_import' => $row['filename_import'] ?? '',
'importdate' => $row['importdate'] ?? '', 'importdate' => $row['importdate'] ?? '',
]; ];
// Fixed fields // Fixed fields
$rowObj['fixedFields'] = []; $rowObj['fixedFields'] = [];
foreach ($fixedFields as $f) { foreach ($fixedFields as $f) {
@ -263,12 +267,7 @@ $gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_
// 5. Auto fields // 5. Auto fields
foreach ($allMappings as $mapping) { foreach ($allMappings as $mapping) {
if ( if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
!$mapping['is_manual']
&& $mapping['main_field'] != 1
&& $mapping['is_visible_import'] == 1
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
) {
$gridColumns[] = [ $gridColumns[] = [
'type' => 'detail', 'type' => 'detail',
'key' => (string)$mapping['id'], 'key' => (string)$mapping['id'],
@ -285,12 +284,7 @@ foreach ($allMappings as $mapping) {
// 6. Manual fields // 6. Manual fields
foreach ($allMappings as $mapping) { foreach ($allMappings as $mapping) {
if ( if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$mapping['is_manual']
&& $mapping['main_field'] != 1
&& $mapping['is_visible_import'] == 1
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
) {
$gridColumns[] = [ $gridColumns[] = [
'type' => 'detail', 'type' => 'detail',
'key' => (string)$mapping['id'], 'key' => (string)$mapping['id'],
@ -348,8 +342,8 @@ $gridMeta = [
?> ?>
<script> <script>
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>; window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>; window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
</script> </script>
<!doctype html> <!doctype html>
@ -370,10 +364,20 @@ $gridMeta = [
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
input.auto-input,
select.auto-input {
background-color: #d4edda;
}
input.manual-input,
select.manual-input {
background-color: #fff3cd;
}
input.required-input,
select.required-input {
background-color: #f8d7da;
}
input.required-input, input.required-input,
select.required-input { select.required-input {
@ -412,9 +416,17 @@ $gridMeta = [
outline: none !important; outline: none !important;
} }
textarea.auto-input {
background-color: #d4edda;
}
textarea.manual-input {
background-color: #fff3cd;
}
textarea.required-input {
background-color: #f8d7da;
}
.status-badge { .status-badge {
display: inline-block; display: inline-block;
@ -451,21 +463,6 @@ $gridMeta = [
position: relative; position: relative;
} }
.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;
}
.top-scrollbar-inner {
height: 1px;
}
.grid-row { .grid-row {
display: flex; display: flex;
align-items: center; align-items: center;
@ -800,7 +797,7 @@ $gridMeta = [
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
} }
#photosModal>.modal-content { #photosModal > .modal-content {
background-color: #fff; background-color: #fff;
margin: 5% auto; margin: 5% auto;
padding: 20px; padding: 20px;
@ -895,17 +892,9 @@ $gridMeta = [
} }
@keyframes new-row-pulse { @keyframes new-row-pulse {
0%, 100% { background-color: transparent; }
0%, 50% { background-color: #cce5ff; }
100% {
background-color: transparent;
} }
50% {
background-color: #cce5ff;
}
}
.row-just-created { .row-just-created {
animation: new-row-pulse 1s ease-in-out 3; animation: new-row-pulse 1s ease-in-out 3;
} }
@ -1022,7 +1011,11 @@ $gridMeta = [
font-style: italic; font-style: italic;
} }
.api-fixed-select.required-input:invalid,
.api-fixed-select[required]:not([value]):not([data-select2-id]) {
background-color: #f8d7da !important;
border-color: #dc3545 !important;
}
/* ── Pagination bar ── */ /* ── Pagination bar ── */
.pager-bar { .pager-bar {
@ -1036,25 +1029,21 @@ $gridMeta = [
font-size: 13px; font-size: 13px;
color: #495057; color: #495057;
} }
.pager-rows-per-page { .pager-rows-per-page {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.pager-label { .pager-label {
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.pager-limit-group { .pager-limit-group {
display: inline-flex; display: inline-flex;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
.pager-limit-btn { .pager-limit-btn {
padding: 3px 10px; padding: 3px 10px;
text-decoration: none; text-decoration: none;
@ -1063,41 +1052,24 @@ $gridMeta = [
transition: background .15s, color .15s; transition: background .15s, color .15s;
font-weight: 500; font-weight: 500;
} }
.pager-limit-btn:last-child { border-right: none; }
.pager-limit-btn:last-child { .pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
border-right: none;
}
.pager-limit-btn:hover {
background: #e9ecef;
text-decoration: none;
color: #212529;
}
.pager-limit-btn.active { .pager-limit-btn.active {
background: #0d6efd; background: #0d6efd;
color: #fff; color: #fff;
} }
.pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
.pager-limit-btn.active:hover {
background: #0b5ed7;
color: #fff;
}
.pager-nav { .pager-nav {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
} }
.pager-info { .pager-info {
margin-right: 8px; margin-right: 8px;
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
.pager-btn, .pager-num {
.pager-btn,
.pager-num {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -1111,152 +1083,21 @@ $gridMeta = [
font-weight: 500; font-weight: 500;
transition: background .15s, color .15s, border-color .15s; transition: background .15s, color .15s, border-color .15s;
} }
.pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
.pager-btn:hover,
.pager-num:hover {
background: #e9ecef;
text-decoration: none;
color: #212529;
}
.pager-num.active { .pager-num.active {
background: #0d6efd; background: #0d6efd;
color: #fff; color: #fff;
border-color: #0d6efd; border-color: #0d6efd;
} }
.pager-btn.disabled { .pager-btn.disabled {
pointer-events: none; pointer-events: none;
opacity: .4; opacity: .4;
} }
.pager-dots { .pager-dots {
padding: 0 2px; padding: 0 2px;
color: #adb5bd; color: #adb5bd;
user-select: none; user-select: none;
} }
/* =========================================================
FINAL GRID COLORS
Schema/customfield fields = green
Fixed/standard fields = white
Required fields = red border only
========================================================= */
/* Default: all fields white */
.grid-container input,
.grid-container select,
.grid-container textarea,
.grid-top input,
.grid-top select,
.grid-top textarea {
background-color: #ffffff !important;
color: #333 !important;
}
/* Schema/customfield fields: green background */
.grid-container .schema-field input,
.grid-container .schema-field select,
.grid-container .schema-field textarea,
.grid-top .schema-field input,
.grid-top .schema-field select,
.grid-top .schema-field textarea {
background-color: #dff3e3 !important;
}
/* Fixed and standard fields: white background */
.grid-container .fixed-field input,
.grid-container .fixed-field select,
.grid-container .fixed-field textarea,
.grid-container .standard-field input,
.grid-container .standard-field select,
.grid-container .standard-field textarea,
.grid-top .fixed-field input,
.grid-top .fixed-field select,
.grid-top .fixed-field textarea,
.grid-top .standard-field input,
.grid-top .standard-field select,
.grid-top .standard-field textarea {
background-color: #ffffff !important;
}
/* Required fields: red border only */
.grid-container .required-field input,
.grid-container .required-field select,
.grid-container .required-field textarea,
.grid-top .required-field input,
.grid-top .required-field select,
.grid-top .required-field textarea {
border: 2px solid #dc3545 !important;
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.15) !important;
}
/* Required schema/customfield fields: green + red border */
.grid-container .schema-field.required-field input,
.grid-container .schema-field.required-field select,
.grid-container .schema-field.required-field textarea,
.grid-top .schema-field.required-field input,
.grid-top .schema-field.required-field select,
.grid-top .schema-field.required-field textarea {
background-color: #dff3e3 !important;
border: 2px solid #dc3545 !important;
}
/* Required fixed/standard fields: white + red border */
.grid-container .fixed-field.required-field input,
.grid-container .fixed-field.required-field select,
.grid-container .fixed-field.required-field textarea,
.grid-container .standard-field.required-field input,
.grid-container .standard-field.required-field select,
.grid-container .standard-field.required-field textarea,
.grid-top .fixed-field.required-field input,
.grid-top .fixed-field.required-field select,
.grid-top .fixed-field.required-field textarea,
.grid-top .standard-field.required-field input,
.grid-top .standard-field.required-field select,
.grid-top .standard-field.required-field textarea {
background-color: #ffffff !important;
border: 2px solid #dc3545 !important;
}
/* Select2 - schema/customfield fields: green */
.grid-container .schema-field .select2-container--default .select2-selection--single,
.grid-top .schema-field .select2-container--default .select2-selection--single {
background-color: #dff3e3 !important;
}
/* Select2 - fixed/standard fields: white */
.grid-container .fixed-field .select2-container--default .select2-selection--single,
.grid-container .standard-field .select2-container--default .select2-selection--single,
.grid-top .fixed-field .select2-container--default .select2-selection--single,
.grid-top .standard-field .select2-container--default .select2-selection--single {
background-color: #ffffff !important;
}
/* Select2 - required fields: red border only */
.grid-container .required-field .select2-container--default .select2-selection--single,
.grid-top .required-field .select2-container--default .select2-selection--single {
border: 2px solid #dc3545 !important;
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.15) !important;
}
/* Remove old red background from required classes */
.grid-container input.required-input,
.grid-container select.required-input,
.grid-container textarea.required-input,
.grid-top input.required-input,
.grid-top select.required-input,
.grid-top textarea.required-input {
background-color: inherit !important;
}
/* Missing required cell: red outline only */
.grid-cell.missing-required {
background-color: inherit !important;
border-right: 1px solid #dee2e6 !important;
outline: 2px solid #dc3545 !important;
outline-offset: -2px;
}
</style> </style>
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title> <title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head> </head>
@ -1338,9 +1179,9 @@ $gridMeta = [
<i class="fas fa-cogs"></i> Actions <i class="fas fa-cogs"></i> Actions
</button> </button>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<?php if ((Auth::user()->hasRole('Admin'))) : ?>
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li> <li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
<?php endif; ?>
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li> <li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
</ul> </ul>
</div> </div>
@ -1360,18 +1201,13 @@ $gridMeta = [
</div> </div>
<div class="card-body"> <div class="card-body">
<form id="editForm"> <form id="editForm">
<div class="top-scrollbar" id="topScrollbar"> <div class="grid-container">
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
</div>
<div class="grid-container" id="gridContainer">
<div class="grid-top" id="gridTopContainer"></div> <div class="grid-top" id="gridTopContainer"></div>
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div> <div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
<div id="gridRowContainer"></div> <div id="gridRowContainer"></div>
</div> </div>
</form> </form>
<div id="partsModalContainer"></div> <div id="partsModalContainer"></div>
<div id="analysisModalContainer"></div>
<div id="annotationsModalContainer"></div> <div id="annotationsModalContainer"></div>
<?php include 'photos_functions.php'; ?> <?php include 'photos_functions.php'; ?>
</div> </div>
@ -1382,12 +1218,7 @@ $gridMeta = [
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a> <a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?> <?php include('include/footer.php'); ?>
</div> </div>
<style> <style>.btn i { margin-top: 0 !important; margin-bottom: 0 !important; }</style>
.btn i {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
</style>
<?php include('jsinclude.php'); ?> <?php include('jsinclude.php'); ?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
@ -1400,54 +1231,6 @@ $gridMeta = [
<script src="photos.js"></script> <script src="photos.js"></script>
<script src="annotationsModal.js"></script> <script src="annotationsModal.js"></script>
<script src="partsTable.js"></script> <script src="partsTable.js"></script>
<script src="analysisModal.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const topScrollbar = document.getElementById('topScrollbar');
const topScrollbarInner = document.getElementById('topScrollbarInner');
const gridContainer = document.getElementById('gridContainer');
if (!topScrollbar || !topScrollbarInner || !gridContainer) return;
let syncingFromTop = false;
let syncingFromGrid = false;
function updateTopScrollbarWidth() {
topScrollbarInner.style.width = gridContainer.scrollWidth + 'px';
// Mostra la barra solo se serve davvero
if (gridContainer.scrollWidth > gridContainer.clientWidth) {
topScrollbar.style.display = 'block';
} else {
topScrollbar.style.display = 'none';
}
}
topScrollbar.addEventListener('scroll', function() {
if (syncingFromGrid) return;
syncingFromTop = true;
gridContainer.scrollLeft = topScrollbar.scrollLeft;
syncingFromTop = false;
});
gridContainer.addEventListener('scroll', function() {
if (syncingFromTop) return;
syncingFromGrid = true;
topScrollbar.scrollLeft = gridContainer.scrollLeft;
syncingFromGrid = false;
});
updateTopScrollbarWidth();
window.addEventListener('resize', updateTopScrollbarWidth);
// Ritarda un attimo per sicurezza, visto che la griglia viene renderizzata via JS
setTimeout(updateTopScrollbarWidth, 200);
setTimeout(updateTopScrollbarWidth, 600);
setTimeout(updateTopScrollbarWidth, 1200);
});
</script>
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true"> <div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">

View File

@ -10,9 +10,6 @@ error_reporting(E_ALL | E_STRICT);
include('../../extra/auth.php'); include('../../extra/auth.php');
//require_once __DIR__ . '/extra/auth.php'; //require_once __DIR__ . '/extra/auth.php';
// Laravel bootstrap (loaded by auth.php) forces UTC via config/app.php — re-apply our TZ
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
// Here we just check if user is not // Here we just check if user is not
// logged in, and in that case we redirect // logged in, and in that case we redirect
// the user to vanguard login page. // the user to vanguard login page.

View File

@ -13,10 +13,7 @@ try {
} }
// Recupera solo i template attivi // Recupera solo i template attivi
$stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type $stmt = $pdo->query("SELECT id, button_label, button_bg_color, button_text_color, button_size FROM excel_templates WHERE status = 'active'");
FROM excel_templates
WHERE status = 'active'
ORDER BY button_label ASC");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC); $templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response["success"] = true; $response["success"] = true;

View File

@ -7,26 +7,7 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
$id = intval($_GET['id']); $id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx, idclient, clientname, idschema, schemaname, schemajson, xls_headers FROM excel_templates WHERE id = ?");
$stmt = $pdo->prepare("
SELECT
name,
header_row,
start_column,
target_table,
source_type,
sample_xlsx,
idclient,
clientname,
idschema,
schemaname,
schemajson,
xls_headers,
api_sample_json,
json_nodes
FROM excel_templates
WHERE id = ?
");
$stmt->execute([$id]); $stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC); $template = $stmt->fetch(PDO::FETCH_ASSOC);
@ -34,43 +15,13 @@ if (!$template) {
die("Template not found"); die("Template not found");
} }
$sourceType = strtoupper($template['source_type'] ?? 'XLS');
if (!in_array($sourceType, ['XLS', 'API', 'PDF'], true)) {
$sourceType = 'XLS';
}
$clientName = $template['clientname'] ?: ''; $clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: ''; $schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : []; $schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
$isSchemajsonEmpty = empty(trim($template['schemajson'] ?? '')); $isSchemajsonEmpty = empty(trim($template['schemajson']));
// Recupera i campi dalla tabella template_mapping // Recupera i campi dalla tabella template_mapping
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, auto_value, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label, main_field, is_visible_import, is_visible_parts FROM template_mapping WHERE template_id = ?");
SELECT
id,
field_id,
excel_column,
json_node,
is_manual,
manual_default,
auto_value,
data_type,
is_required,
default_value,
has_list,
length,
decimals,
min_value,
max_value,
default_curr_date,
tablename,
field_label,
main_field,
is_visible_import,
is_visible_parts
FROM template_mapping
WHERE template_id = ?
");
$stmt->execute([$id]); $stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC); $mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@ -86,25 +37,11 @@ $fixedMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
$hasFixedMappings = !empty($fixedMappings); $hasFixedMappings = !empty($fixedMappings);
// Recupera le colonne XLS già associate nel database // Recupera le colonne già associate nel database
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column')); $usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
// Recupera i nodi JSON già associati nel database
$usedJsonNodesFromDB = array_filter(array_column($mappings, 'json_node'));
// Decodifica l'header XLS salvato, se presente // Decodifica l'header XLS salvato, se presente
$xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : []; $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : [];
if (!is_array($xlsHeaders)) {
$xlsHeaders = [];
}
// Decodifica nodi JSON API salvati, se presenti
$jsonNodes = $template['json_nodes'] ? json_decode($template['json_nodes'], true) : [];
if (!is_array($jsonNodes)) {
$jsonNodes = [];
}
$apiSampleJson = $template['api_sample_json'] ?? '';
?> ?>
<!doctype html> <!doctype html>
@ -226,8 +163,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
width: 100% !important; width: 100% !important;
} }
.xls-columns option.used-option, .xls-columns option.used-option {
.json-nodes option.used-option {
background-color: #fff3cd; background-color: #fff3cd;
color: #856404; color: #856404;
font-weight: 600; font-weight: 600;
@ -240,11 +176,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
#apiJsonExample {
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
</style> </style>
</head> </head>
@ -264,19 +195,13 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<p> <p>
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> | Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> | Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
Source: <strong><?php echo htmlspecialchars($sourceType); ?></strong>
<?php if ($sourceType === 'XLS'): ?>
|
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> | Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span> Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
<?php endif; ?>
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php if ($sourceType === 'XLS'): ?>
<div class="mb-4"> <div class="mb-4">
<label class="form-label">Upload XLS Example:</label> <label class="form-label">Upload XLS Example:</label>
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
@ -294,33 +219,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<?php endif; ?> <?php endif; ?>
</small> </small>
</div> </div>
<?php elseif ($sourceType === 'API'): ?>
<div class="mb-4">
<label class="form-label">API JSON Example:</label>
<textarea id="apiJsonExample"
class="form-control"
rows="12"
placeholder='Paste here an example JSON returned by the API'><?php echo htmlspecialchars($apiSampleJson ?? '', ENT_QUOTES, 'UTF-8'); ?></textarea>
<div class="d-flex align-items-center gap-2 mt-2">
<button type="button" id="loadJsonNodesButton" class="btn btn-primary">
Load JSON Nodes
</button>
<small id="jsonStatus" class="text-muted">
<?php if (!empty($jsonNodes)): ?>
JSON nodes already loaded: <?php echo count($jsonNodes); ?>
<?php else: ?>
No JSON nodes loaded yet.
<?php endif; ?>
</small>
</div>
</div>
<?php elseif ($sourceType === 'PDF'): ?>
<div class="alert alert-warning">
PDF source type is not implemented yet.
</div>
<?php endif; ?>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
@ -338,11 +236,15 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<th style="width:45px; text-align:center;">Parts</th> <th style="width:45px; text-align:center;">Parts</th>
<th style="width:320px;">Title</th> <th style="width:320px;">Title</th>
<th style="width:120px;">Type</th> <th style="width:120px;">Type</th>
<th><?php echo $sourceType === 'API' ? 'JSON Mapping' : 'Mapping'; ?></th> <th>Mapping</th>
<th>Default Value</th> <th>Default Value</th>
</tr> </tr>
</thead> </thead>
<tbody id="schemaFieldsBody"> <tbody id="schemaFieldsBody">
<?php foreach ($mappings as $mapping): ?> <?php foreach ($mappings as $mapping): ?>
<tr> <tr>
@ -366,6 +268,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<?php echo ((int)($mapping['is_visible_parts'] ?? 0) === 1) ? 'checked' : ''; ?>> <?php echo ((int)($mapping['is_visible_parts'] ?? 0) === 1) ? 'checked' : ''; ?>>
</td> </td>
<td class="title-col"> <td class="title-col">
<?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?> <?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?>
<?php if ((int)$mapping['is_required'] === 1): ?> <?php if ((int)$mapping['is_required'] === 1): ?>
@ -373,6 +276,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<?php endif; ?> <?php endif; ?>
</td> </td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td> <td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td> <td>
@ -382,19 +286,9 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
$autoValue = $mapping['auto_value'] ?? 'none'; $autoValue = $mapping['auto_value'] ?? 'none';
$hasAuto = ($autoValue && $autoValue !== 'none'); $hasAuto = ($autoValue && $autoValue !== 'none');
if ($isSceltaMultipla) { $mappingValue = $isSceltaMultipla
$mappingValue = 'manual'; ? 'manual'
} elseif ($hasAuto) { : ($hasAuto ? 'auto' : ($mapping['excel_column'] ? 'xls' : ((int)$mapping['is_manual'] === 1 ? 'manual' : '')));
$mappingValue = 'auto';
} elseif ($sourceType === 'XLS' && !empty($mapping['excel_column'])) {
$mappingValue = 'xls';
} elseif ($sourceType === 'API' && !empty($mapping['json_node'])) {
$mappingValue = 'json';
} elseif ((int)$mapping['is_manual'] === 1) {
$mappingValue = 'manual';
} else {
$mappingValue = '';
}
?> ?>
<select class="form-select mapping-select" <select class="form-select mapping-select"
@ -404,34 +298,18 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<?php if (!$isSceltaMultipla): ?> <?php if (!$isSceltaMultipla): ?>
<option value="">Select Option</option> <option value="">Select Option</option>
<?php if ($sourceType === 'XLS'): ?>
<option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option> <option value="xls" <?php echo ($mappingValue === 'xls') ? 'selected' : ''; ?>>Map to XLS Column</option>
<?php elseif ($sourceType === 'API'): ?>
<option value="json" <?php echo ($mappingValue === 'json') ? 'selected' : ''; ?>>Map to JSON Node</option>
<?php endif; ?>
<option value="auto" <?php echo ($mappingValue === 'auto') ? 'selected' : ''; ?>>Auto value</option> <option value="auto" <?php echo ($mappingValue === 'auto') ? 'selected' : ''; ?>>Auto value</option>
<?php endif; ?> <?php endif; ?>
<option value="manual" <?php echo ($mappingValue === 'manual') ? 'selected' : ''; ?>>Manual Entry</option> <option value="manual" <?php echo ($mappingValue === 'manual') ? 'selected' : ''; ?>>Manual Entry</option>
</select> </select>
<?php if ($sourceType === 'XLS'): ?>
<select class="form-select xls-columns" <select class="form-select xls-columns"
style="display:<?php echo ($mappingValue === 'xls') ? 'block' : 'none'; ?>" style="display:<?php echo ($mappingValue === 'xls') ? 'block' : 'none'; ?>"
data-id="<?php echo (int)$mapping['id']; ?>" data-id="<?php echo (int)$mapping['id']; ?>"
<?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column'], ENT_QUOTES, 'UTF-8') . '"' : ''; ?>> <?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>>
</select> </select>
<?php endif; ?>
<?php if ($sourceType === 'API'): ?>
<select class="form-select json-nodes"
style="display:<?php echo ($mappingValue === 'json') ? 'block' : 'none'; ?>; margin-top:6px;"
data-id="<?php echo (int)$mapping['id']; ?>"
<?php echo !empty($mapping['json_node']) ? 'data-current-json="' . htmlspecialchars($mapping['json_node'], ENT_QUOTES, 'UTF-8') . '"' : ''; ?>>
</select>
<?php endif; ?>
<select class="form-select auto-value-select" <select class="form-select auto-value-select"
style="display:<?php echo ($mappingValue === 'auto') ? 'block' : 'none'; ?>; margin-top:6px;" style="display:<?php echo ($mappingValue === 'auto') ? 'block' : 'none'; ?>; margin-top:6px;"
@ -443,7 +321,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<option value="export_time" <?php echo ($autoValue === 'export_time') ? 'selected' : ''; ?>>Current time (export)</option> <option value="export_time" <?php echo ($autoValue === 'export_time') ? 'selected' : ''; ?>>Current time (export)</option>
</select> </select>
<?php if ($sourceType === 'XLS' && !empty($mapping['excel_column'])): ?> <?php if (!empty($mapping['excel_column'])): ?>
<span class="mapped-column" style="margin-left:5px;"> <span class="mapped-column" style="margin-left:5px;">
(<?php echo htmlspecialchars($mapping['excel_column']); ?>) (<?php echo htmlspecialchars($mapping['excel_column']); ?>)
</span> </span>
@ -452,15 +330,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
style="margin-left:5px;"> style="margin-left:5px;">
X X
</button> </button>
<?php elseif ($sourceType === 'API' && !empty($mapping['json_node'])): ?>
<span class="mapped-json-node" style="margin-left:5px;">
(<?php echo htmlspecialchars($mapping['json_node']); ?>)
</span>
<button class="btn btn-danger btn-sm remove-json"
data-id="<?php echo (int)$mapping['id']; ?>"
style="margin-left:5px;">
X
</button>
<?php endif; ?> <?php endif; ?>
</td> </td>
@ -543,6 +412,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<strong><?php echo htmlspecialchars($fm['fixed_field_key']); ?></strong> <strong><?php echo htmlspecialchars($fm['fixed_field_key']); ?></strong>
</td> </td>
<td><?php echo htmlspecialchars($fm['data_type']); ?></td> <td><?php echo htmlspecialchars($fm['data_type']); ?></td>
<td> <td>
@ -591,6 +461,8 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
</div> </div>
</div> </div>
<div class="mt-4 text-end"> <div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a> <a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div> </div>
@ -607,18 +479,10 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
<?php include('jsinclude.php'); ?> <?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script> <script>
const sourceType = <?php echo json_encode($sourceType); ?>;
let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || []; let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || [];
let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || []; let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || [];
let availableJsonNodes = <?php echo json_encode($jsonNodes); ?> || []; document.getElementById('xlsUpload').addEventListener('change', function(event) {
let usedJsonNodesFromDB = <?php echo json_encode($usedJsonNodesFromDB); ?> || [];
const xlsUploadEl = document.getElementById('xlsUpload');
if (xlsUploadEl) {
xlsUploadEl.addEventListener('change', function(event) {
let file = event.target.files[0]; let file = event.target.files[0];
if (!file) return; if (!file) return;
@ -647,7 +511,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
console.error(error); console.error(error);
}); });
}); });
}
function processXLSX(file) { function processXLSX(file) {
let reader = new FileReader(); let reader = new FileReader();
@ -671,14 +534,8 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
// Track merged cell ranges — adjust for column offset // Track merged cell ranges — adjust for column offset
const merges = (sheet['!merges'] || []).map(m => ({ const merges = (sheet['!merges'] || []).map(m => ({
s: { s: { r: m.s.r, c: m.s.c - colOffset },
r: m.s.r, e: { r: m.e.r, c: m.e.c - colOffset }
c: m.s.c - colOffset
},
e: {
r: m.e.r,
c: m.e.c - colOffset
}
})); }));
console.log('Sheet column offset:', colOffset, '(first col:', String.fromCharCode(65 + colOffset) + ')'); console.log('Sheet column offset:', colOffset, '(first col:', String.fromCharCode(65 + colOffset) + ')');
@ -785,14 +642,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
matched.push(cellVal); matched.push(cellVal);
} }
} }
rowScores.push({ rowScores.push({ row: r + 1, score, nonEmpty: nonEmptyCount, matched, firstNonEmpty: firstNonEmpty + 1, cells: cellValues });
row: r + 1,
score,
nonEmpty: nonEmptyCount,
matched,
firstNonEmpty: firstNonEmpty + 1,
cells: cellValues
});
if (score > bestScore) { if (score > bestScore) {
bestScore = score; bestScore = score;
bestRow = r; bestRow = r;
@ -915,22 +765,16 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
let currentValue = select.value || select.dataset.currentXls || ''; let currentValue = select.value || select.dataset.currentXls || '';
let options = availableXlsColumns let options = availableXlsColumns
.map((col, origIdx) => ({ .map((col, origIdx) => ({ col, origIdx }))
col, .map(({ col, origIdx }) => {
origIdx
}))
.map(({
col,
origIdx
}) => {
const clean = col.replace(/[\r\n\t]+/g, ' ').trim(); const clean = col.replace(/[\r\n\t]+/g, ' ').trim();
const isEmpty = clean === ''; const isEmpty = clean === '';
const colNum = origIdx + 1; const colNum = origIdx + 1;
const val = isEmpty ? `__empty_${colNum}__` : clean; const val = isEmpty ? `__empty_${colNum}__` : clean;
const isUsed = uniqueUsedColumns.includes(col) && col !== currentValue; const isUsed = uniqueUsedColumns.includes(col) && col !== currentValue;
const label = isEmpty ? const label = isEmpty
`(empty column ${colNum})` : ? `(empty column ${colNum})`
(isUsed ? `⚠ ${clean} (already used)` : clean); : (isUsed ? `⚠ ${clean} (already used)` : clean);
const isSelected = (isEmpty ? val === currentValue : col === currentValue) ? 'selected' : ''; const isSelected = (isEmpty ? val === currentValue : col === currentValue) ? 'selected' : '';
return `<option value="${val}" class="${isUsed ? 'used-option' : ''}" ${isSelected}>${label}</option>`; return `<option value="${val}" class="${isUsed ? 'used-option' : ''}" ${isSelected}>${label}</option>`;
}) })
@ -941,88 +785,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
}); });
} }
function escapeHtmlText(str) {
return String(str ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;');
}
function escapeHtmlAttr(str) {
return escapeHtmlText(str);
}
function flattenJsonNodes(obj, prefix = '') {
let nodes = [];
if (Array.isArray(obj)) {
if (obj.length > 0) {
nodes = nodes.concat(flattenJsonNodes(obj[0], prefix ? `${prefix}[]` : '[]'));
}
return nodes;
}
if (obj !== null && typeof obj === 'object') {
Object.keys(obj).forEach(key => {
const path = prefix ? `${prefix}.${key}` : key;
const value = obj[key];
if (value !== null && typeof value === 'object') {
nodes = nodes.concat(flattenJsonNodes(value, path));
} else {
nodes.push(path);
}
});
}
return nodes;
}
function updateJsonDropdowns() {
let usedNodes = Array.from(document.querySelectorAll('select.json-nodes'))
.map(select => select.value || select.dataset.currentJson || '')
.filter(Boolean)
.concat(usedJsonNodesFromDB);
let uniqueUsedNodes = [...new Set(usedNodes)];
document.querySelectorAll('select.json-nodes').forEach(select => {
let currentValue = select.value || select.dataset.currentJson || '';
let options = availableJsonNodes
.map(node => {
const clean = String(node || '').trim();
if (!clean) return '';
const isUsed = uniqueUsedNodes.includes(clean) && clean !== currentValue;
const label = isUsed ? `⚠ ${clean} (already used)` : clean;
const isSelected = clean === currentValue ? 'selected' : '';
return `<option value="${escapeHtmlAttr(clean)}" class="${isUsed ? 'used-option' : ''}" ${isSelected}>${escapeHtmlText(label)}</option>`;
})
.join('');
select.innerHTML = '<option value="">Select JSON Node</option>' + options;
select.dataset.currentJson = currentValue;
});
}
function saveJsonNodes(sampleJson, nodes) {
return fetch('update_api_json_nodes.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?php echo $id; ?>,
api_sample_json: sampleJson,
json_nodes: JSON.stringify(nodes)
})
}).then(response => response.json());
}
function initSelect2ForSchemaDropdowns() { function initSelect2ForSchemaDropdowns() {
// dropdown-select = SceltaMultipla (manual default) // dropdown-select = SceltaMultipla (manual default)
if (window.jQuery && $.fn.select2) { if (window.jQuery && $.fn.select2) {
@ -1096,6 +858,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
}); });
} }
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
let templateId = <?php echo $id; ?>; let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>; let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
@ -1107,72 +870,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
loadingOverlay.innerHTML = '<div>Loading Dropdown Options...</div>'; loadingOverlay.innerHTML = '<div>Loading Dropdown Options...</div>';
document.body.appendChild(loadingOverlay); document.body.appendChild(loadingOverlay);
const loadJsonNodesButton = document.getElementById('loadJsonNodesButton');
if (loadJsonNodesButton) {
loadJsonNodesButton.addEventListener('click', async function() {
const textarea = document.getElementById('apiJsonExample');
const status = document.getElementById('jsonStatus');
if (!textarea || !status) {
console.error('Missing API JSON textarea or status element.');
return;
}
const rawJson = textarea.value.trim();
if (!rawJson) {
status.textContent = '❌ Paste a JSON example first.';
status.classList.remove('text-muted');
status.classList.add('text-danger');
return;
}
let parsedJson;
try {
parsedJson = JSON.parse(rawJson);
} catch (error) {
status.textContent = '❌ Invalid JSON: ' + error.message;
status.classList.remove('text-muted');
status.classList.add('text-danger');
return;
}
let nodes = flattenJsonNodes(parsedJson);
nodes = [...new Set(nodes)].sort();
if (!nodes.length) {
status.textContent = '❌ No usable JSON nodes found.';
status.classList.remove('text-muted');
status.classList.add('text-danger');
return;
}
availableJsonNodes = nodes;
usedJsonNodesFromDB = [];
updateJsonDropdowns();
try {
const data = await saveJsonNodes(rawJson, nodes);
if (!data.success) {
throw new Error(data.message || 'Save failed');
}
status.textContent = `✅ JSON nodes loaded: ${nodes.length}`;
status.classList.remove('text-danger');
status.classList.add('text-muted');
} catch (error) {
console.error(error);
status.textContent = '❌ Error saving JSON nodes: ' + error.message;
status.classList.remove('text-muted');
status.classList.add('text-danger');
}
});
}
async function populateDropdowns() { async function populateDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown-select'); const dropdowns = document.querySelectorAll('.dropdown-select');
if (dropdowns.length === 0) { if (dropdowns.length === 0) {
@ -1285,7 +982,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
tr?.querySelector('.mapping-select')?.getAttribute('data-id'); tr?.querySelector('.mapping-select')?.getAttribute('data-id');
const xlsSelect = tr?.querySelector('.xls-columns'); const xlsSelect = tr?.querySelector('.xls-columns');
const jsonSelect = tr?.querySelector('.json-nodes');
console.log('[SceltaMultipla change] saving', { console.log('[SceltaMultipla change] saving', {
mappingId, mappingId,
@ -1297,14 +993,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
return; return;
} }
saveMapping( saveMapping(mappingId, 'manual', el.value, xlsSelect ? xlsSelect.value : null);
mappingId,
'manual',
el.value,
xlsSelect ? xlsSelect.value : null,
null,
jsonSelect ? jsonSelect.value : null
);
}); });
// 2) eventi Select2 (quando Select2 “mangia” il change) // 2) eventi Select2 (quando Select2 “mangia” il change)
@ -1317,7 +1006,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
tr?.querySelector('.mapping-select')?.getAttribute('data-id'); tr?.querySelector('.mapping-select')?.getAttribute('data-id');
const xlsSelect = tr?.querySelector('.xls-columns'); const xlsSelect = tr?.querySelector('.xls-columns');
const jsonSelect = tr?.querySelector('.json-nodes');
console.log('[SceltaMultipla select2] saving', { console.log('[SceltaMultipla select2] saving', {
mappingId, mappingId,
@ -1329,17 +1017,11 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
return; return;
} }
saveMapping( saveMapping(mappingId, 'manual', el.value, xlsSelect ? xlsSelect.value : null);
mappingId,
'manual',
el.value,
xlsSelect ? xlsSelect.value : null,
null,
jsonSelect ? jsonSelect.value : null
);
}); });
} }
async function loadClientAndSchemaNames() { async function loadClientAndSchemaNames() {
if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) { if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) {
let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`); let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`);
@ -1402,118 +1084,60 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
if (event.target.classList.contains('mapping-select')) { if (event.target.classList.contains('mapping-select')) {
let tr = event.target.closest('tr'); let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id'); let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns'); let xlsSelect = tr.querySelector('.xls-columns');
let jsonSelect = tr.querySelector('.json-nodes');
let manualInput = tr.querySelector('.manual-default'); let manualInput = tr.querySelector('.manual-default');
let autoSelect = tr.querySelector('.auto-value-select'); let autoSelect = tr.querySelector('.auto-value-select');
let mappedColumn = tr.querySelector('.mapped-column'); let mappedColumn = tr.querySelector('.mapped-column');
let mappedJsonNode = tr.querySelector('.mapped-json-node');
let removeBtn = tr.querySelector('.remove-xls'); let removeBtn = tr.querySelector('.remove-xls');
let removeJsonBtn = tr.querySelector('.remove-json');
if (event.target.value === 'xls') { if (event.target.value === 'xls') {
if (xlsSelect) xlsSelect.style.display = 'block'; xlsSelect.style.display = 'block';
if (jsonSelect) jsonSelect.style.display = 'none';
if (autoSelect) autoSelect.style.display = 'none'; if (autoSelect) autoSelect.style.display = 'none';
if (manualInput) {
manualInput.style.display = 'none'; manualInput.style.display = 'none';
manualInput.value = ''; manualInput.value = '';
}
if (mappedColumn) mappedColumn.style.display = 'none'; if (mappedColumn) mappedColumn.style.display = 'none';
if (mappedJsonNode) mappedJsonNode.style.display = 'none'; if (removeBtn) removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
if (removeBtn) removeBtn.style.display = xlsSelect && xlsSelect.value ? 'inline-block' : 'none';
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
} else if (event.target.value === 'json') {
if (xlsSelect) xlsSelect.style.display = 'none';
if (jsonSelect) jsonSelect.style.display = 'block';
if (autoSelect) autoSelect.style.display = 'none';
if (manualInput) {
manualInput.style.display = 'none';
manualInput.value = '';
}
if (mappedColumn) mappedColumn.style.display = 'none';
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none';
if (removeJsonBtn) removeJsonBtn.style.display = jsonSelect && jsonSelect.value ? 'inline-block' : 'none';
} else if (event.target.value === 'manual') { } else if (event.target.value === 'manual') {
if (xlsSelect) xlsSelect.style.display = 'none'; xlsSelect.style.display = 'none';
if (jsonSelect) jsonSelect.style.display = 'none';
if (autoSelect) autoSelect.style.display = 'none'; if (autoSelect) autoSelect.style.display = 'none';
if (manualInput) {
manualInput.style.display = 'block'; manualInput.style.display = 'block';
}
if (mappedColumn) mappedColumn.style.display = 'none'; if (mappedColumn) mappedColumn.style.display = 'none';
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none'; if (removeBtn) removeBtn.style.display = 'none';
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
} else if (event.target.value === 'auto') { } else if (event.target.value === 'auto') {
if (xlsSelect) {
xlsSelect.style.display = 'none'; xlsSelect.style.display = 'none';
xlsSelect.value = ''; xlsSelect.value = ''; // libera UI
}
if (jsonSelect) {
jsonSelect.style.display = 'none';
jsonSelect.value = '';
}
if (manualInput) {
manualInput.style.display = 'none'; manualInput.style.display = 'none';
manualInput.value = ''; manualInput.value = '';
}
if (mappedColumn) mappedColumn.style.display = 'none'; if (mappedColumn) mappedColumn.style.display = 'none';
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none'; if (removeBtn) removeBtn.style.display = 'none';
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
if (autoSelect) autoSelect.style.display = 'block'; if (autoSelect) autoSelect.style.display = 'block';
} else { } else {
if (xlsSelect) xlsSelect.style.display = 'none'; xlsSelect.style.display = 'none';
if (jsonSelect) jsonSelect.style.display = 'none';
if (autoSelect) autoSelect.style.display = 'none'; if (autoSelect) autoSelect.style.display = 'none';
if (manualInput) {
manualInput.style.display = 'none'; manualInput.style.display = 'none';
manualInput.value = ''; manualInput.value = '';
}
if (mappedColumn) mappedColumn.style.display = 'none'; if (mappedColumn) mappedColumn.style.display = 'none';
if (mappedJsonNode) mappedJsonNode.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none'; if (removeBtn) removeBtn.style.display = 'none';
if (removeJsonBtn) removeJsonBtn.style.display = 'none';
} }
saveMapping( saveMapping(
mappingId, mappingId,
event.target.value, event.target.value,
manualInput ? manualInput.value : '', manualInput.value,
xlsSelect ? xlsSelect.value : null, xlsSelect.value,
autoSelect ? autoSelect.value : null, autoSelect ? autoSelect.value : null
jsonSelect ? jsonSelect.value : null
); );
updateXlsDropdowns();
if (sourceType === 'XLS') updateXlsDropdowns();
if (sourceType === 'API') updateJsonDropdowns();
} else if (event.target.classList.contains('main-field-checkbox')) { } else if (event.target.classList.contains('main-field-checkbox')) {
const checkbox = event.target; const checkbox = event.target;
const mappingId = checkbox.dataset.mappingId; const mappingId = checkbox.dataset.mappingId;
@ -1612,7 +1236,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
document.querySelectorAll('.main-field-checkbox').forEach(cb => { document.querySelectorAll('.main-field-checkbox').forEach(cb => {
cb.dataset.originalChecked = cb.checked; cb.dataset.originalChecked = cb.checked;
}); });
// AUTO VALUE select change -> save auto_value // AUTO VALUE select change -> save auto_value
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) { document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (!event.target.classList.contains('auto-value-select')) return; if (!event.target.classList.contains('auto-value-select')) return;
@ -1621,7 +1244,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
const mappingId = event.target.getAttribute('data-id'); const mappingId = event.target.getAttribute('data-id');
const xlsSelect = tr.querySelector('.xls-columns'); const xlsSelect = tr.querySelector('.xls-columns');
const jsonSelect = tr.querySelector('.json-nodes');
const manualInput = tr.querySelector('.manual-default'); const manualInput = tr.querySelector('.manual-default');
saveMapping( saveMapping(
@ -1629,11 +1251,9 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
'auto', 'auto',
manualInput ? manualInput.value : '', manualInput ? manualInput.value : '',
xlsSelect ? xlsSelect.value : null, xlsSelect ? xlsSelect.value : null,
event.target.value, event.target.value
jsonSelect ? jsonSelect.value : null
); );
}); });
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) { document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('xls-columns')) { if (event.target.classList.contains('xls-columns')) {
let tr = event.target.closest('tr'); let tr = event.target.closest('tr');
@ -1666,7 +1286,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
mappingSelect.value = ''; mappingSelect.value = '';
if (mappedColumn) mappedColumn.style.display = 'none'; if (mappedColumn) mappedColumn.style.display = 'none';
e.target.style.display = 'none'; e.target.style.display = 'none';
saveMapping(mappingId, '', '', null, null, null); saveMapping(mappingId, '', '', null);
updateXlsDropdowns(); updateXlsDropdowns();
}); });
} }
@ -1684,75 +1304,21 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
if (mappingSelect && mappingSelect.value !== 'xls') { if (mappingSelect && mappingSelect.value !== 'xls') {
return; // non salvare XLS se non siamo in modalità XLS return; // non salvare XLS se non siamo in modalità XLS
} }
saveMapping(mappingId, 'xls', manualInput ? manualInput.value : '', event.target.value, autoSelect ? autoSelect.value : null, null); saveMapping(mappingId, 'xls', manualInput.value, event.target.value);
updateXlsDropdowns(); updateXlsDropdowns();
} }
}); });
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('json-nodes')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let manualInput = tr.querySelector('.manual-default');
let mappedJsonNode = tr.querySelector('.mapped-json-node');
let removeJsonBtn = tr.querySelector('.remove-json');
if (!mappedJsonNode) {
mappedJsonNode = document.createElement('span');
mappedJsonNode.className = 'mapped-json-node';
mappedJsonNode.style.marginLeft = '5px';
tr.querySelector('td:nth-child(6)').appendChild(mappedJsonNode);
}
if (!removeJsonBtn) {
removeJsonBtn = document.createElement('button');
removeJsonBtn.className = 'btn btn-danger btn-sm remove-json';
removeJsonBtn.textContent = 'X';
removeJsonBtn.style.marginLeft = '5px';
removeJsonBtn.setAttribute('data-id', mappingId);
tr.querySelector('td:nth-child(6)').appendChild(removeJsonBtn);
}
mappedJsonNode.textContent = event.target.value ? `(${event.target.value})` : '';
mappedJsonNode.style.display = event.target.value ? 'inline' : 'none';
removeJsonBtn.style.display = event.target.value ? 'inline-block' : 'none';
const mappingSelect = tr.querySelector('.mapping-select');
if (mappingSelect && mappingSelect.value !== 'json') {
return;
}
saveMapping(
mappingId,
'json',
manualInput ? manualInput.value : '',
null,
null,
event.target.value
);
updateJsonDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) { document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') { if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
let tr = event.target.closest('tr'); let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id'); let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns'); let xlsSelect = tr.querySelector('.xls-columns');
let jsonSelect = tr.querySelector('.json-nodes');
console.log("Manual default dropdown changed:", { console.log("Manual default dropdown changed:", {
id: mappingId, id: mappingId,
value: event.target.value value: event.target.value
}); });
saveMapping( saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
mappingId,
'manual',
event.target.value,
xlsSelect ? xlsSelect.value : null,
null,
jsonSelect ? jsonSelect.value : null
);
} }
}); });
@ -1761,19 +1327,11 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
let tr = event.target.closest('tr'); let tr = event.target.closest('tr');
let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id'); let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns'); let xlsSelect = tr.querySelector('.xls-columns');
let jsonSelect = tr.querySelector('.json-nodes');
console.log("Manual default input changed:", { console.log("Manual default input changed:", {
id: mappingId, id: mappingId,
value: event.target.value value: event.target.value
}); });
saveMapping( saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
mappingId,
'manual',
event.target.value,
xlsSelect ? xlsSelect.value : null,
null,
jsonSelect ? jsonSelect.value : null
);
} }
}); });
@ -1792,49 +1350,17 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
console.log("Removing XLS mapping:", { console.log("Removing XLS mapping:", {
id: mappingId id: mappingId
}); });
saveMapping(mappingId, '', '', null, null, null); saveMapping(mappingId, '', '', null);
updateXlsDropdowns(); updateXlsDropdowns();
} }
}); });
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) { function saveMapping(mappingId, mappingType, defaultValue, excelColumn, autoValue) {
if (event.target.classList.contains('remove-json')) {
let mappingId = event.target.getAttribute('data-id');
let tr = event.target.closest('tr');
let jsonSelect = tr.querySelector('.json-nodes');
let mappingSelect = tr.querySelector('.mapping-select');
let mappedJsonNode = tr.querySelector('.mapped-json-node');
if (jsonSelect) {
jsonSelect.value = '';
jsonSelect.style.display = 'none';
}
if (mappingSelect) {
mappingSelect.value = '';
}
if (mappedJsonNode) {
mappedJsonNode.textContent = '';
mappedJsonNode.style.display = 'none';
}
event.target.style.display = 'none';
saveMapping(mappingId, '', '', null, null, null);
updateJsonDropdowns();
}
});
function saveMapping(mappingId, mappingType, defaultValue, excelColumn, autoValue, jsonNode) {
console.log("Saving mapping:", { console.log("Saving mapping:", {
id: mappingId, id: mappingId,
mapping_type: mappingType, mapping_type: mappingType,
excel_column: excelColumn, excel_column: excelColumn,
json_node: jsonNode, manual_default: defaultValue
manual_default: defaultValue,
auto_value: autoValue
}); });
fetch('save_mapping_json.php', { fetch('save_mapping_json.php', {
method: 'POST', method: 'POST',
@ -1845,7 +1371,6 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
id: mappingId, id: mappingId,
mapping_type: mappingType, mapping_type: mappingType,
excel_column: mappingType === 'xls' ? excelColumn : null, excel_column: mappingType === 'xls' ? excelColumn : null,
json_node: mappingType === 'json' ? jsonNode : null,
manual_default: mappingType === 'manual' ? defaultValue : null, manual_default: mappingType === 'manual' ? defaultValue : null,
auto_value: mappingType === 'auto' ? (autoValue || 'none') : 'none', auto_value: mappingType === 'auto' ? (autoValue || 'none') : 'none',
tablename: "<?php echo $template['target_table']; ?>" tablename: "<?php echo $template['target_table']; ?>"
@ -1854,17 +1379,11 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
.then(data => { .then(data => {
console.log("Save response:", data); console.log("Save response:", data);
if (!data.success) console.error("❌ Error saving mapping:", data.message); if (!data.success) console.error("❌ Error saving mapping:", data.message);
if (data.success && mappingType === 'xls' && excelColumn) { if (data.success && excelColumn) {
usedColumnsFromDB = usedColumnsFromDB.filter(col => col !== excelColumn); usedColumnsFromDB = usedColumnsFromDB.filter(col => col !== excelColumn);
usedColumnsFromDB.push(excelColumn); usedColumnsFromDB.push(excelColumn);
updateXlsDropdowns(); updateXlsDropdowns();
} }
if (data.success && mappingType === 'json' && jsonNode) {
usedJsonNodesFromDB = usedJsonNodesFromDB.filter(node => node !== jsonNode);
usedJsonNodesFromDB.push(jsonNode);
updateJsonDropdowns();
}
}) })
.catch(error => console.error("❌ Fetch error:", error)); .catch(error => console.error("❌ Fetch error:", error));
} }
@ -1959,6 +1478,8 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
}).join(''); }).join('');
} }
// Button: Add Fixed Fields // Button: Add Fixed Fields
const btnAddFixedFields = document.getElementById('btnAddFixedFields'); const btnAddFixedFields = document.getElementById('btnAddFixedFields');
if (btnAddFixedFields) { if (btnAddFixedFields) {
@ -1987,6 +1508,8 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
renderFixedRows(data.rows); renderFixedRows(data.rows);
await fillFixedDropdowns(); await fillFixedDropdowns();
initSelect2ForFixedDropdowns(); initSelect2ForFixedDropdowns();
} }
fixedStatus('✅ Fixed fields created'); fixedStatus('✅ Fixed fields created');
@ -2058,6 +1581,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
}); });
}); });
// Autosave: manual_default (debounce) // Autosave: manual_default (debounce)
let fixedTimer = null; let fixedTimer = null;
document.addEventListener('input', (e) => { document.addEventListener('input', (e) => {
@ -2092,13 +1616,8 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
}, 350); }, 350);
}); });
if (sourceType === 'XLS' && availableXlsColumns.length) {
updateXlsDropdowns();
}
if (sourceType === 'API' && availableJsonNodes.length) { if (availableXlsColumns.length) updateXlsDropdowns();
updateJsonDropdowns();
}
fillFixedDropdowns().then(() => { fillFixedDropdowns().then(() => {
initSelect2ForFixedDropdowns(); initSelect2ForFixedDropdowns();
@ -2246,6 +1765,7 @@ $apiSampleJson = $template['api_sample_json'] ?? '';
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -26,16 +26,10 @@
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"> <button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
Rinumera Parti Rinumera Parti
</button> </button>
<button type="button" class="btn btn-primary btn-sm" id="clonePartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-clone"></i> Clona Parti
</button>
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"> <button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-microphone"></i> Voce <i class="fas fa-microphone"></i> Voce
</button> </button>
<button type="button" class="btn btn-outline-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
</button>
</div> </div>
</div> </div>
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga --> <!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
@ -48,15 +42,7 @@
<select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select> <select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select>
<button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right"></i> Propaga a tutte</button> <button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right"></i> Propaga a tutte</button>
</div> </div>
<div class="parts-table-scroll">
<table class="table table-striped table-sm" id="partsTable"> <table class="table table-striped table-sm" id="partsTable">
<colgroup id="partsTableColgroup">
<col class="parts-col-num">
<col class="parts-col-description">
<col class="parts-col-matrice">
<col class="parts-col-date">
<col class="parts-col-actions">
</colgroup>
<thead> <thead>
<tr> <tr>
<th style="width: 55px;">Num</th> <th style="width: 55px;">Num</th>
@ -96,7 +82,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<div class="col-md-3"> <div class="col-md-3">
<h6>Foto del Campione</h6> <h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;"> <div style="display: flex; align-items: center; margin-bottom: 10px;">
@ -303,20 +288,28 @@
white-space: nowrap; white-space: nowrap;
} }
/* Select delle righe Matrice: si adatta alla colonna */ /* Select delle righe (colonna Matrice) = 150px */
.part-matrice { .part-matrice {
width: 100% !important; width: 300px !important;
min-width: 0 !important; min-width: 300px !important;
max-width: 100% !important; max-width: 300px !important;
flex: 0 0 300px !important;
} }
.part-matrice.select2-hidden-accessible+.select2 { .part-matrice.select2-hidden-accessible+.select2 {
width: 100% !important; width: 300px !important;
min-width: 0 !important; min-width: 300px !important;
max-width: 100% !important; max-width: 300px !important;
flex: 0 0 300px !important;
} }
/* Colonna Descrizione (2ª colonna) = 420px */
#partsTable th:nth-child(2),
#partsTable td:nth-child(2) {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
}
#partsTable .part-number { #partsTable .part-number {
text-align: center; text-align: center;
@ -521,77 +514,6 @@
} }
} }
/* Resizable parts table - stable */
.parts-table-scroll {
width: 100%;
overflow-x: auto;
overflow-y: visible;
contain: inline-size;
}
#partsTable {
table-layout: fixed !important;
width: max-content !important;
min-width: unset !important;
}
#partsTable col.parts-col-num {
width: 55px;
}
#partsTable col.parts-col-description {
width: 320px;
}
#partsTable col.parts-col-matrice {
width: 360px;
}
#partsTable col.parts-col-date {
width: 150px;
}
#partsTable col.parts-col-actions {
width: 230px;
}
#partsTable th,
#partsTable td {
position: relative;
box-sizing: border-box;
}
#partsTable th {
user-select: none;
}
#partsTable th .parts-resizer {
position: absolute;
top: 0;
right: 0;
width: 9px;
height: 100%;
cursor: col-resize;
z-index: 50;
background: rgba(108, 117, 125, 0.12);
border-right: 1px solid rgba(108, 117, 125, 0.45);
}
#partsTable th .parts-resizer:hover {
background: rgba(108, 117, 125, 0.25);
border-right: 2px solid rgba(73, 80, 87, 0.7);
}
#partsTable td {
overflow: hidden;
text-overflow: ellipsis;
}
#partsTable td input,
#partsTable td select,
#partsTable td .select2-container {
max-width: 100% !important;
}
/* rosso */ /* rosso */
</style> </style>

View File

@ -2,111 +2,93 @@
* modals_gridData.js Photos, Parts, Tested Component handlers for gridData pages * modals_gridData.js Photos, Parts, Tested Component handlers for gridData pages
*/ */
(function () { (function () {
"use strict"; 'use strict';
// ── Photos — use photos.js loadPopupContent (exported to window) ── // ── Photos — use photos.js loadPopupContent (exported to window) ──
$(document).on("click", ".photos-btn", function () { $(document).on('click', '.photos-btn', function () {
const iddatadb = $(this).data("iddatadb") || null; const iddatadb = $(this).data('iddatadb') || null;
const idquotations = $(this).data("idquotations") || null; const idquotations = $(this).data('idquotations') || null;
const modal = document.getElementById("photosModal"); const modal = document.getElementById('photosModal');
if (!modal) return; if (!modal) return;
modal.style.display = "block"; modal.style.display = 'block';
if (typeof window.loadPopupContent === "function") { if (typeof window.loadPopupContent === 'function') {
window.loadPopupContent(iddatadb, idquotations); window.loadPopupContent(iddatadb, idquotations);
} }
}); });
// Close photos modal // Close photos modal
$(document).on("click", ".close-btn", function () { $(document).on('click', '.close-btn', function () {
const modal = document.getElementById("photosModal"); const modal = document.getElementById('photosModal');
if (modal) modal.style.display = "none"; if (modal) modal.style.display = 'none';
}); });
// Close on backdrop click // Close on backdrop click
$(document).on("click", "#photosModal", function (e) { $(document).on('click', '#photosModal', function (e) {
if (e.target === this) { if (e.target === this) {
this.style.display = "none"; this.style.display = 'none';
} }
}); });
// ── Parts (matching import_edit2.php behavior) ───────────────────── // ── Parts (matching import_edit2.php behavior) ─────────────────────
$(document).on("click", ".parts-btn", function () { $(document).on('click', '.parts-btn', function () {
const iddatadb = $(this).data("iddatadb") || null; const iddatadb = $(this).data('iddatadb') || null;
const idquotations = $(this).data("idquotations") || null; const idquotations = $(this).data('idquotations') || null;
$.ajax({ $.ajax({
url: "modal_partsTable.php", url: 'modal_partsTable.php',
method: "GET", method: 'GET',
data: { iddatadb: iddatadb }, data: { iddatadb: iddatadb },
success: function (response) { success: function (response) {
$("#partsModalContainer").html(response); $('#partsModalContainer').html(response);
const modalElement = document.getElementById("partsModal"); const modalElement = document.getElementById('partsModal');
if (!modalElement) return; if (!modalElement) return;
$("#trfHeader").text(iddatadb || idquotations || ""); $("#trfHeader").text(iddatadb || idquotations || '');
$("#partsModal").data("iddatadb", iddatadb).data("idquotations", idquotations);
const visibleIddatadbList = Array.isArray(window.gridData)
? window.gridData
.map((r) => parseInt(r.iddatadb, 10))
.filter((v) => !isNaN(v) && v > 0)
: [];
$("#partsModal")
.data("iddatadb", iddatadb)
.data("idquotations", idquotations)
.data("visible-iddatadb-list", visibleIddatadbList);
let modal = bootstrap.Modal.getInstance(modalElement); let modal = bootstrap.Modal.getInstance(modalElement);
if (!modal) if (!modal) modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true });
modal = new bootstrap.Modal(modalElement, {
backdrop: true,
keyboard: true,
focus: true,
});
modal.show(); modal.show();
if (typeof window.loadParts === "function") { if (typeof window.loadParts === 'function') {
window.loadParts(iddatadb, idquotations); window.loadParts(iddatadb, idquotations);
} }
}, },
error: function (xhr, status, error) { error: function (xhr, status, error) {
console.error("Error loading parts:", error); console.error('Error loading parts:', error);
}, }
}); });
}); });
$(document).on("hidden.bs.modal", "#partsModal", function () { $(document).on('hidden.bs.modal', '#partsModal', function () {
const modalElement = document.getElementById("partsModal"); const modalElement = document.getElementById('partsModal');
if (modalElement) { if (modalElement) {
const modal = bootstrap.Modal.getInstance(modalElement); const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) modal.dispose(); if (modal) modal.dispose();
} }
$("#partsModalContainer").empty(); $('#partsModalContainer').empty();
$(".modal-backdrop").remove(); $('.modal-backdrop').remove();
$("body").removeClass("modal-open").css("padding-right", ""); $('body').removeClass('modal-open').css('padding-right', '');
}); });
// ── Tested Component quick add ─────────────────────────────────────── // ── Tested Component quick add ───────────────────────────────────────
$(document).on("click", ".add-part-btn", async function () { $(document).on('click', '.add-part-btn', async function () {
const iddatadb = $(this).data("iddatadb") || null; const iddatadb = $(this).data('iddatadb') || null;
const rowIndex = parseInt($(this).data("row")); const rowIndex = parseInt($(this).data('row'));
const row = window.gridData?.[rowIndex]; const row = window.gridData?.[rowIndex];
const id = iddatadb || (row ? row.iddatadb : null); const id = iddatadb || (row ? row.iddatadb : null);
if (!id) return; if (!id) return;
const $cell = $(this).closest(".grid-cell, div"); const $cell = $(this).closest('.grid-cell, div');
const $input = $cell.find("input"); const $input = $cell.find('input');
const raw = ($input.val() || "").trim(); const raw = ($input.val() || '').trim();
const parts = raw const parts = raw.split('|').map(s => s.trim()).filter(s => s.length > 0);
.split("|")
.map((s) => s.trim())
.filter((s) => s.length > 0);
const uniqueParts = [...new Set(parts)]; const uniqueParts = [...new Set(parts)];
if (!uniqueParts.length) { if (!uniqueParts.length) {
alert("Insert a description first."); alert('Insert a description first.');
$input.focus(); $input.focus();
return; return;
} }
@ -114,23 +96,14 @@
try { try {
for (const p of uniqueParts) { for (const p of uniqueParts) {
const formData = new FormData(); const formData = new FormData();
formData.append("iddatadb", id); formData.append('iddatadb', id);
formData.append("part_description", p); formData.append('part_description', p);
await fetch("add_part_quick.php", { await fetch('add_part_quick.php', { method: 'POST', body: formData });
method: "POST",
body: formData,
});
} }
if (row) {
row.tested_component = raw;
row._dirty = true;
}
alert(`Added ${uniqueParts.length} part(s).`); alert(`Added ${uniqueParts.length} part(s).`);
$input.val(raw); $input.val('');
} catch (e) { } catch (e) {
alert("Error: " + e.message); alert('Error: ' + e.message);
} }
}); });
@ -138,47 +111,44 @@
let deleteIddatadb = null; let deleteIddatadb = null;
let deleteRowIndex = null; let deleteRowIndex = null;
$(document).on("click", ".delete-btn", function () { $(document).on('click', '.delete-btn', function () {
deleteIddatadb = $(this).data("iddatadb"); deleteIddatadb = $(this).data('iddatadb');
deleteRowIndex = parseInt($(this).data("row")); deleteRowIndex = parseInt($(this).data('row'));
const modalEl = document.getElementById("deleteConfirmModal"); const modalEl = document.getElementById('deleteConfirmModal');
if (modalEl) { if (modalEl) {
document.getElementById("deleteIddatadbText").textContent = document.getElementById('deleteIddatadbText').textContent = deleteIddatadb;
deleteIddatadb;
new bootstrap.Modal(modalEl).show(); new bootstrap.Modal(modalEl).show();
} }
}); });
$(document).on("click", "#deleteConfirmBtn", async function () { $(document).on('click', '#deleteConfirmBtn', async function () {
const modalEl = document.getElementById("deleteConfirmModal"); const modalEl = document.getElementById('deleteConfirmModal');
const modal = bootstrap.Modal.getInstance(modalEl); const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide(); if (modal) modal.hide();
if (!deleteIddatadb) return; if (!deleteIddatadb) return;
try { try {
const resp = await fetch("delete_record.php", { const resp = await fetch('delete_record.php', {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: deleteIddatadb }), body: JSON.stringify({ id: deleteIddatadb })
}); });
const result = await resp.json(); const result = await resp.json();
if (result.success) { if (result.success) {
// Remove from gridData // Remove from gridData
const idx = window.gridData.findIndex( const idx = window.gridData.findIndex(r => r.iddatadb === deleteIddatadb);
(r) => r.iddatadb === deleteIddatadb,
);
if (idx >= 0) window.gridData.splice(idx, 1); if (idx >= 0) window.gridData.splice(idx, 1);
// Re-render // Re-render
const gr = window.gridRenderer; const gr = window.gridRenderer;
if (gr) gr.renderVisibleRows(); if (gr) gr.renderVisibleRows();
} else { } else {
alert("Error: " + result.message); alert('Error: ' + result.message);
} }
} catch (e) { } catch (e) {
alert("Error: " + e.message); alert('Error: ' + e.message);
} }
deleteIddatadb = null; deleteIddatadb = null;
@ -186,26 +156,20 @@
}); });
// ── Add new row ────────────────────────────────────────────────────── // ── Add new row ──────────────────────────────────────────────────────
$(document).on("click", "#addRowBtn", async function () { $(document).on('click', '#addRowBtn', async function () {
const btn = this; const btn = this;
btn.disabled = true; btn.disabled = true;
try { try {
const templateId = window.gridMeta?.templateId; const templateId = window.gridMeta?.templateId;
if (!templateId) { if (!templateId) { alert('Template ID missing'); return; }
alert("Template ID missing");
return;
}
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const importref = urlParams.get("importref") || ""; const importref = urlParams.get('importref') || '';
const resp = await fetch("add_record.php", { const resp = await fetch('add_record.php', {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({ template_id: templateId, importreferencecode: importref })
template_id: templateId,
importreferencecode: importref,
}),
}); });
const result = await resp.json(); const result = await resp.json();
@ -213,20 +177,17 @@
// Build new row object // Build new row object
const newRow = { const newRow = {
iddatadb: result.iddatadb, iddatadb: result.iddatadb,
status: "i", status: 'i',
idclient: window.gridMeta?.defaultIdclient || "", idclient: window.gridMeta?.defaultIdclient || '',
cliente_fornitore_id: null, cliente_fornitore_id: null,
commessaweb: null, commessaweb: null,
user_name: result.user_name || "", user_name: result.user_name || '',
importreferencecode: result.importreferencecode || "", importreferencecode: result.importreferencecode || '',
filename_import: "", filename_import: '',
importdate: new Date() importdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
.toISOString()
.slice(0, 19)
.replace("T", " "),
fixedFields: {}, fixedFields: {},
details: {}, details: {},
mainFieldValue: "", mainFieldValue: '',
_dirty: false, _dirty: false,
}; };
@ -238,27 +199,20 @@
if (gr) gr.renderVisibleRows(); if (gr) gr.renderVisibleRows();
// Highlight new row briefly // Highlight new row briefly
const newGridRow = document.querySelector( const newGridRow = document.querySelector(`.grid-row[data-id="${result.iddatadb}"]`);
`.grid-row[data-id="${result.iddatadb}"]`,
);
if (newGridRow) { if (newGridRow) {
newGridRow.classList.add("row-just-created"); newGridRow.classList.add('row-just-created');
newGridRow.scrollIntoView({ newGridRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
behavior: "smooth", setTimeout(() => newGridRow.classList.remove('row-just-created'), 4000);
block: "center",
});
setTimeout(
() => newGridRow.classList.remove("row-just-created"),
4000,
);
} }
} else { } else {
alert("Error: " + (result.message || "Unknown error")); alert('Error: ' + (result.message || 'Unknown error'));
} }
} catch (e) { } catch (e) {
alert("Error: " + e.message); alert('Error: ' + e.message);
} finally { } finally {
btn.disabled = false; btn.disabled = false;
} }
}); });
})(); })();

View File

@ -1595,11 +1595,7 @@ $(document).ready(function () {
dataType: "json", dataType: "json",
delay: 150, delay: 150,
data: function (params) { data: function (params) {
return { return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
q: params.term || "",
limit: 20,
macro: selectedMacro || "",
};
}, },
processResults: function (data) { processResults: function (data) {
return { results: data.results || [] }; return { results: data.results || [] };
@ -1658,11 +1654,7 @@ $(document).ready(function () {
dataType: "json", dataType: "json",
delay: 150, delay: 150,
data: function (params) { data: function (params) {
return { return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
q: params.term || "",
limit: 20,
macro: selectedMacro || "",
};
}, },
processResults: function (data) { processResults: function (data) {
return { results: data.results || [] }; return { results: data.results || [] };
@ -1808,9 +1800,7 @@ $(document).ready(function () {
$("#partsTableBody .part-matrice").each(function () { $("#partsTableBody .part-matrice").each(function () {
const $target = $(this); const $target = $(this);
if (!$target.find(`option[value="${globalVal}"]`).length) { if (!$target.find(`option[value="${globalVal}"]`).length) {
$target.append( $target.append(new Option(globalText, globalVal, true, true));
new Option(globalText, globalVal, true, true),
);
} }
$target.val(globalVal).trigger("change"); $target.val(globalVal).trigger("change");
}); });
@ -1979,109 +1969,7 @@ $(document).ready(function () {
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn", ".add-row-global, .add-mix-global, .add-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn",
markUnsaved, markUnsaved,
); );
$(document).on("click", "#clonePartsBtn", function (e) {
e.preventDefault();
const sourceIddatadb =
parseInt($("#partsModal").data("iddatadb"), 10) || null;
const visibleListRaw =
$("#partsModal").data("visible-iddatadb-list") || [];
if (!sourceIddatadb) {
const errorMsg = $(
'<div class="alert alert-danger temp-alert" role="alert">Source record not found.</div>',
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
const targetIds = (Array.isArray(visibleListRaw) ? visibleListRaw : [])
.map((v) => parseInt(v, 10))
.filter((v) => !isNaN(v) && v > 0 && v !== sourceIddatadb);
if (!targetIds.length) {
const errorMsg = $(
'<div class="alert alert-warning temp-alert" role="alert">No other visible records available for clone.</div>',
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
return;
}
if (
!confirm(
`Confermi il clone delle parti del record ${sourceIddatadb} negli altri ${targetIds.length} record visibili?`,
)
) {
return;
}
const $btn = $(this);
const originalHtml = $btn.html();
$btn.prop("disabled", true).html(
'<i class="fas fa-spinner fa-spin"></i> Clonazione...',
);
$.ajax({
url: "clone_parts_to_visible.php",
method: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify({
source_iddatadb: sourceIddatadb,
target_iddatadb_list: targetIds,
}),
success: function (response) {
$btn.prop("disabled", false).html(originalHtml);
if (response.success) {
const successMsg = $(
`<div class="alert alert-success temp-alert" role="alert">
Clone completed. Source parts copied to ${response.cloned_targets || 0} record(s), total cloned parts: ${response.total_cloned_parts || 0}.
</div>`,
);
$("#partsModal .modal-body").prepend(successMsg);
setTimeout(() => {
successMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
} else {
const errorMsg = $(
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${response.message || "Unknown error"}</div>`,
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
}
},
error: function (xhr, status, error) {
$btn.prop("disabled", false).html(originalHtml);
const errorMsg = $(
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${error} (${xhr.status})</div>`,
);
$("#partsModal .modal-body").prepend(errorMsg);
setTimeout(() => {
errorMsg.fadeOut(500, function () {
$(this).remove();
});
}, 5000);
},
});
});
// Esporta la funzione loadParts per essere usata da import_Edit2.php // Esporta la funzione loadParts per essere usata da import_Edit2.php
window.loadParts = loadParts; window.loadParts = loadParts;
@ -2463,142 +2351,4 @@ $(document).on("click", "#showHideImageBtn", function () {
"<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>", "<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
); );
} }
if (typeof window.applyPartsColumnWidths === "function") {
setTimeout(window.applyPartsColumnWidths, 150);
}
}); });
// ===================
// RESIZABLE PARTS TABLE COLUMNS - FIXED COLGROUP VERSION
// ===================
(function () {
// Larghezze default per indice colonna (0-based)
const defaultWidths = [55, 320, 360, 150, 230];
const savedWidths = [...defaultWidths];
function getColgroup() {
return $("#partsTableColgroup");
}
function syncColgroupToHeaders() {
const $table = $("#partsTable");
const $ths = $table.find("thead tr:first th");
const $colgroup = getColgroup();
const thCount = $ths.length;
// Assicura che ci siano esattamente tante <col> quante <th>
while ($colgroup.find("col").length < thCount) {
$colgroup.append("<col>");
}
while ($colgroup.find("col").length > thCount) {
$colgroup.find("col:last").remove();
}
// Applica le larghezze salvate
$colgroup.find("col").each(function (i) {
const w = savedWidths[i] !== undefined ? savedWidths[i] : 150;
$(this).css("width", w + "px");
});
// Imposta larghezza totale della tabella = somma colonne (evita reflow)
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
$table.css("width", total + "px");
}
function applyColumnWidth(colIndex, newWidth) {
const w = Math.max(40, Math.round(newWidth));
savedWidths[colIndex] = w;
const $col = getColgroup().find("col").eq(colIndex);
if ($col.length) {
$col.css("width", w + "px");
}
// Aggiorna larghezza totale tabella senza toccare le altre colonne
const thCount = $("#partsTable thead tr:first th").length;
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
$("#partsTable").css("width", total + "px");
}
function addResizers() {
const $table = $("#partsTable");
if (!$table.length) return;
$table.find("thead tr:first th").each(function (colIndex) {
const $th = $(this);
// Salta colonna Num (indice 0) — non ridimensionabile
if (colIndex === 0) return;
// Non aggiungere due volte
if ($th.find(".parts-resizer").length) return;
const $resizer = $("<span class='parts-resizer'></span>");
$th.css("position", "relative"); // necessario per il posizionamento assoluto
$th.append($resizer);
$resizer.on("mousedown", function (e) {
e.preventDefault();
e.stopPropagation();
const startX = e.pageX;
// Leggi la larghezza ESATTA dalla <col>, non dal <th>
const startWidth =
savedWidths[colIndex] !== undefined
? savedWidths[colIndex]
: parseInt(
getColgroup()
.find("col")
.eq(colIndex)
.css("width"),
10,
) || 150;
$("body")
.css("user-select", "none")
.css("cursor", "col-resize");
$(document).on("mousemove.partsResize", function (ev) {
const delta = ev.pageX - startX;
applyColumnWidth(colIndex, startWidth + delta);
});
$(document).on("mouseup.partsResize", function () {
$("body").css("user-select", "").css("cursor", "");
$(document).off(".partsResize");
});
});
});
}
function init() {
syncColgroupToHeaders();
addResizers();
}
// Init al primo show del modale
$(document).on("shown.bs.modal", "#partsModal", function () {
setTimeout(init, 120);
});
// Re-init dopo ogni AJAX (nuove righe, caricamenti)
$(document).ajaxComplete(function () {
if ($("#partsModal").hasClass("show")) {
setTimeout(syncColgroupToHeaders, 200);
setTimeout(addResizers, 220);
}
});
// Re-init dopo aggiunta/rimozione righe
$(document).on(
"click",
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, #renumberPartsBtn, #clonePartsBtn",
function () {
setTimeout(syncColgroupToHeaders, 120);
setTimeout(addResizers, 140);
},
);
window.initPartsResizableColumns = init;
window.applyPartsColumnWidths = syncColgroupToHeaders;
})();

View File

@ -1,36 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
set_time_limit(20);
try {
$api = VisualLimsApiClient::getInstance();
$start = microtime(true);
// Chiamata minima: 1 solo record, 1 solo campo
$data = $api->get('Rapporto', [
'$top' => 1,
'$select' => 'IdRapporto'
]);
$elapsed = round(microtime(true) - $start, 3);
echo json_encode([
'success' => true,
'elapsed_seconds' => $elapsed,
'data' => $data
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -10,9 +10,6 @@ session_start();
// Includi PHPSpreadsheet // Includi PHPSpreadsheet
require_once '../../vendor/autoload.php'; require_once '../../vendor/autoload.php';
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '']; $response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => ''];
try { try {

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ try {
$iddatadb = intval($_POST['iddatadb']); $iddatadb = intval($_POST['iddatadb']);
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null; $idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null; $clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
$testedComponent = isset($_POST['tested_component']) ? trim((string)$_POST['tested_component']) : null;
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
@ -144,11 +144,7 @@ try {
$setParts[] = "cliente_fornitore_id = ?"; $setParts[] = "cliente_fornitore_id = ?";
$params[] = $clienteFornitoreId; $params[] = $clienteFornitoreId;
} }
// 5a3) tested_component (se presente)
if (isset($testedComponent)) {
$setParts[] = "tested_component = ?";
$params[] = ($testedComponent === '') ? null : $testedComponent;
}
// 5b) fixed fields dal POST // 5b) fixed fields dal POST
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist), // QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale // ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale

View File

@ -16,11 +16,9 @@ if (!$data || !isset($data['id'])) {
exit; exit;
} }
$mappingId = (int)$data['id']; $mappingId = $data['id'];
$mappingType = $data['mapping_type'] ?? ''; $mappingType = $data['mapping_type'] ?? '';
$excelColumn = $data['excel_column'] ?? null; $excelColumn = $data['excel_column'] ?? null;
$jsonNode = $data['json_node'] ?? null;
$manualDefault = $data['manual_default'] ?? null; $manualDefault = $data['manual_default'] ?? null;
$autoValue = $data['auto_value'] ?? 'none'; $autoValue = $data['auto_value'] ?? 'none';
@ -28,7 +26,7 @@ $tablename = $data['tablename'] ?? '';
try { try {
// Normalize mapping type // Normalize mapping type
$allowedTypes = ['', 'xls', 'json', 'manual', 'auto']; $allowedTypes = ['', 'xls', 'manual', 'auto'];
if (!in_array($mappingType, $allowedTypes, true)) { if (!in_array($mappingType, $allowedTypes, true)) {
echo json_encode(["success" => false, "message" => "Invalid mapping_type"]); echo json_encode(["success" => false, "message" => "Invalid mapping_type"]);
exit; exit;
@ -43,44 +41,28 @@ try {
// Decide what to persist based on mapping_type // Decide what to persist based on mapping_type
$isManual = 0; $isManual = 0;
$excelToSave = null; $excelToSave = null;
$jsonNodeToSave = null;
$manualToSave = null; $manualToSave = null;
$autoToSave = 'none'; $autoToSave = 'none';
if ($mappingType === 'xls') { if ($mappingType === 'xls') {
$isManual = 0; $isManual = 0;
$excelToSave = $excelColumn ?: null; $excelToSave = $excelColumn ?: null;
$jsonNodeToSave = null;
$manualToSave = null;
$autoToSave = 'none';
} elseif ($mappingType === 'json') {
$isManual = 0;
$excelToSave = null;
$jsonNodeToSave = $jsonNode ?: null;
$manualToSave = null; $manualToSave = null;
$autoToSave = 'none'; $autoToSave = 'none';
} elseif ($mappingType === 'manual') { } elseif ($mappingType === 'manual') {
$isManual = 1; $isManual = 1;
$excelToSave = null; $excelToSave = null;
$jsonNodeToSave = null;
$manualToSave = $manualDefault; $manualToSave = $manualDefault;
$autoToSave = 'none'; $autoToSave = 'none';
} elseif ($mappingType === 'auto') { } elseif ($mappingType === 'auto') {
$isManual = 0; $isManual = 0;
$excelToSave = null; $excelToSave = null;
$jsonNodeToSave = null;
$manualToSave = null; $manualToSave = null;
$autoToSave = $autoValue ?: 'none'; $autoToSave = $autoValue ?: 'none';
} else { } else {
// reset
// Reset mapping
$isManual = 0; $isManual = 0;
$excelToSave = null; $excelToSave = null;
$jsonNodeToSave = null;
$manualToSave = null; $manualToSave = null;
$autoToSave = 'none'; $autoToSave = 'none';
} }
@ -90,20 +72,12 @@ try {
SET SET
is_manual = ?, is_manual = ?,
excel_column = ?, excel_column = ?,
json_node = ?,
manual_default = ?, manual_default = ?,
auto_value = ? auto_value = ?
WHERE id = ? WHERE id = ?
"); ");
$result = $stmt->execute([ $result = $stmt->execute([$isManual, $excelToSave, $manualToSave, $autoToSave, $mappingId]);
$isManual,
$excelToSave,
$jsonNodeToSave,
$manualToSave,
$autoToSave,
$mappingId
]);
if (!$result) { if (!$result) {
echo json_encode(["success" => false, "message" => "Database update failed"]); echo json_encode(["success" => false, "message" => "Database update failed"]);
@ -114,17 +88,15 @@ try {
"success" => true, "success" => true,
"message" => "Mapping updated successfully", "message" => "Mapping updated successfully",
"saved" => [ "saved" => [
"id" => $mappingId, "id" => (int)$mappingId,
"mapping_type" => $mappingType, "mapping_type" => $mappingType,
"is_manual" => $isManual, "is_manual" => $isManual,
"excel_column" => $excelToSave, "excel_column" => $excelToSave,
"json_node" => $jsonNodeToSave,
"manual_default" => $manualToSave, "manual_default" => $manualToSave,
"auto_value" => $autoToSave "auto_value" => $autoToSave
] ]
]); ]);
} catch (Throwable $e) { } catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]); echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
} }
exit; exit;

File diff suppressed because one or more lines are too long

View File

@ -653,7 +653,7 @@
"IdSchemaCustomFields": 163, "IdSchemaCustomFields": 163,
"ConteggioClienti": 0, "ConteggioClienti": 0,
"Nome": "Devred", "Nome": "Devred",
"Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024 aggiornato il 23\/04\/2026\r\n\r\n" "Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024\r\n\r\n"
}, },
{ {
"IdSchemaCustomFields": 164, "IdSchemaCustomFields": 164,
@ -760,7 +760,7 @@
{ {
"IdSchemaCustomFields": 182, "IdSchemaCustomFields": 182,
"ConteggioClienti": 0, "ConteggioClienti": 0,
"Nome": "Ralph Lauren - All testing V.10", "Nome": "Ralph Lauren - All testing V.6",
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione" "Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
}, },
{ {
@ -876,12 +876,6 @@
"ConteggioClienti": 0, "ConteggioClienti": 0,
"Nome": "ROSSIMODA", "Nome": "ROSSIMODA",
"Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n" "Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n"
},
{
"IdSchemaCustomFields": 202,
"ConteggioClienti": 0,
"Nome": "LIMS-CIM - MAX MARA",
"Descrizione": "Schema per MAX MARA scambio dati Database"
} }
] ]
} }

View File

@ -2,12 +2,7 @@
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php'; require_once __DIR__ . '/class/db-functions.php';
include dirname(__DIR__) . '/../extra/auth.php'; include dirname(__DIR__) . '/../extra/auth.php';
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
if (!Auth::check()) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
require_once __DIR__ . '/class/VisualLimsApiClient.class.php'; require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
@ -19,95 +14,44 @@ $q = mb_strtolower(trim($_GET['q'] ?? ''));
$limit = max(1, min(50, intval($_GET['limit'] ?? 20))); $limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
$id = isset($_GET['id']) ? intval($_GET['id']) : null; $id = isset($_GET['id']) ? intval($_GET['id']) : null;
function formatClientLabel(array $client): string
{
$name = trim($client['Nominativo'] ?? '');
$id = trim((string)($client['IdCliente'] ?? ''));
$code = trim((string)($client['CodiceCliente'] ?? ''));
$parts = explode('_', $code);
$suffix = trim($parts[1] ?? '');
if ($suffix === '' && $code !== '') {
$suffix = substr($code, 0, 1);
}
if ($suffix === '') {
$suffix = '--';
}
return $name . ' - ' . $suffix . ' (ID: ' . $id . ')';
}
try { try {
// Load from cache or API // Load from cache or API
$cacheFile = __DIR__ . '/cache/clienti.json'; $cacheFile = __DIR__ . '/cache/clienti.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) { if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
$data = json_decode(file_get_contents($cacheFile), true); $data = json_decode(file_get_contents($cacheFile), true);
} else { } else {
$api = VisualLimsApiClient::getInstance(); $api = VisualLimsApiClient::getInstance();
$params = [ $params = [
'$select' => 'IdCliente,Nominativo,CodiceCliente', '$select' => 'IdCliente,Nominativo,CodiceCliente',
'$orderby' => 'Nominativo asc' '$orderby' => 'Nominativo asc'
]; ];
$data = $api->get("Cliente?" . http_build_query($params)); $data = $api->get("Cliente?" . http_build_query($params));
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
if (!is_dir(__DIR__ . '/cache')) {
mkdir(__DIR__ . '/cache', 0777, true);
}
file_put_contents($cacheFile, json_encode($data)); file_put_contents($cacheFile, json_encode($data));
} }
$clients = $data['value'] ?? []; $clients = $data['value'] ?? [];
// If requesting by specific ID, used for loading selected value // If requesting by specific ID (for loading selected value)
if ($id !== null) { if ($id !== null) {
foreach ($clients as $c) { foreach ($clients as $c) {
if ((int)$c['IdCliente'] === $id) { if ((int)$c['IdCliente'] === $id) {
echo json_encode([ echo json_encode(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
'results' => [[
'id' => $c['IdCliente'],
'text' => formatClientLabel($c),
'IdCliente' => $c['IdCliente'],
'Nominativo' => trim($c['Nominativo'] ?? ''),
'CodiceCliente' => trim($c['CodiceCliente'] ?? '')
]]
]);
exit; exit;
} }
} }
echo json_encode(['results' => []]); echo json_encode(['results' => []]);
exit; exit;
} }
// Search by query // Search by query
$results = []; $results = [];
foreach ($clients as $c) { foreach ($clients as $c) {
$name = trim($c['Nominativo'] ?? ''); $name = trim($c['Nominativo'] ?? '');
$code = trim($c['CodiceCliente'] ?? ''); $code = trim($c['CodiceCliente'] ?? '');
if ($q === '' || mb_strpos(mb_strtolower($name), $q) !== false || mb_strpos(mb_strtolower($code), $q) !== false) {
if ( $results[] = ['id' => $c['IdCliente'], 'text' => $name];
$q === '' || if (count($results) >= $limit) break;
mb_strpos(mb_strtolower($name), $q) !== false ||
mb_strpos(mb_strtolower($code), $q) !== false
) {
$results[] = [
'id' => $c['IdCliente'],
'text' => formatClientLabel($c),
'IdCliente' => $c['IdCliente'],
'Nominativo' => $name,
'CodiceCliente' => $code
];
if (count($results) >= $limit) {
break;
}
} }
} }

View File

@ -1,73 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Esempio: search_rapporto_min.php?codice=2621716
$codice = trim($_GET['codice'] ?? '');
if ($codice === '') {
throw new Exception("Parametro codice mancante. Usa ?codice=2621716");
}
/*
* Minimal:
* - niente expand
* - max 5 record
* - pochi campi
*
* Se il campo Codice non è quello giusto, sotto proviamo anche CodiceRapporto.
*/
$attempts = [];
$filters = [
'Codice' => "Codice eq '{$codice}'",
'CodiceRapporto' => "CodiceRapporto eq '{$codice}'",
'Numero' => "Numero eq '{$codice}'"
];
foreach ($filters as $label => $filter) {
try {
$data = $api->get('Rapporto', [
'$filter' => $filter,
'$top' => 5,
'$select' => 'IdRapporto,Codice,CodiceRapporto,Numero,Data,Versione,Cliente'
]);
$attempts[$label] = [
'success' => true,
'filter' => $filter,
'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
'data' => $data
];
} catch (Exception $e) {
$attempts[$label] = [
'success' => false,
'filter' => $filter,
'error' => $e->getMessage()
];
}
}
echo json_encode([
'success' => true,
'input_codice' => $codice,
'attempts' => $attempts
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}

View File

@ -8,9 +8,6 @@ ini_set('log_errors', 1);
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php'; require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
header('Content-Type: application/json'); header('Content-Type: application/json');
try { try {

View File

@ -1,117 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
ini_set('display_errors', '1');
error_reporting(E_ALL);
set_time_limit(60);
/**
* Uso: get_pdf.php?id=591749
*/
try {
$api = VisualLimsApiClient::getInstance();
$idRapportoFile = isset($_GET['id']) ? intval($_GET['id']) : 0;
if (!$idRapportoFile) {
throw new Exception("Parametro id mancante. Usa ?id=591749");
}
// STEP 1: leggi metadati entita
$entityData = $api->get("RapportoFile(" . $idRapportoFile . ")");
$pdfBody = null;
$strategyUsed = null;
$debugLog = array();
// Strategia A - campo FileContent base64
if (!empty($entityData['FileContent'])) {
$decoded = base64_decode($entityData['FileContent'], true);
if ($decoded !== false && substr($decoded, 0, 4) === '%PDF') {
$pdfBody = $decoded;
$strategyUsed = 'A: FileContent base64';
}
}
// Strategia B - campo Contenuto base64
if (!$pdfBody && !empty($entityData['Contenuto'])) {
$decoded = base64_decode($entityData['Contenuto'], true);
if ($decoded !== false && substr($decoded, 0, 4) === '%PDF') {
$pdfBody = $decoded;
$strategyUsed = 'B: Contenuto base64';
}
}
// Recupera token e baseUrl dalla classe
$token = $api->getToken();
$baseUrl = rtrim($api->getBaseUrl(), '/');
$rawEndpoints = array(
'C: $value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/$value',
'D: FileContent/$value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/FileContent/$value',
'E: Contenuto/$value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/Contenuto/$value',
);
foreach ($rawEndpoints as $label => $url) {
if ($pdfBody) break;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' . $token,
'Accept: application/pdf, application/octet-stream, */*',
));
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$ct = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
$debugLog[$label] = array(
'url' => $url,
'http_code' => $httpCode,
'content_type' => $ct,
'body_start' => substr((string)$body, 0, 300),
);
if ($httpCode === 200 && is_string($body) && substr($body, 0, 4) === '%PDF') {
$pdfBody = $body;
$strategyUsed = $label;
}
}
// RISPOSTA
if ($pdfBody) {
$fileName = isset($entityData['FileName']) ? $entityData['FileName'] : 'rapporto_' . $idRapportoFile . '.pdf';
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . $fileName . '"');
header('Content-Length: ' . strlen($pdfBody));
header('Cache-Control: private, max-age=0, must-revalidate');
echo $pdfBody;
exit;
}
// Nessuna strategia ha funzionato
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array(
'success' => false,
'message' => 'Nessuna strategia ha restituito un PDF valido.',
'id_rapporto_file' => $idRapportoFile,
'entity_fields' => array_keys($entityData ? $entityData : array()),
'entity_data' => $entityData,
'strategies_debug' => $debugLog,
), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array(
'success' => false,
'error' => $e->getMessage(),
), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}

View File

@ -1,6 +1,10 @@
<?php <?php
include('include/headscript.php'); include('include/headscript.php');
// ✅ FIX timezone (Rome)
ini_set('date.timezone', 'Europe/Rome');
date_default_timezone_set('Europe/Rome');
$template_id = intval($_GET['id'] ?? 0); $template_id = intval($_GET['id'] ?? 0);
if (!$template_id) { if (!$template_id) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante")); header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
@ -2012,51 +2016,6 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== '')); el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== ''));
}); });
})(); })();
// ── Column resize ──
(function() {
const resizers = document.querySelectorAll('.resizer');
let currentResizer = null, startX = 0, startWidth = 0, columnIndex = null;
function resize(e) {
if (!currentResizer || columnIndex === null) return;
const newWidth = Math.max(80, startWidth + (e.pageX - startX));
const sel = '[data-index="' + columnIndex + '"]';
const header = document.querySelector('.grid-header' + sel);
if (header) header.style.flex = '0 0 ' + newWidth + 'px';
const topCell = document.querySelector('.grid-top .grid-cell' + sel);
if (topCell) topCell.style.flex = '0 0 ' + newWidth + 'px';
document.querySelectorAll('.grid-row .grid-cell' + sel).forEach(function(cell) {
cell.style.flex = '0 0 ' + newWidth + 'px';
});
var filterCell = document.querySelector('#gridFilterRow > .grid-cell:nth-child(' + (parseInt(columnIndex, 10) + 1) + ')');
if (filterCell) filterCell.style.flex = '0 0 ' + newWidth + 'px';
}
function stopResize() {
if (currentResizer) {
document.removeEventListener('mousemove', resize);
document.removeEventListener('mouseup', stopResize);
currentResizer = null;
columnIndex = null;
}
}
resizers.forEach(function(resizer) {
resizer.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
currentResizer = this;
var header = this.closest('.grid-header');
if (!header) return;
columnIndex = header.getAttribute('data-index');
startX = e.pageX;
startWidth = header.offsetWidth;
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
});
});
})();
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,63 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
throw new Exception('Invalid JSON payload');
}
$templateId = isset($input['template_id']) ? (int)$input['template_id'] : 0;
$apiSampleJson = $input['api_sample_json'] ?? '';
$jsonNodes = $input['json_nodes'] ?? '';
if ($templateId <= 0) {
throw new Exception('Invalid template ID');
}
if (trim($apiSampleJson) === '') {
throw new Exception('API sample JSON is empty');
}
json_decode($apiSampleJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid API sample JSON: ' . json_last_error_msg());
}
$decodedNodes = json_decode($jsonNodes, true);
if (!is_array($decodedNodes)) {
throw new Exception('Invalid JSON nodes array');
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("
UPDATE excel_templates
SET
api_sample_json = ?,
json_nodes = ?
WHERE id = ?
");
$stmt->execute([
$apiSampleJson,
json_encode($decodedNodes, JSON_UNESCAPED_UNICODE),
$templateId
]);
echo json_encode([
'success' => true,
'nodes_count' => count($decodedNodes)
]);
} catch (Throwable $e) {
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
}