Compare commits
16 Commits
feature/mi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 813bd66f96 | |||
| ce00247d1c | |||
| 3a7dd266c8 | |||
| ba8dc4c721 | |||
| cfbbc36116 | |||
| 50d578eea1 | |||
| 6b0d2aa9b9 | |||
| fa7997c980 | |||
| 66be442eb6 | |||
| 19a2d6e3f7 | |||
| a15ab08576 | |||
| f71e8a56b5 | |||
| cb38bfb75a | |||
| 28a708dad3 | |||
| 6b9cf20ab9 | |||
| d40fc7d177 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -40,10 +40,13 @@ 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
|
||||||
@ -55,3 +58,5 @@ yarn-error.log
|
|||||||
|
|
||||||
# Ignora tutti i log ovunque
|
# Ignora tutti i log ovunque
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
public/userarea/cache/
|
||||||
@ -3,6 +3,9 @@ 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 {
|
||||||
|
|||||||
@ -255,4 +255,39 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ 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;
|
||||||
|
|||||||
131
public/userarea/clone_parts_to_visible.php
Normal file
131
public/userarea/clone_parts_to_visible.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@ 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'];
|
||||||
|
|||||||
112
public/userarea/download_rapporto_file.php
Normal file
112
public/userarea/download_rapporto_file.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
@ -25,6 +25,41 @@ 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">
|
||||||
@ -35,6 +70,64 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<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>
|
||||||
@ -123,12 +216,46 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<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">
|
||||||
@ -258,6 +385,16 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
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';
|
||||||
@ -280,9 +417,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
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(`${nome.trim()} (ID: ${id})`, id);
|
const option = new Option(formatClientLabel(client), id);
|
||||||
|
|
||||||
if (parseInt(id) === parseInt(selectedClientId)) {
|
if (parseInt(id) === parseInt(selectedClientId)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
|
|||||||
@ -328,3 +328,6 @@
|
|||||||
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
|
||||||
|
|||||||
@ -542,7 +542,23 @@ 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,
|
||||||
@ -557,7 +573,7 @@ try {
|
|||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
"idcommessaweb" => $commessaId,
|
"idcommessaweb" => $commessaId,
|
||||||
"commessaweb" => $commessaWebCode,
|
"commessaweb" => $finalCodiceCommessa ?: $commessaWebCode,
|
||||||
"commessaWeb" => $finalCommessa,
|
"commessaWeb" => $finalCommessa,
|
||||||
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
|
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
|
||||||
"totalCampioni" => count($campioni),
|
"totalCampioni" => count($campioni),
|
||||||
|
|||||||
95
public/userarea/get_analisiabilitate.php
Normal file
95
public/userarea/get_analisiabilitate.php
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
88
public/userarea/get_campionematrice.php
Normal file
88
public/userarea/get_campionematrice.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
256
public/userarea/get_rapporto_full_by_codice.php
Normal file
256
public/userarea/get_rapporto_full_by_codice.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
118
public/userarea/get_rapporto_lims.php
Normal file
118
public/userarea/get_rapporto_lims.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
@ -2,25 +2,140 @@
|
|||||||
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');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Costruzione manuale dell'endpoint con espansione annidata
|
// Esempi:
|
||||||
$endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
|
// rapporto_by_codice_expand_step.php?codice=2541111&step=base
|
||||||
|
// 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
|
||||||
|
|
||||||
// Non passiamo options, già incluso nell'endpoint
|
$codiceRapporto = trim($_GET['codice'] ?? '');
|
||||||
$data = $api->get($endpoint);
|
$step = trim($_GET['step'] ?? 'base');
|
||||||
|
|
||||||
file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
if ($codiceRapporto === '') {
|
||||||
echo json_encode($data);
|
throw new Exception("Parametro codice mancante. Usa ?codice=2541111");
|
||||||
} catch (Exception $e) {
|
}
|
||||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
|
||||||
http_response_code(500);
|
$allowedSteps = [
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
'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) {
|
||||||
|
file_put_contents(
|
||||||
|
__DIR__ . '/error_log.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);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,65 +10,125 @@
|
|||||||
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Layout flessibile per gestire dimensioni diverse */
|
/* Main buttons container */
|
||||||
#templateButtons {
|
.template-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
/* Allinea a sinistra */
|
padding: 10px 0;
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Definizione delle dimensioni */
|
/* Button sizes */
|
||||||
/* 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: 30px;
|
min-height: 36px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
/* Aggiunto */
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
/* Aggiunto */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* Aggiunto */
|
gap: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-medium {
|
.btn-medium {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
min-width: 130px;
|
min-width: 140px;
|
||||||
min-height: 45px;
|
min-height: 48px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
/* Aggiunto */
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
/* Aggiunto */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* Aggiunto */
|
gap: 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-large {
|
.btn-large {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
padding: 14px 28px;
|
padding: 14px 28px;
|
||||||
min-width: 180px;
|
min-width: 190px;
|
||||||
min-height: 60px;
|
min-height: 64px;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
/* Aggiunto */
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
/* Aggiunto */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* Aggiunto */
|
gap: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stile della barra di ricerca */
|
.template-icon {
|
||||||
|
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;
|
padding: 10px 14px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 18px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #d9d9d9;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
|
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>
|
||||||
@ -87,14 +147,54 @@
|
|||||||
<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 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 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>
|
</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'); ?>
|
||||||
@ -104,47 +204,138 @@
|
|||||||
|
|
||||||
<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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let templateButtons = document.getElementById("templateButtons");
|
if (!Array.isArray(data.data)) {
|
||||||
templateButtons.innerHTML = '';
|
clearContainers();
|
||||||
|
Object.values(containers).forEach(container => {
|
||||||
data.data.forEach(template => {
|
container.innerHTML = `<div class="empty-message">Invalid response format.</div>`;
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
})
|
return;
|
||||||
.catch(error => console.error("Fetch error:", error));
|
|
||||||
|
|
||||||
// Funzione per la ricerca live
|
|
||||||
document.getElementById("searchInput").addEventListener("input", function() {
|
|
||||||
let searchValue = this.value.toLowerCase();
|
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allTemplates.push(...data.data);
|
||||||
|
renderTemplates();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Fetch error:", error);
|
||||||
|
clearContainers();
|
||||||
|
Object.values(containers).forEach(container => {
|
||||||
|
container.innerHTML = `<div class="empty-message">Fetch error while loading templates.</div>`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("searchInput").addEventListener("input", function() {
|
||||||
|
const searchValue = this.value.toLowerCase().trim();
|
||||||
|
renderTemplates(searchValue);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
<?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;
|
||||||
|
|||||||
@ -40,6 +40,22 @@ 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%;
|
||||||
@ -324,8 +340,12 @@ 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="table-container">
|
<div class="top-scrollbar" id="topTableScrollbar">
|
||||||
<table class="table table-striped table-bordered">
|
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container" id="mainTableContainer">
|
||||||
|
<table class="table table-striped table-bordered" id="importPreviewTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
||||||
@ -363,6 +383,47 @@ 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');
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
<?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"));
|
||||||
@ -202,13 +198,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) {
|
||||||
@ -267,7 +263,12 @@ $gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_
|
|||||||
|
|
||||||
// 5. Auto fields
|
// 5. Auto fields
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
if (
|
||||||
|
!$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'],
|
||||||
@ -284,7 +285,12 @@ foreach ($allMappings as $mapping) {
|
|||||||
|
|
||||||
// 6. Manual fields
|
// 6. Manual fields
|
||||||
foreach ($allMappings as $mapping) {
|
foreach ($allMappings as $mapping) {
|
||||||
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
if (
|
||||||
|
$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'],
|
||||||
@ -364,20 +370,10 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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 {
|
||||||
@ -416,17 +412,9 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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;
|
||||||
@ -463,6 +451,21 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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;
|
||||||
@ -892,9 +895,17 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes new-row-pulse {
|
@keyframes new-row-pulse {
|
||||||
0%, 100% { background-color: transparent; }
|
|
||||||
50% { background-color: #cce5ff; }
|
0%,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@ -1011,11 +1022,7 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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 {
|
||||||
@ -1029,21 +1036,25 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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;
|
||||||
@ -1052,24 +1063,41 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
.pager-limit-btn:last-child {
|
||||||
|
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;
|
||||||
@ -1083,21 +1111,152 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
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>
|
||||||
@ -1179,9 +1338,9 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<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>
|
||||||
@ -1201,13 +1360,18 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="editForm">
|
<form id="editForm">
|
||||||
<div class="grid-container">
|
<div class="top-scrollbar" id="topScrollbar">
|
||||||
|
<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>
|
||||||
@ -1218,7 +1382,12 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<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>.btn i { margin-top: 0 !important; margin-bottom: 0 !important; }</style>
|
<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>
|
||||||
@ -1231,6 +1400,54 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
|||||||
<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">
|
||||||
|
|||||||
@ -10,6 +10,9 @@ 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.
|
||||||
|
|||||||
@ -13,7 +13,10 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recupera solo i template attivi
|
// Recupera solo i template attivi
|
||||||
$stmt = $pdo->query("SELECT id, button_label, button_bg_color, button_text_color, button_size FROM excel_templates WHERE status = 'active'");
|
$stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type
|
||||||
|
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;
|
||||||
|
|||||||
@ -7,7 +7,26 @@ 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);
|
||||||
|
|
||||||
@ -15,13 +34,43 @@ 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("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 = ?");
|
$stmt = $pdo->prepare("
|
||||||
|
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);
|
||||||
|
|
||||||
@ -37,11 +86,25 @@ $fixedMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
$hasFixedMappings = !empty($fixedMappings);
|
$hasFixedMappings = !empty($fixedMappings);
|
||||||
|
|
||||||
// Recupera le colonne già associate nel database
|
// Recupera le colonne XLS 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>
|
||||||
@ -163,7 +226,8 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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;
|
||||||
@ -176,6 +240,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#apiJsonExample {
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -195,13 +264,19 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<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">
|
||||||
@ -219,6 +294,33 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<?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">
|
||||||
@ -236,15 +338,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<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>Mapping</th>
|
<th><?php echo $sourceType === 'API' ? 'JSON Mapping' : '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>
|
||||||
@ -268,7 +366,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<?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): ?>
|
||||||
@ -276,7 +373,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<?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>
|
||||||
@ -286,9 +382,19 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
$autoValue = $mapping['auto_value'] ?? 'none';
|
$autoValue = $mapping['auto_value'] ?? 'none';
|
||||||
$hasAuto = ($autoValue && $autoValue !== 'none');
|
$hasAuto = ($autoValue && $autoValue !== 'none');
|
||||||
|
|
||||||
$mappingValue = $isSceltaMultipla
|
if ($isSceltaMultipla) {
|
||||||
? 'manual'
|
$mappingValue = 'manual';
|
||||||
: ($hasAuto ? 'auto' : ($mapping['excel_column'] ? 'xls' : ((int)$mapping['is_manual'] === 1 ? 'manual' : '')));
|
} elseif ($hasAuto) {
|
||||||
|
$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"
|
||||||
@ -298,18 +404,34 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
<?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']) . '"' : ''; ?>>
|
<?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column'], ENT_QUOTES, 'UTF-8') . '"' : ''; ?>>
|
||||||
</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;"
|
||||||
@ -321,7 +443,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<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 (!empty($mapping['excel_column'])): ?>
|
<?php if ($sourceType === 'XLS' && !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>
|
||||||
@ -330,6 +452,15 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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>
|
||||||
|
|
||||||
@ -412,7 +543,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<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>
|
||||||
@ -461,8 +591,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
</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>
|
||||||
@ -479,10 +607,18 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
<?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); ?> || [];
|
||||||
|
|
||||||
document.getElementById('xlsUpload').addEventListener('change', function(event) {
|
let availableJsonNodes = <?php echo json_encode($jsonNodes); ?> || [];
|
||||||
|
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;
|
||||||
|
|
||||||
@ -511,6 +647,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function processXLSX(file) {
|
function processXLSX(file) {
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
@ -534,8 +671,14 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
|
|
||||||
// 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: { r: m.s.r, c: m.s.c - colOffset },
|
s: {
|
||||||
e: { r: m.e.r, c: m.e.c - colOffset }
|
r: m.s.r,
|
||||||
|
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) + ')');
|
||||||
|
|
||||||
@ -642,7 +785,14 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
matched.push(cellVal);
|
matched.push(cellVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rowScores.push({ row: r + 1, score, nonEmpty: nonEmptyCount, matched, firstNonEmpty: firstNonEmpty + 1, cells: cellValues });
|
rowScores.push({
|
||||||
|
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;
|
||||||
@ -765,16 +915,22 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
let currentValue = select.value || select.dataset.currentXls || '';
|
let currentValue = select.value || select.dataset.currentXls || '';
|
||||||
|
|
||||||
let options = availableXlsColumns
|
let options = availableXlsColumns
|
||||||
.map((col, origIdx) => ({ col, origIdx }))
|
.map((col, origIdx) => ({
|
||||||
.map(({ col, origIdx }) => {
|
col,
|
||||||
|
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>`;
|
||||||
})
|
})
|
||||||
@ -785,6 +941,88 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtmlText(str) {
|
||||||
|
return String(str ?? '')
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll("'", ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
@ -858,7 +1096,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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); ?>;
|
||||||
@ -870,6 +1107,72 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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) {
|
||||||
@ -982,6 +1285,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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,
|
||||||
@ -993,7 +1297,14 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMapping(mappingId, 'manual', el.value, xlsSelect ? xlsSelect.value : null);
|
saveMapping(
|
||||||
|
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)
|
||||||
@ -1006,6 +1317,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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,
|
||||||
@ -1017,11 +1329,17 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMapping(mappingId, 'manual', el.value, xlsSelect ? xlsSelect.value : null);
|
saveMapping(
|
||||||
|
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']; ?>`);
|
||||||
@ -1084,60 +1402,118 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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') {
|
||||||
xlsSelect.style.display = 'block';
|
if (xlsSelect) 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 (removeBtn) removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
|
if (mappedJsonNode) mappedJsonNode.style.display = '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') {
|
||||||
xlsSelect.style.display = 'none';
|
if (xlsSelect) 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 = ''; // libera UI
|
xlsSelect.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
xlsSelect.style.display = 'none';
|
if (xlsSelect) 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.value,
|
manualInput ? manualInput.value : '',
|
||||||
xlsSelect.value,
|
xlsSelect ? xlsSelect.value : null,
|
||||||
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;
|
||||||
@ -1236,6 +1612,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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;
|
||||||
@ -1244,6 +1621,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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(
|
||||||
@ -1251,9 +1629,11 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
'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');
|
||||||
@ -1286,7 +1666,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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);
|
saveMapping(mappingId, '', '', null, null, null);
|
||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1304,21 +1684,75 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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.value, event.target.value);
|
saveMapping(mappingId, 'xls', manualInput ? manualInput.value : '', event.target.value, autoSelect ? autoSelect.value : null, null);
|
||||||
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(mappingId, 'manual', event.target.value, xlsSelect.value);
|
saveMapping(
|
||||||
|
mappingId,
|
||||||
|
'manual',
|
||||||
|
event.target.value,
|
||||||
|
xlsSelect ? xlsSelect.value : null,
|
||||||
|
null,
|
||||||
|
jsonSelect ? jsonSelect.value : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1327,11 +1761,19 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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(mappingId, 'manual', event.target.value, xlsSelect.value);
|
saveMapping(
|
||||||
|
mappingId,
|
||||||
|
'manual',
|
||||||
|
event.target.value,
|
||||||
|
xlsSelect ? xlsSelect.value : null,
|
||||||
|
null,
|
||||||
|
jsonSelect ? jsonSelect.value : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1350,17 +1792,49 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
console.log("Removing XLS mapping:", {
|
console.log("Removing XLS mapping:", {
|
||||||
id: mappingId
|
id: mappingId
|
||||||
});
|
});
|
||||||
saveMapping(mappingId, '', '', null);
|
saveMapping(mappingId, '', '', null, null, null);
|
||||||
updateXlsDropdowns();
|
updateXlsDropdowns();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function saveMapping(mappingId, mappingType, defaultValue, excelColumn, autoValue) {
|
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
|
||||||
|
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,
|
||||||
manual_default: defaultValue
|
json_node: jsonNode,
|
||||||
|
manual_default: defaultValue,
|
||||||
|
auto_value: autoValue
|
||||||
});
|
});
|
||||||
fetch('save_mapping_json.php', {
|
fetch('save_mapping_json.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -1371,6 +1845,7 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
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']; ?>"
|
||||||
@ -1379,11 +1854,17 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
.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 && excelColumn) {
|
if (data.success && mappingType === 'xls' && 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));
|
||||||
}
|
}
|
||||||
@ -1478,8 +1959,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Button: Add Fixed Fields
|
// Button: Add Fixed Fields
|
||||||
const btnAddFixedFields = document.getElementById('btnAddFixedFields');
|
const btnAddFixedFields = document.getElementById('btnAddFixedFields');
|
||||||
if (btnAddFixedFields) {
|
if (btnAddFixedFields) {
|
||||||
@ -1508,8 +1987,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
renderFixedRows(data.rows);
|
renderFixedRows(data.rows);
|
||||||
await fillFixedDropdowns();
|
await fillFixedDropdowns();
|
||||||
initSelect2ForFixedDropdowns();
|
initSelect2ForFixedDropdowns();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fixedStatus('✅ Fixed fields created');
|
fixedStatus('✅ Fixed fields created');
|
||||||
@ -1581,7 +2058,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Autosave: manual_default (debounce)
|
// Autosave: manual_default (debounce)
|
||||||
let fixedTimer = null;
|
let fixedTimer = null;
|
||||||
document.addEventListener('input', (e) => {
|
document.addEventListener('input', (e) => {
|
||||||
@ -1616,8 +2092,13 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
}, 350);
|
}, 350);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (sourceType === 'XLS' && availableXlsColumns.length) {
|
||||||
|
updateXlsDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
if (availableXlsColumns.length) updateXlsDropdowns();
|
if (sourceType === 'API' && availableJsonNodes.length) {
|
||||||
|
updateJsonDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
fillFixedDropdowns().then(() => {
|
fillFixedDropdowns().then(() => {
|
||||||
initSelect2ForFixedDropdowns();
|
initSelect2ForFixedDropdowns();
|
||||||
@ -1765,7 +2246,6 @@ $xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], t
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -26,10 +26,16 @@
|
|||||||
<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 -->
|
||||||
@ -42,7 +48,15 @@
|
|||||||
<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>
|
||||||
@ -82,6 +96,7 @@
|
|||||||
</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;">
|
||||||
@ -288,28 +303,20 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select delle righe (colonna Matrice) = 150px */
|
/* Select delle righe Matrice: si adatta alla colonna */
|
||||||
.part-matrice {
|
.part-matrice {
|
||||||
width: 300px !important;
|
width: 100% !important;
|
||||||
min-width: 300px !important;
|
min-width: 0 !important;
|
||||||
max-width: 300px !important;
|
max-width: 100% !important;
|
||||||
flex: 0 0 300px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.part-matrice.select2-hidden-accessible+.select2 {
|
.part-matrice.select2-hidden-accessible+.select2 {
|
||||||
width: 300px !important;
|
width: 100% !important;
|
||||||
min-width: 300px !important;
|
min-width: 0 !important;
|
||||||
max-width: 300px !important;
|
max-width: 100% !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;
|
||||||
@ -514,6 +521,77 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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>
|
||||||
@ -2,93 +2,111 @@
|
|||||||
* 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) modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true });
|
if (!modal)
|
||||||
|
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.split('|').map(s => s.trim()).filter(s => s.length > 0);
|
const parts = raw
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
@ -96,14 +114,23 @@
|
|||||||
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', { method: 'POST', body: formData });
|
await fetch("add_part_quick.php", {
|
||||||
|
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('');
|
$input.val(raw);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('Error: ' + e.message);
|
alert("Error: " + e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -111,44 +138,47 @@
|
|||||||
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 = deleteIddatadb;
|
document.getElementById("deleteIddatadbText").textContent =
|
||||||
|
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(r => r.iddatadb === deleteIddatadb);
|
const idx = window.gridData.findIndex(
|
||||||
|
(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;
|
||||||
@ -156,20 +186,26 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── 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) { alert('Template ID missing'); return; }
|
if (!templateId) {
|
||||||
|
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({ template_id: templateId, importreferencecode: importref })
|
body: JSON.stringify({
|
||||||
|
template_id: templateId,
|
||||||
|
importreferencecode: importref,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
|
|
||||||
@ -177,17 +213,20 @@
|
|||||||
// 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().toISOString().slice(0, 19).replace('T', ' '),
|
importdate: new Date()
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 19)
|
||||||
|
.replace("T", " "),
|
||||||
fixedFields: {},
|
fixedFields: {},
|
||||||
details: {},
|
details: {},
|
||||||
mainFieldValue: '',
|
mainFieldValue: "",
|
||||||
_dirty: false,
|
_dirty: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,20 +238,27 @@
|
|||||||
if (gr) gr.renderVisibleRows();
|
if (gr) gr.renderVisibleRows();
|
||||||
|
|
||||||
// Highlight new row briefly
|
// Highlight new row briefly
|
||||||
const newGridRow = document.querySelector(`.grid-row[data-id="${result.iddatadb}"]`);
|
const newGridRow = document.querySelector(
|
||||||
|
`.grid-row[data-id="${result.iddatadb}"]`,
|
||||||
|
);
|
||||||
if (newGridRow) {
|
if (newGridRow) {
|
||||||
newGridRow.classList.add('row-just-created');
|
newGridRow.classList.add("row-just-created");
|
||||||
newGridRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
newGridRow.scrollIntoView({
|
||||||
setTimeout(() => newGridRow.classList.remove('row-just-created'), 4000);
|
behavior: "smooth",
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1595,7 +1595,11 @@ $(document).ready(function () {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function (params) {
|
data: function (params) {
|
||||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
return {
|
||||||
|
q: params.term || "",
|
||||||
|
limit: 20,
|
||||||
|
macro: selectedMacro || "",
|
||||||
|
};
|
||||||
},
|
},
|
||||||
processResults: function (data) {
|
processResults: function (data) {
|
||||||
return { results: data.results || [] };
|
return { results: data.results || [] };
|
||||||
@ -1654,7 +1658,11 @@ $(document).ready(function () {
|
|||||||
dataType: "json",
|
dataType: "json",
|
||||||
delay: 150,
|
delay: 150,
|
||||||
data: function (params) {
|
data: function (params) {
|
||||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
return {
|
||||||
|
q: params.term || "",
|
||||||
|
limit: 20,
|
||||||
|
macro: selectedMacro || "",
|
||||||
|
};
|
||||||
},
|
},
|
||||||
processResults: function (data) {
|
processResults: function (data) {
|
||||||
return { results: data.results || [] };
|
return { results: data.results || [] };
|
||||||
@ -1800,7 +1808,9 @@ $(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(new Option(globalText, globalVal, true, true));
|
$target.append(
|
||||||
|
new Option(globalText, globalVal, true, true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$target.val(globalVal).trigger("change");
|
$target.val(globalVal).trigger("change");
|
||||||
});
|
});
|
||||||
@ -1969,7 +1979,109 @@ $(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;
|
||||||
|
|
||||||
@ -2351,4 +2463,142 @@ $(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;
|
||||||
|
})();
|
||||||
|
|||||||
36
public/userarea/ping_lims_api.php
Normal file
36
public/userarea/ping_lims_api.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
@ -10,6 +10,9 @@ 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
@ -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,7 +144,11 @@ 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
|
||||||
|
|||||||
@ -16,9 +16,11 @@ if (!$data || !isset($data['id'])) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mappingId = $data['id'];
|
$mappingId = (int)$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';
|
||||||
@ -26,7 +28,7 @@ $tablename = $data['tablename'] ?? '';
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Normalize mapping type
|
// Normalize mapping type
|
||||||
$allowedTypes = ['', 'xls', 'manual', 'auto'];
|
$allowedTypes = ['', 'xls', 'json', '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;
|
||||||
@ -41,28 +43,44 @@ 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';
|
||||||
}
|
}
|
||||||
@ -72,12 +90,20 @@ 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([$isManual, $excelToSave, $manualToSave, $autoToSave, $mappingId]);
|
$result = $stmt->execute([
|
||||||
|
$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"]);
|
||||||
@ -88,15 +114,17 @@ try {
|
|||||||
"success" => true,
|
"success" => true,
|
||||||
"message" => "Mapping updated successfully",
|
"message" => "Mapping updated successfully",
|
||||||
"saved" => [
|
"saved" => [
|
||||||
"id" => (int)$mappingId,
|
"id" => $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 (Exception $e) {
|
} catch (Throwable $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
@ -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\r\n\r\n"
|
"Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024 aggiornato il 23\/04\/2026\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.6",
|
"Nome": "Ralph Lauren - All testing V.10",
|
||||||
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -876,6 +876,12 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2,7 +2,12 @@
|
|||||||
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';
|
||||||
|
|
||||||
@ -14,44 +19,95 @@ $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 (for loading selected value)
|
// If requesting by specific ID, used 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(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
|
echo json_encode([
|
||||||
|
'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) {
|
|
||||||
$results[] = ['id' => $c['IdCliente'], 'text' => $name];
|
if (
|
||||||
if (count($results) >= $limit) break;
|
$q === '' ||
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
73
public/userarea/search_rapporto_min.php
Normal file
73
public/userarea/search_rapporto_min.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
@ -8,6 +8,9 @@ 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 {
|
||||||
|
|||||||
117
public/userarea/test_pdf_value.php
Normal file
117
public/userarea/test_pdf_value.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
@ -1,10 +1,6 @@
|
|||||||
<?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"));
|
||||||
@ -2016,6 +2012,51 @@ 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>
|
||||||
|
|||||||
63
public/userarea/update_api_json_nodes.php
Normal file
63
public/userarea/update_api_json_nodes.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user