first commit theloftstore

This commit is contained in:
Claudio 2025-11-12 18:43:50 +01:00
parent e75be99e43
commit 6b65b31932
480 changed files with 9352 additions and 57951 deletions

View File

@ -42,8 +42,9 @@
"laravel/tinker": "^2.7",
"laravel/ui": "^4.0",
"phpmailer/phpmailer": "^6.9",
"phpoffice/phpspreadsheet": "^4.1",
"phpoffice/phpspreadsheet": "^5.2",
"proengsoft/laravel-jsvalidation": "^4.0.0",
"smalot/pdfparser": "^2.12",
"socialiteproviders/microsoft": "^4.7",
"spatie/laravel-query-builder": "^5.0",
"vanguardapp/activity-log": "^6.0",

76
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9c4f1e3bc3ee2180211c055e70635aef",
"content-hash": "97382155de516648df8ab1f1c50ab1d5",
"packages": [
{
"name": "akaunting/laravel-setting",
@ -3839,16 +3839,16 @@
},
{
"name": "phpoffice/phpspreadsheet",
"version": "4.1.0",
"version": "5.2.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "6ff18c3a8df3a945492f75ce455d77f7ad55dd5c"
"reference": "3b8994b3aac4b61018bc04fc8c441f4fd68c18eb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/6ff18c3a8df3a945492f75ce455d77f7ad55dd5c",
"reference": "6ff18c3a8df3a945492f75ce455d77f7ad55dd5c",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3b8994b3aac4b61018bc04fc8c441f4fd68c18eb",
"reference": "3b8994b3aac4b61018bc04fc8c441f4fd68c18eb",
"shasum": ""
},
"require": {
@ -3878,18 +3878,19 @@
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.3",
"mitoteam/jpgraph": "^10.5",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions",
"ext-intl": "PHP Internationalization Functions, regquired for NumberFormat Wizard",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
@ -3938,9 +3939,9 @@
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/4.1.0"
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.2.0"
},
"time": "2025-03-02T06:52:24+00:00"
"time": "2025-10-26T15:54:22+00:00"
},
{
"name": "phpoption/phpoption",
@ -4980,6 +4981,57 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "smalot/pdfparser",
"version": "v2.12.1",
"source": {
"type": "git",
"url": "https://github.com/smalot/pdfparser.git",
"reference": "98d31ba34ef5b5a98897ef4b6c3925d502ea53b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smalot/pdfparser/zipball/98d31ba34ef5b5a98897ef4b6c3925d502ea53b1",
"reference": "98d31ba34ef5b5a98897ef4b6c3925d502ea53b1",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"ext-zlib": "*",
"php": ">=7.1",
"symfony/polyfill-mbstring": "^1.18"
},
"type": "library",
"autoload": {
"psr-0": {
"Smalot\\PdfParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
"authors": [
{
"name": "Sebastien MALOT",
"email": "sebastien@malot.fr"
}
],
"description": "Pdf parser library. Can read and extract information from pdf file.",
"homepage": "https://www.pdfparser.org",
"keywords": [
"extract",
"parse",
"parser",
"pdf",
"text"
],
"support": {
"issues": "https://github.com/smalot/pdfparser/issues",
"source": "https://github.com/smalot/pdfparser/tree/v2.12.1"
},
"time": "2025-07-31T06:19:56+00:00"
},
{
"name": "socialiteproviders/manager",
"version": "v4.8.1",
@ -11355,6 +11407,6 @@
"php": "^8.2.0",
"ext-json": "*"
},
"platform-dev": [],
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +0,0 @@
<?php
ob_start();
session_start();
require_once '../../vendor/autoload.php';
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$template_id = isset($input['template_id']) ? intval($input['template_id']) : 0;
$filename = $input['routine_data']['filename'] ?? '';
$headerrow = $input['routine_data']['headerrow'] ?? 1;
$excelData = $input['excel_data'] ?? [];
$routineData = $input['routine_data'] ?? [];
if (!$filename || empty($excelData)) {
throw new Exception("Dati della routine mancanti.");
}
$routineFile = __DIR__ . '/routines/' . $filename;
if (file_exists($routineFile)) {
include_once $routineFile;
$routineData['xls_headers'] = $_SESSION['headers'] ?? [];
applyRoutine($excelData, $routineData); // Modifica $excelData in place
error_log("Routine {$routineData['name']} applicata (file: {$filename}) per template {$template_id}, header row: {$headerrow}");
} else {
throw new Exception("File della routine non trovato: $routineFile");
}
// Aggiorna la sessione con i dati modificati
$_SESSION['excel_data'] = $excelData;
$response['excel_data'] = $excelData;
$response['rows'] = array_column($excelData, 'data');
$response['columns'] = $_SESSION['headers'];
$response['template_id'] = $template_id;
$response['filename'] = $input['filename'] ?? '';
} else {
$response['error'] = "Richiesta non valida.";
}
} catch (Exception $e) {
$response['error'] = "Errore durante l'applicazione della routine: " . $e->getMessage();
error_log("Exception in apply_routine.php: " . $e->getMessage());
}
ob_end_clean();
header('Content-Type: application/json');
echo json_encode($response);
exit;

View File

@ -0,0 +1,88 @@
<?php
// class/chatpdf_helper.php
class ChatPDFHelper
{
private $apiKey = 'sec_Wyk8yl97UBzDW85zVWsuk4YFpSPGlr2Q';
private $apiBase = 'https://api.chatpdf.com/v1';
// Carica un PDF e restituisce l'ID del file
public function uploadPdf($pdfPath)
{
$ch = curl_init("{$this->apiBase}/sources/add-file");
$cfile = new CURLFile($pdfPath, 'application/pdf', basename($pdfPath));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ['file' => $cfile],
CURLOPT_HTTPHEADER => [
"x-api-key: {$this->apiKey}"
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
return $result['sourceId'] ?? null;
}
// Esegue una domanda al PDF caricato
public function askPdf($sourceId, $question)
{
$apiKey = $_ENV['CHATPDF_API_KEY'] ?? null;
if (!$apiKey) {
throw new Exception("ChatPDF API key not found in .env");
}
$url = "https://api.chatpdf.com/v1/chats/message";
$payload = json_encode([
"sourceId" => $sourceId,
"messages" => [
[
"role" => "user",
"content" => $question
]
]
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"x-api-key: $apiKey",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => $payload,
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new Exception('cURL error: ' . curl_error($ch));
}
curl_close($ch);
$data = json_decode($response, true);
// Debug temporaneo
file_put_contents(
__DIR__ . '/../debug_chatpdf.log',
"Question: {$question}\nResponse: {$response}\n\n",
FILE_APPEND
);
if (isset($data['content'])) {
return trim($data['content']);
}
// ChatPDF a volte restituisce una lista di risposte
if (isset($data[0]['content'])) {
return trim($data[0]['content']);
}
return null;
}
}

View File

@ -0,0 +1,249 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// === Carica elenco clienti ===
$clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client Files Management</title>
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">
<style>
.upload-box {
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.select2-container {
width: 100% !important;
}
</style>
</head>
<body>
<?php include('include/navbar.php'); ?>
<div class="dashboard-main-wrapper wrapper">
<?php include('include/topbar.php'); ?>
<div class="dashboard-body">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-4">📁 Client File Management</h4>
<!-- Client selection -->
<div class="row mb-4">
<div class="col-md-4">
<label for="idclient" class="form-label">Select Client</label>
<select id="idclient" class="form-select select2" required>
<option value="">-- Choose Client --</option>
<?php foreach ($clients as $cl): ?>
<option value="<?= htmlspecialchars($cl['idclient']) ?>">
<?= htmlspecialchars($cl['client_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<!-- Upload sections -->
<div class="row">
<div class="col-md-6">
<div class="upload-box">
<h6>📄 Upload Mapping File (PDF)</h6>
<form id="uploadMappingForm" enctype="multipart/form-data">
<input type="hidden" name="file_type" value="mapping">
<div class="mb-3">
<input type="file" name="file" accept=".pdf" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">Upload Mapping</button>
</form>
</div>
</div>
<div class="col-md-6">
<div class="upload-box">
<h6>💰 Upload Price List (PDF)</h6>
<form id="uploadPriceForm" enctype="multipart/form-data">
<input type="hidden" name="file_type" value="pricelist">
<div class="mb-3">
<input type="file" name="file" accept=".pdf" class="form-control" required>
</div>
<button type="submit" class="btn btn-success w-100">Upload Pricelist</button>
</form>
</div>
</div>
</div>
<hr>
<h5 class="mt-4">📋 Uploaded Files</h5>
<div class="table-responsive">
<table id="clientFilesTable" class="table table-striped table-bordered w-100">
<thead>
<tr>
<th>ID</th>
<th>Client</th>
<th>File Type</th>
<th>Filename</th>
<th>Source ID</th>
<th>Uploaded By</th>
<th>Upload Date</th>
<th>Actions</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
$(document).ready(function() {
$('.select2').select2();
const table = $('#clientFilesTable').DataTable({
ajax: {
url: 'client_files_list.php',
dataSrc: ''
},
columns: [{
data: 'id'
},
{
data: 'client_name'
},
{
data: 'file_type'
},
{
data: 'filename'
},
{
data: 'source_id'
},
{
data: 'uploaded_by'
},
{
data: 'upload_date'
},
{
data: null,
orderable: false,
render: function(data, type, row) {
return `
<button class="btn btn-danger btn-sm delete-file" data-id="${row.id}">
<i class="fas fa-trash"></i>
</button>`;
}
}
],
pageLength: 10,
order: [
[0, 'desc']
]
});
// Upload handlers
function handleUpload(formSelector) {
$(formSelector).on('submit', function(e) {
e.preventDefault();
const idclient = $('#idclient').val();
if (!idclient) {
Swal.fire('Warning', 'Select a client first.', 'warning');
return;
}
const formData = new FormData(this);
formData.append('idclient', idclient);
$.ajax({
url: 'client_files_upload.php',
type: 'POST',
data: formData,
processData: false,
contentType: false,
beforeSend: () => Swal.showLoading(),
success: function(res) {
Swal.close();
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Success', 'File uploaded successfully.', 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Error', json.message, 'error');
}
},
error: function() {
Swal.fire('Error', 'Upload failed.', 'error');
}
});
});
}
handleUpload('#uploadMappingForm');
handleUpload('#uploadPriceForm');
// Delete
$('#clientFilesTable').on('click', '.delete-file', function() {
const id = $(this).data('id');
Swal.fire({
title: 'Delete file?',
text: 'This will remove the file record.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it'
}).then(result => {
if (result.isConfirmed) {
$.post('client_files_delete.php', {
id
}, res => {
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Deleted', 'File removed.', 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Error', json.message, 'error');
}
});
}
});
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,33 @@
<?php
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
header('Content-Type: application/json');
$id = intval($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'Invalid file ID']);
exit;
}
try {
// Recupera il nome del file
$stmt = $pdo->prepare("SELECT filename FROM client_files WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row && !empty($row['filename'])) {
$filePath = __DIR__ . '/../uploads/client_files/' . $row['filename'];
if (file_exists($filePath)) {
unlink($filePath); // elimina file fisico
}
}
// Cancella riga dal DB
$pdo->prepare("DELETE FROM client_files WHERE id = ?")->execute([$id]);
echo json_encode(['status' => 'ok']);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -0,0 +1,36 @@
<?php
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
header('Content-Type: application/json');
try {
$idclient = intval($_GET['idclient'] ?? 0);
$query = "
SELECT
cf.id,
c.client_name,
cf.file_type,
cf.filename,
cf.source_id,
cf.upload_date,
cf.notes
FROM client_files cf
LEFT JOIN clients c ON c.idclient = cf.idclient
" . ($idclient > 0 ? "WHERE cf.idclient = :idclient" : "") . "
ORDER BY cf.upload_date DESC
";
$stmt = $pdo->prepare($query);
if ($idclient > 0) {
$stmt->bindValue(':idclient', $idclient, PDO::PARAM_INT);
}
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($data);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -0,0 +1,98 @@
<?php
require_once(__DIR__ . '/class/db-functions.php');
use Dotenv\Dotenv;
header('Content-Type: application/json');
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new Exception("Invalid request method.");
}
$idclient = intval($_POST['idclient'] ?? 0);
$file_type = $_POST['file_type'] ?? '';
$uploaded_by = $_POST['uploaded_by'] ?? null; // opzionale
$notes = $_POST['notes'] ?? null;
if ($idclient <= 0 || empty($file_type) || !isset($_FILES['file'])) {
throw new Exception("Missing required parameters.");
}
// Cartella di upload
$uploadDir = __DIR__ . '/../uploads/client_files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$file = $_FILES['file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new Exception("File upload error.");
}
$filename = time() . '_' . preg_replace('/[^A-Za-z0-9_.-]/', '_', basename($file['name']));
$targetPath = $uploadDir . $filename;
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
throw new Exception("Failed to move uploaded file.");
}
// === 1⃣ Inserisci nel DB senza source_id inizialmente ===
$stmt = $pdo->prepare("
INSERT INTO client_files (idclient, file_type, filename, uploaded_by, notes)
VALUES (:idclient, :file_type, :filename, :uploaded_by, :notes)
");
$stmt->execute([
':idclient' => $idclient,
':file_type' => $file_type,
':filename' => $filename,
':uploaded_by' => $uploaded_by,
':notes' => $notes
]);
$insertId = $pdo->lastInsertId();
// === 2⃣ Upload verso ChatPDF ===
$dotenv = Dotenv::createImmutable(dirname(__DIR__, 2));
$dotenv->load();
$apiKey = $_ENV['CHATPDF_API_KEY'] ?? null;
if (!$apiKey) {
throw new Exception("ChatPDF API key not found in .env");
}
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.chatpdf.com/v1/sources/add-file",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["x-api-key: $apiKey"],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
"file" => new CURLFile($targetPath)
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) throw new Exception("ChatPDF API error: $err");
$json = json_decode($response, true);
$sourceId = $json['sourceId'] ?? null;
// === 3⃣ Se ChatPDF restituisce un source_id, aggiorniamo il DB ===
if ($sourceId) {
$upd = $pdo->prepare("UPDATE client_files SET source_id = :source_id WHERE id = :id");
$upd->execute([':source_id' => $sourceId, ':id' => $insertId]);
$msg = "File uploaded and ChatPDF registered (sourceId: $sourceId)";
} else {
$msg = "File uploaded, but ChatPDF did not return a source ID. Response: " . json_encode($json);
}
echo json_encode(['status' => 'ok', 'message' => $msg, 'source_id' => $sourceId]);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -19,4 +19,87 @@
<!-- Font awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
/* --- Allineamento contenuto rispetto alla sidebar --- */
.dashboard-main-wrapper {
display: flex;
flex-direction: row;
}
/* Sidebar fissa a sinistra */
.sidebar-wrapper {
width: 260px;
/* o la larghezza effettiva della tua sidebar */
position: fixed;
left: 0;
top: 0;
height: 100%;
z-index: 1000;
}
/* Contenuto principale spostato a destra della sidebar */
.dashboard-body {
margin-left: 260px;
/* deve corrispondere alla larghezza sidebar */
padding: 20px 30px;
width: calc(100% - 260px);
background-color: #f9fafb;
}
/* Se vuoi includere anche la topbar fissa */
.dashboard-body {
margin-top: 70px;
/* altezza della topbar */
}
/* --- Comportamento responsive --- */
@media (max-width: 992px) {
.sidebar-wrapper {
position: absolute;
left: -260px;
transition: all 0.3s ease;
}
.sidebar-wrapper.active {
left: 0;
}
.dashboard-body {
margin-left: 0;
width: 100%;
}
}
</style>
<style>
/* === Sidebar collassata === */
.wrapper.toggled .sidebar-wrapper {
width: 70px !important;
transition: all 0.3s ease;
overflow: hidden;
}
/* Nasconde il testo */
.wrapper.toggled .logo-text,
.wrapper.toggled .menu-title,
.wrapper.toggled .menu-label {
display: none !important;
}
/* Riduce padding e centra le icone */
.wrapper.toggled .metismenu a {
justify-content: center !important;
padding: 10px 0 !important;
}
.wrapper.toggled .metismenu i {
margin-right: 0 !important;
}
/* Sposta il contenuto principale */
.wrapper.toggled .dashboard-body {
margin-left: 70px !important;
width: calc(100% - 70px) !important;
transition: all 0.3s ease;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -1,29 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$partId = $data['part_id'] ?? null;
if (!$partId) {
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE id = :part_id");
$stmt->execute([':part_id' => $partId]);
$rowCount = $stmt->rowCount();
if ($rowCount > 0) {
echo json_encode(['success' => true, 'message' => 'Parte eliminata con successo']);
} else {
echo json_encode(['success' => false, 'message' => 'Nessuna parte trovata con ID ' . $partId]);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nell\'eliminazione: ' . $e->getMessage()]);
}

View File

@ -1,28 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$partId = $data['part_id'] ?? null;
if (!$partId) {
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE id = :part_id");
$stmt->execute([':part_id' => $partId]);
$rowCount = $stmt->rowCount();
if ($rowCount > 0) {
echo json_encode(['success' => true, 'message' => 'Parte eliminata con successo']);
} else {
echo json_encode(['success' => false, 'message' => 'Nessuna parte trovata con ID ' . $partId]);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nell\'eliminazione: ' . $e->getMessage()]);
}

View File

@ -1,37 +0,0 @@
<?php
// delete_photo.php
include('include/headscript.php');
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['photo_id'])) {
echo json_encode(['success' => false, 'message' => 'Richiesta non valida']);
exit;
}
$photoId = intval($_POST['photo_id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Recupera il percorso del file
$stmt = $pdo->prepare("SELECT file_path FROM datadb_photos WHERE id = ?");
$stmt->execute([$photoId]);
$photo = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$photo) {
echo json_encode(['success' => false, 'message' => 'Foto non trovata']);
exit;
}
// Elimina il file dal server
$filePath = '../photostrf/' . $photo['file_path'];
if (file_exists($filePath)) {
unlink($filePath);
}
// Elimina il record dal database
$stmt = $pdo->prepare("DELETE FROM datadb_photos WHERE id = ?");
$stmt->execute([$photoId]);
echo json_encode(['success' => true, 'message' => 'Foto eliminata con successo']);

View File

@ -1,64 +0,0 @@
<?php
// Enable errors for debugging
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/delete_record_debug.log');
// Log iniziale
error_log("Inizio cancellazione record alle " . date('Y-m-d H:i:s'));
// Includi il file di configurazione del database
include('include/headscript.php');
// Ricevi l'ID dalla richiesta POST
$input = json_decode(file_get_contents('php://input'), true);
$iddatadb = isset($input['id']) ? intval($input['id']) : 0;
if ($iddatadb <= 0) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'ID non valido']);
exit;
}
// Connessione al database
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Errore di connessione al database: ' . $e->getMessage()]);
error_log("Errore di connessione al database: " . $e->getMessage());
exit;
}
// Inizia una transazione
$pdo->beginTransaction();
try {
// Elimina i dettagli associati dal tavolo import_data_details
$stmt = $pdo->prepare("DELETE FROM import_data_details WHERE id = ?");
$stmt->execute([$iddatadb]);
// Elimina il record principale dal tavolo datadb
$stmt = $pdo->prepare("DELETE FROM datadb WHERE iddatadb = ?");
$stmt->execute([$iddatadb]);
// Verifica se il record è stato eliminato
if ($stmt->rowCount() > 0) {
$pdo->commit();
echo json_encode(['success' => true, 'message' => 'Record eliminato con successo']);
error_log("Record con iddatadb=$iddatadb eliminato con successo");
} else {
$pdo->rollBack();
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Record non trovato']);
error_log("Record con iddatadb=$iddatadb non trovato");
}
} catch (Exception $e) {
$pdo->rollBack();
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Errore durante la cancellazione: ' . $e->getMessage()]);
error_log("Errore durante la cancellazione del record con iddatadb=$iddatadb: " . $e->getMessage());
}

View File

@ -1,49 +0,0 @@
<?php
// Abilita la gestione degli errori
ini_set('display_errors', 1);
error_reporting(E_ALL);
// Includi la connessione al database
require_once 'class/db-functions.php';
try {
// Controlla se è stato passato un ID valido
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
throw new Exception('Invalid ID');
}
$id = intval($_GET['id']); // Sanifica l'ID
// Connessione al database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if (!$pdo) {
throw new Exception('Database connection failed');
}
// Verifica se l'ID esiste
$stmtCheck = $pdo->prepare("SELECT id FROM excel_templates WHERE id = ?");
$stmtCheck->execute([$id]);
if ($stmtCheck->rowCount() === 0) {
throw new Exception('Template not found');
}
// Esegui l'eliminazione
$stmt = $pdo->prepare("DELETE FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
$message = "Template deleted successfully!";
$status = "success";
} else {
throw new Exception('Failed to delete template');
}
} catch (Exception $e) {
$message = $e->getMessage();
$status = "error";
}
// Reindirizza con un messaggio
header("Location: templates_dashboard.php?status=$status&message=" . urlencode($message));
exit;

View File

@ -1,434 +0,0 @@
<?php include('include/headscript.php');
// Controlla se è stato passato un ID valido
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']); // Sanifica l'ID
// Recupera il template dal database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit;
}
// Recupera tutte le routine dal database
$stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<?php include('cssinclude.php'); ?>
<!-- Include jQuery prima di Select2 -->
<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>
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Update XLS Template</h5>
</div>
<div class="card-body">
<p class="mb-2">Edit the following form in order to update the selected import XLS template</p>
<p class="mb-2">Mandatory Fields</p>
<ul class="mb-0">
<li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li>
<li>Schema</li>
</ul>
</div>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Edit Template: <?php echo htmlspecialchars($template['name']); ?></h6>
</div>
</div>
</div>
<div class="card-body">
<div class="col-12">
<form id="editTemplateForm" method="POST">
<input type="hidden" name="id" value="<?php echo $template['id']; ?>">
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" value="<?php echo htmlspecialchars($template['name']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" class="form-control" value="<?php echo $template['header_row']; ?>" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?>*</label>
<input type="text" name="start_column" class="form-control" value="<?php echo htmlspecialchars($template['start_column']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description']); ?></textarea>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?>*</label>
<input type="text" name="target_table" class="form-control" value="<?php echo htmlspecialchars($template['target_table']); ?>" readonly required>
</div>
<div class="mb-3">
<label class="form-label">Button Size</label>
<select name="button_size" class="form-control">
<option value="small" <?php echo ($template['button_size'] ?? 'medium') === 'small' ? 'selected' : ''; ?>>Small</option>
<option value="medium" <?php echo ($template['button_size'] ?? 'medium') === 'medium' ? 'selected' : ''; ?>>Medium</option>
<option value="large" <?php echo ($template['button_size'] ?? 'medium') === 'large' ? 'selected' : ''; ?>>Large</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
</div>
<div class="mb-3">
<label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
</div>
<div class="mb-3">
<label class="form-label">Button Label</label>
<input type="text" name="button_label" class="form-control" value="<?php echo htmlspecialchars($template['button_label'] ?? 'Click Me'); ?>">
</div>
<div class="mb-3">
<label class="form-label">Select Client *</label>
<select name="client_id" id="clientSelect" class="form-control" required>
<option value="">Select a client...</option>
</select>
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
</div>
<div class="mb-3">
<label class="form-label">Select Schema *</label>
<select name="schema_id" id="schemaSelect" class="form-control" required>
<option value="">Select a schema...</option>
</select>
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Caricamento schemi in corso...</span>
</div>
<div class="mb-3">
<label class="form-label">Select Routine</label>
<select name="idroutine" id="routineSelect" class="form-control">
<option value="">Select a routine...</option>
<?php foreach ($routines as $routine): ?>
<option value="<?php echo $routine['idroutine']; ?>" <?php echo ($template['idroutine'] ?? '') == $routine['idroutine'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($routine['name']); ?>
</option>
<?php endforeach; ?>
</select>
<div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6>
<p><strong>Name:</strong> <span id="routineName"></span></p>
<p><strong>Description:</strong> <span id="routineDescription"></span></p>
<p><strong>Action 1:</strong> <span id="routineAction1"></span></p>
<p><strong>Action 2:</strong> <span id="routineAction2"></span></p>
<p><strong>Action 3:</strong> <span id="routineAction3"></span></p>
</div>
</div>
<br>
<button type="submit" class="btn btn-primary"><?= htmlspecialchars($savechanges, ENT_QUOTES, 'UTF-8'); ?></button>
<a href="templates_dashboard.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Verifica che jQuery sia caricato
if (typeof jQuery === 'undefined') {
alert("Errore: jQuery non è caricato. Contatta l'amministratore.");
return;
}
const form = document.getElementById("editTemplateForm");
const clientLoadingStatus = document.getElementById("clientLoadingStatus");
const schemaLoadingStatus = document.getElementById("schemaLoadingStatus");
const routineSelect = document.getElementById("routineSelect");
const routineDetails = document.getElementById("routineDetails");
const routineName = document.getElementById("routineName");
const routineDescription = document.getElementById("routineDescription");
const routineAction1 = document.getElementById("routineAction1");
const routineAction2 = document.getElementById("routineAction2");
const routineAction3 = document.getElementById("routineAction3");
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return;
}
// Inizializza Select2
$('#clientSelect').select2({
placeholder: "Search for a client...",
allowClear: true
});
$('#schemaSelect').select2({
placeholder: "Search for a schema...",
allowClear: true
});
$('#routineSelect').select2({
placeholder: "Select a routine...",
allowClear: true
});
// Carica i clienti
async function loadClients() {
try {
clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
const response = await fetch("get_clienti.php", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
const select = document.getElementById("clientSelect");
select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile";
const id = client.IdCliente || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
if (parseInt(id) === parseInt(<?php echo json_encode($template['idclient'] ?? 0); ?>)) {
option.selected = true;
}
select.add(option);
});
$(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati.";
} catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare i clienti: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
}
}
// Carica gli schemi
async function loadSchemas() {
try {
schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
const response = await fetch("get_schemi.php", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
const select = document.getElementById("schemaSelect");
select.innerHTML = '<option value="">Select a schema...</option>';
data.value.forEach(schema => {
const nome = schema.Nome || "Nome non disponibile";
const id = schema.IdSchemaCustomFields || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
if (parseInt(id) === parseInt(<?php echo json_encode($template['idschema'] ?? 0); ?>)) {
option.selected = true;
}
select.add(option);
});
$(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati.";
} catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare gli schemi: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000);
}
}
// Carica i dati
async function loadData() {
try {
await loadClients();
await loadSchemas();
} catch (error) {
Swal.fire({
title: "Errore!",
text: "Errore nel caricamento dei dati: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
}
}
loadData();
// Routine dettagli
const routines = <?php echo json_encode($routines); ?>;
function updateRoutineDetails() {
const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId);
if (routine) {
routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A';
routineAction1.textContent = routine.action1 || 'N/A';
routineAction2.textContent = routine.action2 || 'N/A';
routineAction3.textContent = routine.action3 || 'N/A';
} else {
routineName.textContent = 'N/A';
routineDescription.textContent = 'N/A';
routineAction1.textContent = 'N/A';
routineAction2.textContent = 'N/A';
routineAction3.textContent = 'N/A';
}
} else {
routineName.textContent = '';
routineDescription.textContent = '';
routineAction1.textContent = '';
routineAction2.textContent = '';
routineAction3.textContent = '';
}
}
routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails(); // Inizializza dettagli se una routine è preselezionata
// Submit del form
form.addEventListener("submit", function(e) {
e.preventDefault();
let formData = new FormData(this);
const clientSelect = document.getElementById("clientSelect");
const clientId = clientSelect.value;
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
if (!clientId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona un cliente.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
let clientName = "";
if (selectedClientOption) {
const optionText = selectedClientOption.text.trim();
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
clientName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("client_name", clientName);
const schemaSelect = document.getElementById("schemaSelect");
const schemaId = schemaSelect.value;
const selectedSchemaOption = schemaSelect.options[schemaSelect.selectedIndex];
if (!schemaId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona uno schema.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
let schemaName = "";
if (selectedSchemaOption) {
const optionText = selectedSchemaOption.text.trim();
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("idschema", schemaId);
formData.append("schemaname", schemaName);
// Aggiungi idroutine
const routineId = routineSelect.value;
formData.append("idroutine", routineId);
fetch("process_edit_template_xls.php", {
method: "POST",
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
title: "Successo!",
text: "Template aggiornato con successo!",
icon: "success",
confirmButtonText: "OK"
}).then(() => {
window.location.href = "templates_dashboard.php";
});
} else {
Swal.fire({
title: "Errore!",
text: data.message,
icon: "error",
confirmButtonText: "OK"
});
}
})
.catch(error => {
Swal.fire({
title: "Errore!",
text: "Si è verificato un errore imprevisto.",
icon: "error",
confirmButtonText: "OK"
});
});
});
});
</script>
</body>
</html>

View File

@ -1,239 +0,0 @@
document.addEventListener("DOMContentLoaded", () => {
console.log("export_to_lims.js loaded");
// Debug: verifica che i pulsanti siano trovati
const exportButtons = document.querySelectorAll(".export-lims-btn");
console.log(`Found ${exportButtons.length} export-lims-btn buttons`);
if (exportButtons.length === 0) {
console.warn("No .export-lims-btn buttons found in the DOM");
return;
}
exportButtons.forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
const rowIndex = btn.dataset.row;
const iddatadb = btn.dataset.iddatadb;
console.log(
`Export to LIMS clicked for row ${rowIndex}, iddatadb: ${iddatadb}`,
);
// Mostra il modale di conferma
const confirmModalElement =
document.getElementById("exportConfirmModal");
if (!confirmModalElement) {
console.error("exportConfirmModal not found in the DOM");
alert("Errore: Modale di conferma non trovato");
return;
}
const confirmModal = new bootstrap.Modal(confirmModalElement, {
keyboard: false,
});
document.getElementById("exportIddatadb").textContent = iddatadb;
confirmModal.show();
// Gestisci il click su "Conferma"
const confirmBtn = document.getElementById("exportConfirmBtn");
if (!confirmBtn) {
console.error("exportConfirmBtn not found in the DOM");
confirmModal.hide();
alert("Errore: Pulsante di conferma non trovato");
return;
}
const confirmHandler = async () => {
console.log(`Confirmed export for iddatadb: ${iddatadb}`);
confirmModal.hide();
const formData = new FormData();
formData.append("iddatadb", iddatadb);
try {
const response = await fetch("export_to_lims.php", {
method: "POST",
body: formData,
});
if (!response.ok)
throw new Error(
`HTTP error! status: ${response.status}`,
);
const data = await response.json();
console.log("Export response:", data);
// Mostra il modale di risposta
const responseModalElement = document.getElementById(
"exportResponseModal",
);
if (!responseModalElement) {
console.error(
"exportResponseModal not found in the DOM",
);
alert("Errore: Modale di risposta non trovato");
return;
}
const responseModal = new bootstrap.Modal(
responseModalElement,
{
keyboard: false,
},
);
const responseMessage = document.getElementById(
"exportResponseMessage",
);
if (data.success) {
responseMessage.innerHTML = `${data.message.replace(/\n/g, "<br>")}<br>ID CommessaWeb: ${data.idcommessaweb}`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Esportazione Completata";
responseModal.show();
// Aggiorna la UI per riflettere lo stato 'To LIMS'
const statusCell = btn
.closest(".grid-row")
.querySelector(
'.grid-cell[data-col="status"] .status-badge',
);
if (statusCell) {
statusCell.classList.remove("status-i", "status-P");
statusCell.classList.add("status-l");
statusCell.textContent = "To LIMS";
}
// Gestisci la chiusura del modale di risposta
responseModalElement.addEventListener(
"hidden.bs.modal",
() => {
console.log(
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log(
"Removing backdrop:",
backdrop,
);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
}
},
{ once: true },
);
} else {
responseMessage.textContent = `Errore durante la generazione dei payload: ${data.message}`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Errore Esportazione";
responseModal.show();
// Gestisci la chiusura del modale di risposta anche in caso di errore
responseModalElement.addEventListener(
"hidden.bs.modal",
() => {
console.log(
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log(
"Removing backdrop:",
backdrop,
);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
}
},
{ once: true },
);
}
} catch (error) {
console.error("Export error:", error);
const responseModalElement = document.getElementById(
"exportResponseModal",
);
if (!responseModalElement) {
console.error(
"exportResponseModal not found in the DOM",
);
alert("Errore: Modale di risposta non trovato");
return;
}
const responseModal = new bootstrap.Modal(
responseModalElement,
{
keyboard: false,
},
);
document.getElementById(
"exportResponseMessage",
).textContent =
`Errore durante la generazione dei payload: ${error.message}`;
document.getElementById(
"exportResponseModalLabel",
).textContent = "Errore Esportazione";
responseModal.show();
// Gestisci la chiusura del modale di risposta in caso di errore
responseModalElement.addEventListener(
"hidden.bs.modal",
() => {
console.log(
"exportResponseModal closed, cleaning up",
);
// Rimuovi tutti i backdrop residui
document
.querySelectorAll(".modal-backdrop")
.forEach((backdrop) => {
console.log("Removing backdrop:", backdrop);
backdrop.remove();
});
// Ripristina il body
document.body.classList.remove("modal-open");
document.body.style.paddingRight = "";
// Nascondi l'overlay
const overlay = document.querySelector(
".overlay.toggle-icon",
);
if (overlay) {
overlay.style.display = "none";
}
},
{ once: true },
);
}
// Rimuovi il listener dopo l'esecuzione
confirmBtn.removeEventListener("click", confirmHandler);
};
// Rimuovi eventuali listener precedenti
confirmBtn.removeEventListener("click", confirmHandler);
confirmBtn.addEventListener("click", confirmHandler);
});
});
});

View File

@ -1,291 +0,0 @@
<?php
require_once "class/VisualLimsApiClient.class.php";
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
header("Content-Type: application/json");
// 🔹 Configura directory log (creala se non esiste)
$logDir = __DIR__ . '/logs/api/';
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// 🔹 Base URL API
$apiBaseUrl = 'https://93.43.5.102/limsapi/api/odata/';
// 🔹 Funzione per validare e convertire date
function validateDate($value)
{
// Prova a validare come data (accetta formati comuni)
$date = DateTime::createFromFormat('Y-m-d', $value) ?: DateTime::createFromFormat('Y-m-d H:i:s', $value);
if ($date) {
return $date->format('Y-m-d\TH:i:sP'); // Formato ISO 8601
}
return null; // Imposta null se non è una data valida
}
try {
$iddatadb = $_POST['iddatadb'] ?? null;
if (!$iddatadb) {
throw new Exception("Missing iddatadb");
}
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
$stmt = $pdo->prepare("
SELECT d.idclient AS clienteId, et.idschema AS schemaId
FROM datadb as d
INNER JOIN excel_templates as et ON d.templateid = et.id
WHERE d.iddatadb = :iddatadb
LIMIT 1
");
$stmt->execute(['iddatadb' => $iddatadb]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result) {
throw new Exception("No Cliente/Schema found for iddatadb {$iddatadb}");
}
$clienteId = (int) $result['clienteId'];
$schemaId = (int) $result['schemaId'];
// 🔹 STEP 3: Fetch Parts (including idmatrice)
$stmt = $pdo->prepare("
SELECT part_number, part_description, material, color, mix, idmatrice
FROM identification_parts
WHERE iddatadb = :iddatadb
");
$stmt->execute(['iddatadb' => $iddatadb]);
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 🔹 STEP 4: Fetch Field Values with Labels
$stmt = $pdo->prepare("
SELECT
idd.field_value,
m.field_label,
m.schema_id,
m.field_id
FROM
import_data_details as idd
JOIN template_mapping m ON idd.mapping_id = m.id
WHERE idd.id = :iddatadb
");
$stmt->execute(['iddatadb' => $iddatadb]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$fieldValues = [];
$valueMap = [];
foreach ($rows as $row) {
$fieldValues[] = [
"IdCommesseCustomFields" => (int) $row['field_id'],
"Valore" => $row['field_value'],
"FieldLabel" => $row['field_label']
];
$valueMap[(int) $row['field_id']] = $row['field_value'];
}
// Logga i fieldValues in error_log
$logFieldValues = "FieldValues dal DB (iddatadb={$iddatadb}):\n" . json_encode($fieldValues, JSON_PRETTY_PRINT);
error_log($logFieldValues);
// 🔹 Initialize API client
$api = VisualLimsApiClient::getInstance();
// 🔹 STEP 5: Create CommessaWeb (NOT WebOrder)
$commessaWebPayload = [
"Cliente" => $clienteId,
"SchemaCustomField" => $schemaId,
"Richiedente" => "Test Web Import",
"Descrizione" => "TEST CommessaWeb",
];
// Costruisci log curl-like per STEP 5
$jsonPayload = json_encode($commessaWebPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep5 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'";
$commessaWeb = $api->post("CommessaWeb", $commessaWebPayload);
$logContentStep5 .= "\n\nRESPONSE:\n" . json_encode($commessaWeb, JSON_PRETTY_PRINT);
// Salva log
$logFileStep5 = $logDir . "commessa_create_step5_" . $iddatadb . "_" . time() . ".txt";
file_put_contents($logFileStep5, $logContentStep5);
$commessaId = $commessaWeb["IdCommessa"];
$commessaWebCode = substr($commessaWeb["CodiceCommessa"] ?? "TEST CommessaWeb", 0, 30); // Limite a 30 caratteri
// 🔹 STEP 6: Create Campioni (Samples) for each part
$campioni = [];
$logContentStep6 = "";
foreach ($parts as $index => $part) {
$matriceId = (int) ($part["idmatrice"] ?? 0);
if ($matriceId <= 0) {
throw new Exception("Invalid or missing idmatrice for part: " . ($part["part_number"] ?? "Unknown"));
}
$campionePayload = [
"Commessa" => $commessaId,
"Matrice" => $matriceId,
"SottoMatrice" => null,
"SchemaCustomField" => $schemaId,
"NoteWeb" => $part["part_description"] ?? ""
];
// Costruisci curl-like per questo campione
$jsonPayload = json_encode($campionePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep6 .= "CAMPIONE #{$index}\n" .
"curl --location --request POST '{$apiBaseUrl}Campione' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'\n\n";
$campione = $api->post("Campione", $campionePayload);
$logContentStep6 .= "RESPONSE:\n" . json_encode($campione, JSON_PRETTY_PRINT) . "\n\n---\n";
$campione["PartNumber"] = $part["part_number"] ?? "";
$campione["Material"] = $part["material"] ?? "";
$campione["Color"] = $part["color"] ?? "";
$campione["Mix"] = $part["mix"] ?? "";
$campioni[] = $campione;
}
// Salva log per STEP 6
$logFileStep6 = $logDir . "commessa_{$commessaId}_campioni_step6_" . time() . ".txt";
file_put_contents($logFileStep6, $logContentStep6);
// 🔹 STEP 7: Update Custom Fields for CommessaWeb
if (!empty($fieldValues)) {
// GET con espansione per CustomField
$expand = "CommesseCustomFields(\$expand=CustomField)";
$commessaWithFields = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
// Logga il GET
$logContentGet = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaWithFields, JSON_PRETTY_PRINT);
$logFileGet = $logDir . "commessa_{$commessaId}_get_step7_" . time() . ".txt";
file_put_contents($logFileGet, $logContentGet);
// Prepara payload PATCH
$commessaCustomFields = [];
foreach ($commessaWithFields["CommesseCustomFields"] as $customField) {
$definitionId = (int) ($customField["CustomField"]["IdCustomField"] ?? 0);
$fieldId = (int) $customField["IdCommesseCustomFields"];
$currentValue = $customField["Valore"] ?? '';
$fieldType = $customField["CustomField"]["Tipo"] ?? '';
$newValue = isset($valueMap[$definitionId]) ? $valueMap[$definitionId] : $currentValue;
// Valida se il campo è di tipo Data
if ($fieldType === 'Data' && $newValue !== $currentValue) {
$newValue = validateDate($newValue);
}
$commessaCustomFields[] = [
"IdCommesseCustomFields" => $fieldId,
"Valore" => $newValue
];
}
if (!empty($commessaCustomFields)) {
$updatePayload = ["CommesseCustomFields" => $commessaCustomFields];
// Logga payload e response
$jsonPayload = json_encode($updatePayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$logContentStep7 = "curl --location --request PATCH '{$apiBaseUrl}CommessaWeb({$commessaId})' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{$jsonPayload}'";
$patchResponse = $api->patch("CommessaWeb({$commessaId})", $updatePayload);
$logContentStep7 .= "\n\nRESPONSE:\n" . json_encode($patchResponse, JSON_PRETTY_PRINT);
$logFileStep7 = $logDir . "commessa_{$commessaId}_update_step7_" . time() . ".txt";
file_put_contents($logFileStep7, $logContentStep7);
}
}
// 🔹 STEP 8: Update datadb with idcommessaweb, commessaweb, and status
$stmt = $pdo->prepare("
UPDATE datadb
SET idcommessaweb = :idcommessaweb, commessaweb = :commessaweb, status = 'l'
WHERE iddatadb = :iddatadb
");
$stmt->execute([
'idcommessaweb' => $commessaId,
'commessaweb' => $commessaWebCode,
'iddatadb' => $iddatadb
]);
// 🔹 STEP 9: Send CommessaWeb to laboratory (commentato come richiesto)
/*
$sendResult = $api->post("CommessaWeb({$commessaId})/InviaCommessa", []);
// Logga il POST
$logContentStep9 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/InviaCommessa' \\\n" .
"--header 'Content-Type: application/json' \\\n" .
"--header 'Authorization: Bearer ••••••' \\\n" .
"--data '{}'\n\n" .
"RESPONSE:\n" . json_encode($sendResult, JSON_PRETTY_PRINT);
$logFileStep9 = $logDir . "commessa_{$commessaId}_send_step9_" . time() . ".txt";
file_put_contents($logFileStep9, $logContentStep9);
*/
// 🔹 STEP 10: GET di controllo post-PATCH
$expand = "CommesseCustomFields(\$expand=CustomField)";
$commessaAfterPatch = $api->get("CommessaWeb(" . $commessaId . ")?\$expand=" . $expand);
// Logga il GET di controllo
$logContentStep10 = "curl --location --request GET '{$apiBaseUrl}CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)' \\\n" .
"--header 'Authorization: Bearer ••••••'\n\n" .
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
file_put_contents($logFileStep10, $logContentStep10);
// 🔹 STEP 11: Prepare final response
$finalCommessa = [
"Cliente" => $clienteId,
"SchemaCustomField" => $schemaId,
"Richiedente" => $commessaWeb["Richiedente"] ?? "Web Import",
"Descrizione" => $commessaWeb["Descrizione"] ?? "",
"CommesseCustomFields" => $commessaAfterPatch["CommesseCustomFields"] ?? [],
"Campioni" => $campioni,
"Inviata" => 0 // Non inviato, come richiesto
];
echo json_encode([
"success" => true,
"commessaWeb" => $finalCommessa,
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
"totalCampioni" => count($campioni),
"totalCustomFields" => count($commessaAfterPatch["CommesseCustomFields"] ?? []),
"message" => "Export successful",
"logFiles" => [
"step5_create" => $logFileStep5,
"step6_campioni" => $logFileStep6,
"step7_patch" => $logFileStep7,
"step10_get" => $logFileStep10
]
]);
} catch (Exception $e) {
error_log("LIMS Export Error: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
echo json_encode([
"success" => false,
"message" => "Export failed: " . $e->getMessage(),
"logFiles" => [
"step5_create" => $logFileStep5 ?? null,
"step6_campioni" => $logFileStep6 ?? null,
"step7_patch" => $logFileStep7 ?? null,
"step10_get" => $logFileStep10 ?? null
]
]);
}

View File

@ -1,150 +0,0 @@
<?php
// Abilita il debug degli errori (solo per sviluppo)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Assicurati che non ci sia output prima del JSON
ob_start();
// Imposta l'header per JSON
header('Content-Type: application/json');
// Configura la tua chiave API di TrackingMore
$apiKey = 'u4ssgynn-xuyy-9act-ca29-2glsv2qlzh0w'; // La tua chiave API reale
// Funzione per inviare una risposta JSON e terminare l'esecuzione
function sendResponse($data)
{
ob_end_clean();
echo json_encode($data);
exit;
}
// Verifica che il numero di tracking e il corriere siano stati inviati
if (!isset($_POST['tracking_number']) || empty($_POST['tracking_number']) || !isset($_POST['courier_code']) || empty($_POST['courier_code'])) {
sendResponse(['success' => false, 'message' => 'Numero di tracking o corriere non fornito']);
}
$trackingNumber = $_POST['tracking_number'];
$courierCode = $_POST['courier_code'];
// Lista dei corrieri validi per validazione
$validCarriers = ['tnt-it', 'dhl', 'gls', 'sda', 'ups'];
if (!in_array($courierCode, $validCarriers)) {
sendResponse(['success' => false, 'message' => 'Corriere non valido']);
}
// Imposta il nome del corriere in base al codice
$carrierNames = [
'tnt-it' => 'TNT Italy',
'dhl' => 'DHL',
'gls' => 'GLS',
'sda' => 'SDA',
'ups' => 'UPS'
];
$courierName = $carrierNames[$courierCode];
// Funzione per fare una richiesta cURL a TrackingMore
function makeRequest($url, $data, $apiKey, $method = 'POST')
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Tracking-Api-Key: ' . $apiKey,
'Content-Type: application/json'
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
$jsonData = json_encode($data, JSON_PRETTY_PRINT);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
file_put_contents('debug.log', "Encoded JSON Data: $jsonData\n", FILE_APPEND);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
file_put_contents('debug.log', "Request URL: $url\nRequest Data: " . json_encode($data) . "\nResponse: $response\nHTTP Code: $httpCode\nError: $error\n", FILE_APPEND);
if ($response === false) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Errore nella richiesta API: ' . $error
];
}
$decodedResponse = json_decode($response, true);
if ($decodedResponse === null) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Risposta API non valida (non è JSON)'
];
}
$decodedResponse['success'] = isset($decodedResponse['meta']['code']) && ($decodedResponse['meta']['code'] === 200 || $decodedResponse['meta']['code'] === 201);
$decodedResponse['http_code'] = $httpCode;
return $decodedResponse;
}
// Step 1: Prova a creare il tracking
$createUrl = 'https://api.trackingmore.com/v4/trackings/create';
$createData = [
'tracking_number' => $trackingNumber,
'courier_code' => $courierCode
];
$createResponse = makeRequest($createUrl, $createData, $apiKey);
file_put_contents('debug.log', "Create Response: " . json_encode($createResponse) . "\n", FILE_APPEND);
$trackingInfo = null;
if ($createResponse['success']) {
// Creazione riuscita, usa i dati restituiti
$trackingInfo = $createResponse['data'];
} else {
// Controlla se l'errore è "Tracking No. already exists" (4101)
if (isset($createResponse['meta']['code']) && $createResponse['meta']['code'] === 4101) {
// Il tracking esiste già, usa /trackings (GET) con tracking_number e courier_code
$getUrl = 'https://api.trackingmore.com/v4/get?tracking_numbers=' . urlencode($trackingNumber);
$getResponse = makeRequest($getUrl, [], $apiKey, 'GET');
file_put_contents('debug.log', "Get Response: " . json_encode($getResponse) . "\n", FILE_APPEND);
if ($getResponse['success'] && !empty($getResponse['data']['items']) && !empty($getResponse['data']['items'][0])) {
$trackingInfo = $getResponse['data']['items'][0];
} else {
$errorMessage = isset($getResponse['meta']['message']) ? $getResponse['meta']['message'] : 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nel recupero del tracking esistente: ' . $errorMessage]);
}
} else {
// Altro errore nella creazione
$errorMessage = isset($createResponse['meta']['message']) ? $createResponse['meta']['message'] : 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nella creazione del tracking: ' . $errorMessage]);
}
}
if (!$trackingInfo) {
sendResponse(['success' => false, 'message' => 'Nessuna informazione di tracking trovata']);
}
// Estrai la data di consegna e il firmatario
$deliveryDate = 'Non disponibile';
$signedBy = 'Non disponibile';
if (isset($trackingInfo['origin_info']['trackinfo']) && is_array($trackingInfo['origin_info']['trackinfo'])) {
foreach ($trackingInfo['origin_info']['trackinfo'] as $checkpoint) {
if (isset($checkpoint['checkpoint_delivery_status']) && $checkpoint['checkpoint_delivery_status'] === 'delivered') {
$deliveryDate = $checkpoint['checkpoint_date'] ?? 'Non disponibile';
$signedBy = $trackingInfo['signed_by'] ?? 'Non disponibile';
break;
}
}
}
// Restituisci i dati al frontend
sendResponse([
'success' => true,
'deliveryDate' => $deliveryDate,
'signedBy' => $signedBy,
'carrierName' => $courierName
]);

View File

@ -1,166 +0,0 @@
<?php
// Abilita il debug degli errori (solo per sviluppo)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Assicurati che non ci sia output prima del JSON
ob_start();
// Imposta l'header per JSON
header('Content-Type: application/json');
// Configura la tua chiave API di 17Track
$apiKey = '489F6B6DADDE09A5B6CB1C42B5363A3F'; // Sostituisci con la tua chiave API reale
// Funzione per inviare una risposta JSON e terminare l'esecuzione
function sendResponse($data)
{
ob_end_clean();
echo json_encode($data);
exit;
}
// Verifica che il numero di tracking sia stato inviato
if (!isset($_POST['tracking_number']) || empty($_POST['tracking_number'])) {
sendResponse(['success' => false, 'message' => 'Numero di tracking non fornito']);
}
$trackingNumber = $_POST['tracking_number'];
// Funzione per fare una richiesta cURL a 17Track
function makeRequest($url, $data, $apiKey, $method = 'POST')
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'17token: ' . $apiKey,
'Content-Type: application/json'
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
file_put_contents('debug.log', "Request URL: $url\nRequest Data: " . json_encode($data) . "\nResponse: $response\nHTTP Code: $httpCode\nError: $error\n", FILE_APPEND);
if ($response === false || $httpCode !== 200) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Errore nella richiesta API: HTTP ' . $httpCode . ' - ' . $error
];
}
$decodedResponse = json_decode($response, true);
if ($decodedResponse === null) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Risposta API non valida (non è JSON)'
];
}
return $decodedResponse;
}
// Step 1: Prova con auto-detection
$trackUrl = 'https://api.17track.net/track/v2/register';
$trackData = [
[
'number' => $trackingNumber,
'carrier' => null,
'auto_detection' => true
]
];
$registerResponse = makeRequest($trackUrl, $trackData, $apiKey);
file_put_contents('debug.log', "Register Response (Auto-detect): " . json_encode($registerResponse) . "\n", FILE_APPEND);
if (!isset($registerResponse['data']['accepted']) || empty($registerResponse['data']['accepted'])) {
$errorMessage = $registerResponse['data']['rejected'][0]['error']['message'] ?? 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nella registrazione del tracking: ' . $errorMessage]);
}
// Step 2: Recupera i dettagli con riprova
$getUrl = 'https://api.17track.net/track/v2/gettrackinfo';
$getData = [['number' => $trackingNumber]];
$maxAttempts = 3;
$delaySeconds = 5;
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$trackResponse = makeRequest($getUrl, $getData, $apiKey);
file_put_contents('debug.log', "Track Response (Attempt $attempt): " . json_encode($trackResponse) . "\n", FILE_APPEND);
if (isset($trackResponse['data']['accepted']) && !empty($trackResponse['data']['accepted'])) {
break;
}
if ($attempt < $maxAttempts) {
sleep($delaySeconds);
}
}
// Se auto-detection ha successo, procedi
if (isset($trackResponse['data']['accepted']) && !empty($trackResponse['data']['accepted'])) {
$trackingInfo = $trackResponse['data']['accepted'][0]['track'] ?? null;
if (!$trackingInfo) {
sendResponse(['success' => false, 'message' => 'Nessuna informazione di tracking trovata']);
}
$deliveryDate = 'Non disponibile';
$signedBy = 'Non disponibile';
$carrierName = $trackingInfo['carrier']['name'] ?? 'Sconosciuto';
if (isset($trackingInfo['z0']['e']) && $trackingInfo['z0']['e'] == 40) {
$deliveryDate = $trackingInfo['z0']['z'] ?? 'Non disponibile';
$signedBy = $trackingInfo['z0']['d'] ?? 'Non disponibile';
}
sendResponse([
'success' => true,
'deliveryDate' => $deliveryDate,
'signedBy' => $signedBy,
'carrierName' => $carrierName
]);
}
// Step 3: Se auto-detection fallisce, prova una lista di corrieri comuni
$commonCarriers = [
['code' => 100003, 'name' => 'TNT'], // TNT
['code' => 100001, 'name' => 'DHL'], // DHL Express
['code' => 100065, 'name' => 'DHL eCommerce'], // DHL eCommerce
['code' => 100002, 'name' => 'UPS'] // UPS
];
$possibleCarriers = [];
foreach ($commonCarriers as $carrier) {
$trackData = [
[
'number' => $trackingNumber,
'carrier' => $carrier['code'],
'auto_detection' => false
]
];
$registerResponse = makeRequest($trackUrl, $trackData, $apiKey);
if (isset($registerResponse['data']['accepted']) && !empty($registerResponse['data']['accepted'])) {
$possibleCarriers[] = $carrier['name'];
}
sleep(1); // Piccolo ritardo per evitare limiti di rate
}
if (!empty($possibleCarriers)) {
sendResponse([
'success' => false,
'message' => 'Auto-detection fallita. Seleziona un corriere tra quelli possibili.',
'possibleCarriers' => $possibleCarriers
]);
} else {
sendResponse(['success' => false, 'message' => 'Nessun corriere identificato per questo numero di tracking']);
}

View File

@ -1,166 +0,0 @@
<?php
// Abilita il debug degli errori (solo per sviluppo)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Assicurati che non ci sia output prima del JSON
ob_start();
// Imposta l'header per JSON
header('Content-Type: application/json');
// Configura la tua chiave API di TrackingMore
$apiKey = 'u4ssgynn-xuyy-9act-ca29-2glsv2qlzh0w'; // La tua chiave API reale
// Funzione per inviare una risposta JSON e terminare l'esecuzione
function sendResponse($data)
{
ob_end_clean();
echo json_encode($data);
exit;
}
// Verifica che il numero di tracking sia stato inviato
if (!isset($_POST['tracking_number']) || empty($_POST['tracking_number'])) {
sendResponse(['success' => false, 'message' => 'Numero di tracking non fornito']);
}
$trackingNumber = $_POST['tracking_number'];
// Funzione per fare una richiesta cURL a TrackingMore
function makeRequest($url, $data, $apiKey, $method = 'POST')
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Tracking-Api-Key: ' . $apiKey,
'Content-Type: application/json'
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
file_put_contents('debug.log', "Request URL: $url\nRequest Data: " . json_encode($data) . "\nResponse: $response\nHTTP Code: $httpCode\nError: $error\n", FILE_APPEND);
if ($response === false || ($httpCode !== 200 && $httpCode !== 201)) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Errore nella richiesta API: HTTP ' . $httpCode . ' - ' . $error
];
}
$decodedResponse = json_decode($response, true);
if ($decodedResponse === null) {
return [
'success' => false,
'code' => $httpCode,
'message' => 'Risposta API non valida (non è JSON)'
];
}
$decodedResponse['success'] = isset($decodedResponse['meta']['code']) && ($decodedResponse['meta']['code'] === 200 || $decodedResponse['meta']['code'] === 201);
return $decodedResponse;
}
// Step 1: Identifica il corriere
$detectUrl = 'https://api.trackingmore.com/v4/couriers/detect';
$detectData = ['tracking_number' => $trackingNumber];
$detectResponse = makeRequest($detectUrl, $detectData, $apiKey);
file_put_contents('debug.log', "Detect Response: " . json_encode($detectResponse) . "\n", FILE_APPEND);
if (!$detectResponse['success']) {
$errorMessage = isset($detectResponse['meta']['message']) ? $detectResponse['meta']['message'] : 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nella rilevazione del corriere: ' . $errorMessage]);
}
$couriers = $detectResponse['data'] ?? [];
if (empty($couriers)) {
sendResponse(['success' => false, 'message' => 'Corriere non identificato per il numero di tracking']);
}
// Prendi il primo corriere per ora
$courierCode = $couriers[0]['courier_code'] ?? null;
$courierName = $couriers[0]['courier_name'] ?? 'Sconosciuto';
if (!$courierCode) {
sendResponse(['success' => false, 'message' => 'Corriere non identificato']);
}
// Step 2: Crea un tracking
$createUrl = 'https://api.trackingmore.com/v4/trackings/create';
$createData = [
'tracking_number' => $trackingNumber,
'courier_code' => $courierCode
];
$createResponse = makeRequest($createUrl, $createData, $apiKey);
file_put_contents('debug.log', "Create Response: " . json_encode($createResponse) . "\n", FILE_APPEND);
if (!$createResponse['success']) {
$errorMessage = isset($createResponse['meta']['message']) ? $createResponse['meta']['message'] : 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nella creazione del tracking: ' . $errorMessage]);
}
// Step 3: Recupera i dettagli del tracking con riprova
$getUrl = 'https://api.trackingmore.com/v4/trackings/get?tracking_number=' . urlencode($trackingNumber);
$maxAttempts = 3;
$delaySeconds = 5;
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$trackResponse = makeRequest($getUrl, [], $apiKey, 'GET');
file_put_contents('debug.log', "Track Response (Attempt $attempt): " . json_encode($trackResponse) . "\n", FILE_APPEND);
if ($trackResponse['success'] && !empty($trackResponse['data'])) {
break;
}
if ($attempt < $maxAttempts) {
sleep($delaySeconds);
}
}
if (!$trackResponse['success']) {
$errorMessage = isset($trackResponse['meta']['message']) ? $trackResponse['meta']['message'] : 'Errore sconosciuto';
sendResponse(['success' => false, 'message' => 'Errore nel recupero delle informazioni: ' . $errorMessage]);
}
$trackingInfo = $trackResponse['data'] ?? null;
if (!$trackingInfo) {
// Se ci sono più corrieri rilevati, restituisci una lista per la tendina
if (count($couriers) > 1) {
$possibleCarriers = array_map(function ($courier) {
return ['code' => $courier['courier_code'], 'name' => $courier['courier_name']];
}, $couriers);
sendResponse([
'success' => false,
'message' => 'Dati non trovati. Seleziona un corriere tra quelli rilevati.',
'possibleCarriers' => $possibleCarriers
]);
}
sendResponse(['success' => false, 'message' => 'Nessuna informazione di tracking trovata']);
}
// Estrai la data di consegna e il firmatario
$deliveryDate = 'Non disponibile';
$signedBy = 'Non disponibile';
foreach ($trackingInfo['trackinfo'] as $checkpoint) {
if ($checkpoint['status'] === 'delivered') {
$deliveryDate = $checkpoint['Date'];
$signedBy = $checkpoint['signed_by'] ?? 'Non disponibile';
break;
}
}
// Restituisci i dati al frontend
sendResponse([
'success' => true,
'deliveryDate' => $deliveryDate,
'signedBy' => $signedBy,
'carrierName' => $courierName
]);

View File

@ -1,74 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
// Disable PHP error display
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Parametri OData
$params = [
'$select' => 'IdCliente,Nominativo,CodiceNazioneFatturazione',
'$orderby' => 'Nominativo asc'
];
// Costruisce query string con encoding corretto
$queryString = http_build_query($params);
// Componi endpoint finale
$endpoint = "Cliente?$queryString";
// Funzione per eseguire la chiamata con retry
function makeApiRequest($api, $endpoint, $maxRetries = 3)
{
for ($retry = 0; $retry < $maxRetries; $retry++) {
try {
// Tenta la chiamata API
$data = $api->get($endpoint);
// Salva risposta per debug
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data, JSON_PRETTY_PRINT));
return $data;
} catch (Exception $e) {
$errorMessage = $e->getMessage();
// Controlla se l'errore è legato all'autenticazione (HTTP 400 con messaggio specifico)
if (strpos($errorMessage, 'HTTP 400') !== false && strpos($errorMessage, 'Cannot persist the object') !== false) {
// Forza il refresh del token
try {
// Assumi che VisualLimsApiClient abbia un metodo per il refresh del token
$api->refreshToken(); // Da implementare in VisualLimsApiClient se non esiste
error_log("Tentativo $retry: Refresh token eseguito per endpoint $endpoint");
} catch (Exception $refreshEx) {
error_log("Errore durante il refresh del token: " . $refreshEx->getMessage());
throw new Exception("Impossibile eseguire il refresh del token: " . $refreshEx->getMessage());
}
// Ritarda leggermente prima del retry
usleep(500000); // 500ms
continue;
}
// Altri errori non gestiti dal retry
throw $e;
}
}
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
// Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint);
echo json_encode($data);
} catch (Exception $e) {
http_response_code(500);
$errorResponse = [
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
error_log("Errore in get_clienti.php: " . json_encode($errorResponse));
echo json_encode($errorResponse);
}

View File

@ -1,39 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// ID della CommessaWeb specifica (cambialo di volta in volta)
$id = 529435; // TODO: Cambia questo valore con l'ID desiderato
// Endpoint per recuperare la CommessaWeb specifica con espansione dello schema custom
$endpoint = "CommessaWeb({$id})";
// Opzioni per l'espansione: includi OrderCustomFields per ottenere i campi custom dello schema assegnato all'ordine
$options = ['$expand' => 'OrderCustomFields'];
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il JSON in locale
file_put_contents(__DIR__ . '/commessaweb_schema_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} 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(['error' => $e->getMessage()]);
}

View File

@ -1,41 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// მივიღოთ მრავლობითი field_ids
$fieldIds = [];
if (isset($_GET['field_ids'])) {
$fieldIds = array_filter(array_map('intval', explode(',', $_GET['field_ids'])));
}
// თუ არ გადმოგვცეს -> ერთი default
if (empty($fieldIds)) {
$fieldIds = [156];
}
$results = [];
foreach ($fieldIds as $customFieldId) {
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
$data = $api->get($endpoint);
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
}
// Debug ფაილი
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
echo json_encode($results);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage()
]);
}

View File

@ -1,59 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
use Dotenv\Dotenv;
// Set JSON header
header('Content-Type: application/json');
// Debug: Log the path where we expect the .env file
$envPath = dirname(__DIR__, 2);
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Expected .env path: ' . $envPath . PHP_EOL, FILE_APPEND);
// Carica il file .env dalla root del progetto
try {
$dotenv = Dotenv::createImmutable($envPath);
$dotenv->load();
} catch (Exception $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore caricamento configurazione: ' . $e->getMessage()]);
exit(1);
}
// Recupera le variabili d'ambiente
$dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE'];
$dbUser = $_ENV['DB_USERNAME'];
$dbPass = $_ENV['DB_PASSWORD'];
$dbPrefix = $_ENV['DB_PREFIX'];
// Debug: Log database connection details (excluding password)
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . " - DB Connection: host=$dbHost, dbname=$dbName, user=$dbUser, prefix=$dbPrefix" . PHP_EOL, FILE_APPEND);
// Connessione al database MySQL
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore connessione DB: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore connessione al database: ' . $e->getMessage()]);
exit(1);
}
try {
// Query per recuperare i valori distinti di MacroMatrice, escludendo quelli che iniziano con '*' e ordinandoli
$query = "SELECT DISTINCT MacroMatrice FROM {$dbPrefix}matrici WHERE MacroMatrice IS NOT NULL AND MacroMatrice NOT LIKE '*%' ORDER BY MacroMatrice ASC";
$stmt = $pdo->prepare($query);
$stmt->execute();
$macroMatrici = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Debug: Log del numero di MacroMatrice recuperate
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Retrieved ' . count($macroMatrici) . ' MacroMatrice from database' . PHP_EOL, FILE_APPEND);
// Restituisci risposta JSON
echo json_encode(['success' => true, 'value' => $macroMatrici]);
} catch (PDOException $e) {
// Log errore e restituisci risposta di errore
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore nel recupero delle MacroMatrice: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore nel recupero delle MacroMatrice: ' . $e->getMessage()]);
exit(1);
}

View File

@ -1,36 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Endpoint per recuperare le Matrici
$endpoint = 'Matrice';
// (Opzionale) aggiungi parametri, ad esempio $top per limitare i risultati
$options = []; // oppure ad esempio: ['$top' => 100]
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il JSON in locale
file_put_contents(__DIR__ . '/matrici_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} 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(['error' => $e->getMessage()]);
}

View File

@ -1,59 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Risale a root/vendor/
use Dotenv\Dotenv;
// Set JSON header
header('Content-Type: application/json');
// Debug: Log the path where we expect the .env file
$envPath = dirname(__DIR__, 2);
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Expected .env path: ' . $envPath . PHP_EOL, FILE_APPEND);
// Carica il file .env dalla root del progetto
try {
$dotenv = Dotenv::createImmutable($envPath);
$dotenv->load();
} catch (Exception $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore caricamento .env: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore caricamento configurazione: ' . $e->getMessage()]);
exit(1);
}
// Recupera le variabili d'ambiente
$dbHost = $_ENV['DB_HOST'];
$dbName = $_ENV['DB_DATABASE'];
$dbUser = $_ENV['DB_USERNAME'];
$dbPass = $_ENV['DB_PASSWORD'];
$dbPrefix = $_ENV['DB_PREFIX'];
// Debug: Log database connection details (excluding password)
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . " - DB Connection: host=$dbHost, dbname=$dbName, user=$dbUser, prefix=$dbPrefix" . PHP_EOL, FILE_APPEND);
// Connessione al database MySQL
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore connessione DB: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore connessione al database: ' . $e->getMessage()]);
exit(1);
}
try {
// Query per recuperare le matrici, includendo MacroMatrice, escludendo quelle che iniziano con '*' e ordinandole
$query = "SELECT IdMatrice, NomeMatrice, MacroMatrice FROM {$dbPrefix}matrici WHERE NomeMatrice NOT LIKE '*%' ORDER BY NomeMatrice ASC";
$stmt = $pdo->prepare($query);
$stmt->execute();
$matrici = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Debug: Log del numero di matrici recuperate
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Retrieved ' . count($matrici) . ' matrices from database' . PHP_EOL, FILE_APPEND);
// Restituisci risposta JSON
echo json_encode(['success' => true, 'value' => $matrici]);
} catch (PDOException $e) {
// Log errore e restituisci risposta di errore
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - Errore nel recupero delle matrici: ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
echo json_encode(['success' => false, 'message' => 'Errore nel recupero delle matrici: ' . $e->getMessage()]);
exit(1);
}

View File

@ -1,37 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClientXml.class.php';
header('Content-Type: application/xml; charset=utf-8');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClientXml::getInstance();
// Endpoint per i metadata
$endpoint = '$metadata';
// Nessun parametro aggiuntivo necessario
$options = [];
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il file XML in locale
file_put_contents(__DIR__ . '/metadata_response.xml', $data);
// Restituisci il contenuto XML
echo $data;
} 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 '<?xml version="1.0" encoding="utf-8"?><error>' . htmlspecialchars($e->getMessage()) . '</error>';
}

View File

@ -1,26 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$rapporto_id = 533329;
// Costruzione manuale dell'endpoint con espansione annidata
$endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
// Non passiamo options, già incluso nell'endpoint
$data = $api->get($endpoint);
file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
echo json_encode($data);
} 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(['error' => $e->getMessage()]);
}

View File

@ -1,30 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// ID dello schema passato via GET o default 45
$schemaId = isset($_GET['id']) && is_numeric($_GET['id']) ? intval($_GET['id']) : 42;
// IMPORTANTE: $expand va dentro la stringa endpoint per entità singola
$endpoint = "SchemaCustomField($schemaId)?\$expand=SchemiCustomFieldsDettagli(\$expand=CustomField)";
// Nessun parametro aggiuntivo
$data = $api->get($endpoint);
// Salva la risposta per debug
file_put_contents(__DIR__ . '/schema_dettagli_response.json', json_encode($data));
echo json_encode($data);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage()
]);
}

View File

@ -1,36 +0,0 @@
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
// Nessun filtro o espansione: solo la lista degli schemi
$endpoint = 'SchemaCustomField';
// (Opzionale) aggiungi $top se vuoi limitare i risultati
$options = []; // oppure ad esempio: ['$top' => 100]
// Debug: salva URL usato
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
$query = http_build_query($options);
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
file_put_contents(__DIR__ . '/last_url.txt', $full_url . PHP_EOL, FILE_APPEND);
// Chiamata API
$data = $api->get($endpoint, $options);
// Salva il JSON in locale
file_put_contents(__DIR__ . '/schemi_base_response.json', json_encode($data, JSON_PRETTY_PRINT));
echo json_encode($data);
} 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(['error' => $e->getMessage()]);
}

View File

@ -1,26 +0,0 @@
```php
<?php
require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; // Torna al livello di public per trovare vendor/
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
header('Content-Type: application/json');
// Disabilita la visualizzazione degli errori PHP per evitare output HTML
ini_set('display_errors', '0');
error_reporting(E_ALL);
try {
$api = VisualLimsApiClient::getInstance();
$data = $api->get("SchemaCustomField"); // Recupera la lista degli schemi custom fields
// Salva la risposta in un file per debug
file_put_contents(__DIR__ . '/schemi_custom_fields_response.json', json_encode($data));
echo json_encode($data);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage()
]);
}
?>

File diff suppressed because it is too large Load Diff

View File

@ -1,153 +0,0 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<style>
/* Layout flessibile per gestire dimensioni diverse */
#templateButtons {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
/* Allinea a sinistra */
padding: 20px;
}
/* Definizione delle dimensioni */
/* Definizione delle dimensioni */
.btn-small {
font-size: 12px;
padding: 6px 12px;
min-width: 100px;
min-height: 30px;
display: flex;
/* Aggiunto */
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
}
.btn-medium {
font-size: 16px;
padding: 10px 20px;
min-width: 130px;
min-height: 45px;
display: flex;
/* Aggiunto */
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
}
.btn-large {
font-size: 20px;
padding: 14px 28px;
min-width: 180px;
min-height: 60px;
display: flex;
/* Aggiunto */
justify-content: center;
/* Aggiunto */
align-items: center;
/* Aggiunto */
}
/* Stile della barra di ricerca */
#searchInput {
width: 100%;
padding: 10px;
font-size: 16px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Active Templates</h6>
</div>
<div class="card-body">
<!-- Barra di ricerca -->
<input type="text" id="searchInput" placeholder="Search template...">
<div id="templateButtons"></div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
fetch("load_active_templates.php")
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("Error loading templates:", data.message);
return;
}
let templateButtons = document.getElementById("templateButtons");
templateButtons.innerHTML = '';
data.data.forEach(template => {
let sizeClass = "btn-medium"; // Default
if (template.button_size === "small") sizeClass = "btn-small";
if (template.button_size === "large") sizeClass = "btn-large";
let btn = document.createElement("a");
btn.href = `import_xls2.php?id=${template.id}`;
btn.className = `btn ${sizeClass}`;
btn.style.backgroundColor = template.button_bg_color;
btn.style.color = template.button_text_color;
btn.textContent = template.button_label;
btn.setAttribute("data-label", template.button_label.toLowerCase()); // Attributo per ricerca
templateButtons.appendChild(btn);
});
})
.catch(error => 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";
}
});
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,173 +0,0 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/import_debug.log');
if (!file_exists(__DIR__ . '/import_debug.log')) {
file_put_contents(__DIR__ . '/import_debug.log', "Inizio importazione alle " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
}
// Log iniziale
error_log("Inizio importazione alle " . date('Y-m-d H:i:s'));
include('include/headscript.php');
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"));
exit;
}
$template_id = intval($_POST['template_id']);
$selected_rows = array_map('intval', $_POST['selected_rows']);
$columns = json_decode($_POST['columns'], true);
$rows = json_decode($_POST['rows'], true);
$excelrows = json_decode($_POST['excelrows'], true);
$newFilename = htmlspecialchars($_POST['filename']);
$_SESSION['template_id'] = $template_id;
$_SESSION['selected_rows'] = $selected_rows;
$_SESSION['columns'] = $columns;
$_SESSION['rows'] = $rows;
$_SESSION['excelrows'] = $excelrows;
$_SESSION['filename'] = $newFilename;
error_log("Received Data - Template ID: $template_id, Selected Rows: " . json_encode($selected_rows));
error_log("Columns: " . json_encode($columns));
error_log("Rows: " . json_encode($rows));
error_log("Excelrows: " . json_encode($excelrows));
$user_id = $iduserlogin ?? 1;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Genera un UUID univoco per importreferencecode
$importReferenceCode = date('YmdHis') . '-' . uniqid();
// Recupera tutti i mapping dal template
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($allMappings)) {
header("Location: import_xls.php?id=$template_id&status=error&message=" . urlencode("Nessun mapping trovato per il template"));
exit;
}
// Trova il campo main_field
$mainFieldMapping = null;
foreach ($allMappings as $mapping) {
if ($mapping['main_field'] == 1) {
$mainFieldMapping = $mapping;
break;
}
}
// Inserisci le righe selezionate in datadb
$insertedIds = [];
foreach ($selected_rows as $rowIndex) {
$row = $rows[$rowIndex] ?? null;
$excelrow = $excelrows[$rowIndex] ?? null;
if ($row === null || $excelrow === null) {
error_log("Errore: riga o excelrow mancante per rowIndex $rowIndex");
continue;
}
// Recupera l'idclient di default dal template
$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
$template_stmt->execute([$template_id]);
$template = $template_stmt->fetch(PDO::FETCH_ASSOC);
$default_idclient = $template['idclient'] ?? null;
$values = [
$template_id,
$importReferenceCode,
$newFilename,
'i',
$user_id,
null,
date('Y-m-d'),
$excelrow,
$default_idclient // Aggiungi idclient
];
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
$iddatadb = $pdo->lastInsertId();
$insertedIds[] = $iddatadb;
// Inserisci tutti i campi in import_data_details
foreach ($allMappings as $mapping) {
$fieldValue = null;
if (!$mapping['is_manual']) {
$excelColumn = trim($mapping['excel_column']);
$excelColumnIndex = array_search($excelColumn, array_map('trim', $columns));
if ($excelColumnIndex !== false && isset($row[$excelColumnIndex]) && $row[$excelColumnIndex] !== '') {
$fieldValue = $row[$excelColumnIndex];
error_log("Found Excel column '$excelColumn' at index $excelColumnIndex, value: " . var_export($fieldValue, true));
} else {
$fieldValue = $mapping['manual_default'] ?? '';
error_log("Excel column '$excelColumn' not found or empty, using default: " . var_export($fieldValue, true));
}
switch ($mapping['data_type']) {
case 'INT':
$fieldValue = is_numeric($fieldValue) ? (int)$fieldValue : ($mapping['manual_default'] ?? 0);
break;
case 'DATE':
$fieldValue = !empty($fieldValue) ? date('Y-m-d', strtotime($fieldValue)) : ($mapping['manual_default'] === 'today' ? date('Y-m-d') : ($mapping['manual_default'] ?? ''));
break;
case 'CHAR':
$fieldValue = !empty($fieldValue) ? substr((string)$fieldValue, 0, 1) : ($mapping['manual_default'] ?? '');
break;
case 'Testo':
case 'VARCHAR':
default:
$fieldValue = !empty($fieldValue) ? htmlspecialchars((string)$fieldValue) : ($mapping['manual_default'] ?? '');
break;
}
} else {
$fieldValue = $mapping['manual_default'] ?? '';
if ($mapping['data_type'] === 'DATE' && $mapping['manual_default'] === 'today') {
$fieldValue = date('Y-m-d');
}
}
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
}
error_log("Inserting into import_data_details - Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true) . ", Is Manual: " . $mapping['is_manual'] . ", Excel Column: " . ($mapping['excel_column'] ?? 'N/A') . ", Manual Default: " . ($mapping['manual_default'] ?? 'N/A'));
$stmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
$stmt->execute([$iddatadb, $mapping['id'], $fieldValue]);
error_log("Inserted into import_data_details for ID $iddatadb, Mapping ID: " . $mapping['id'] . ", Field Value: " . var_export($fieldValue, true));
}
}
$_SESSION['inserted_ids'] = $insertedIds;
$params = [
'template_id' => $template_id,
'filename' => $newFilename,
];
?>
<form id="redirectForm" action="import_edit2.php" method="post">
<input type="hidden" name="template_id" value="<?= htmlspecialchars($template_id) ?>">
<input type="hidden" name="filename" value="<?= htmlspecialchars($newFilename) ?>">
<?php foreach ($selected_rows as $row): ?>
<input type="hidden" name="selected_rows[]" value="<?= htmlspecialchars($row) ?>">
<?php endforeach; ?>
<?php foreach ($insertedIds as $id): ?>
<input type="hidden" name="inserted_ids[]" value="<?= htmlspecialchars($id) ?>">
<?php endforeach; ?>
<input type="hidden" name="columns" value='<?= json_encode($columns) ?>'>
<input type="hidden" name="rows" value='<?= json_encode($rows) ?>'>
<input type="hidden" name="excelrows" value='<?= json_encode($excelrows) ?>'>
</form>
<script>
document.getElementById('redirectForm').submit();
</script>
<?php
exit;
?>

View File

@ -0,0 +1,412 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php'); // usa la tua classe PDO
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Imported Data Management</title>
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">
<style>
.editable {
cursor: pointer;
background-color: #fff8e1;
}
.action-btn {
padding: 4px 8px;
font-size: 0.85rem;
}
td.editable:hover {
background-color: #fff5cc;
cursor: text;
}
td.modified {
background-color: #ff9c9cff !important;
/* evidenzia celle modificate */
}
td.noedit {
background-color: #f8f9fa;
color: #888;
}
</style>
</head>
<body>
<?php include('include/navbar.php'); ?>
<div class="dashboard-main-wrapper wrapper">
<?php include('include/topbar.php'); ?>
<div class="dashboard-body">
<div class="card shadow-sm">
<div class="card-body">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3">
<h4 class="mb-0">Imported Data</h4>
<div class="d-flex align-items-center gap-2">
<label for="clientFilter" class="form-label mb-0 me-2">Filter by Client:</label>
<select id="clientFilter" class="form-select form-select-sm" style="width: 220px;">
<option value="">All Clients</option>
<?php
// carichiamo i clienti dal DB
$clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC);
foreach ($clients as $cl) {
echo '<option value="' . htmlspecialchars($cl['idclient']) . '">' . htmlspecialchars($cl['client_name']) . '</option>';
}
?>
</select>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<button id="updatePricesBtn" class="btn btn-warning btn-sm" style="display:none;">
<i class="fas fa-sync-alt"></i> Aggiorna Prezzi
</button>
</div>
</div>
<div class="table-responsive">
<table id="importTable" class="table table-striped table-bordered w-100">
<thead>
<tr>
<th></th> <!-- Checkbox column -->
<th>ID</th>
<th>Collection</th>
<th>Category</th>
<th>Model</th>
<th>Model Description</th>
<th>Mod.Code</th>
<th>Description</th>
<th>Finish.Code</th>
<th>Finishing Description</th>
<th>Available Qty</th>
<th>WH</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script>
$(document).ready(function() {
let table = $('#importTable').DataTable({
ajax: {
url: 'import_list_data.php',
dataSrc: ''
},
columns: [{
data: null,
orderable: false,
searchable: false,
className: 'text-center',
render: function() {
return '<input type="checkbox" class="row-check" />';
}
},
{
data: 'id'
},
{
data: 'collection',
className: 'editable'
},
{
data: 'category',
className: 'noedit text-muted'
},
{
data: 'model',
className: 'editable'
},
{
data: 'model_description',
className: 'editable'
},
{
data: 'mod_code',
className: 'text-muted'
},
{
data: 'description',
className: 'editable'
},
{
data: 'finish_code',
className: 'editable'
},
{
data: 'finishing_description',
className: 'editable'
},
{
data: 'available_qty',
className: 'editable'
},
{
data: 'wh',
className: 'editable'
},
{
data: 'price',
className: 'editable'
},
{
data: null,
orderable: false,
render: function(data, type, row) {
return `
<button class="btn btn-success btn-sm action-btn save-row" data-id="${row.id}">
<i class="fas fa-save"></i>
</button>
<button class="btn btn-danger btn-sm action-btn delete-row" data-id="${row.id}">
<i class="fas fa-trash"></i>
</button>
`;
}
}
],
pageLength: 25,
order: [
[1, 'desc']
],
responsive: true,
autoWidth: false
});
// === Filtro per Client ===
$('#clientFilter').on('change', function() {
const selectedClient = $(this).val();
// Ricarica i dati passando il parametro idclient
table.ajax.url('import_list_data.php' + (selectedClient ? '?idclient=' + selectedClient : '')).load();
});
// === Inline Editing ===
$('#importTable tbody').on('click', 'td.editable', function() {
const cell = table.cell(this);
const rowData = table.row(cell.index().row).data();
const colName = table.column(cell.index().column).dataSrc();
const originalValue = rowData[colName] ?? '';
// Evita doppia apertura
if ($(this).find('input').length > 0) return;
const input = $('<input type="text" class="form-control form-control-sm"/>')
.val(originalValue)
.css({
'min-width': '120px',
'height': '24px',
'padding': '0 4px'
});
$(this).html(input);
input.focus();
// Gestione conferma o uscita
input.on('blur keydown', function(e) {
if (e.type === 'blur' || e.key === 'Enter') {
const newValue = $(this).val();
// Aggiorna in memoria e interfaccia
rowData[colName] = newValue;
table.row(cell.index().row).data(rowData).draw(false);
const td = $(table.cell(cell.index()).node());
if (newValue !== originalValue) {
td.addClass('modified'); // evidenzia modificato
} else {
td.removeClass('modified');
}
}
});
});
// === Salvataggio singola riga ===
$('#importTable').on('click', '.save-row', function() {
const rowData = table.row($(this).closest('tr')).data();
$.ajax({
url: 'import_list_save.php',
type: 'POST',
data: {
row: JSON.stringify(rowData)
},
success: function(res) {
Swal.close();
// Se il server restituisce già JSON, non serve JSON.parse()
let json = (typeof res === 'object') ? res : null;
if (!json) {
try {
json = JSON.parse(res);
} catch (e) {
console.error("Invalid JSON from server:", res);
Swal.fire('Errore', 'Risposta non valida dal server.', 'error');
return;
}
}
if (json.status === 'ok') {
Swal.fire({
title: 'Successo',
text: `Aggiornati ${json.updated} prezzi.`,
icon: 'success',
timer: 2000,
showConfirmButton: false
});
table.ajax.reload(null, false); // 🔁 aggiorna tabella
} else {
console.error("Errore backend:", json);
Swal.fire('Errore', json.message || 'Errore sconosciuto.', 'error');
}
},
error: function(xhr, status, err) {
Swal.fire('Errore', 'Errore di rete o server non raggiungibile.', 'error');
console.error("AJAX error:", status, err, xhr.responseText);
}
});
});
// === Eliminazione con conferma ===
$('#importTable').on('click', '.delete-row', function() {
const id = $(this).data('id');
Swal.fire({
title: 'Are you sure?',
text: 'This record will be permanently deleted.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it',
cancelButtonText: 'Cancel'
}).then(result => {
if (result.isConfirmed) {
$.ajax({
url: 'import_list_delete.php',
type: 'POST',
data: {
id: id
},
success: function(res) {
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Deleted', 'Row removed successfully.', 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Error', json.message, 'error');
}
},
error: function() {
Swal.fire('Error', 'Request failed.', 'error');
}
});
}
});
});
// === Gestione selezione righe e pulsante Aggiorna Prezzi ===
$('#importTable').on('change', '.row-check', function() {
const checkedCount = $('#importTable .row-check:checked').length;
$('#updatePricesBtn').toggle(checkedCount > 0);
});
// === Aggiornamento prezzi con ChatPDF ===
$('#updatePricesBtn').on('click', function() {
const selectedRows = [];
$('#importTable .row-check:checked').each(function() {
const row = table.row($(this).closest('tr')).data();
if (row) selectedRows.push(row);
});
if (selectedRows.length === 0) {
Swal.fire('Notice', 'Seleziona almeno una riga.', 'info');
return;
}
const clientId = $('#clientFilter').val();
if (!clientId) {
Swal.fire('Notice', 'Seleziona prima un cliente.', 'info');
return;
}
Swal.fire({
title: 'Aggiornare i prezzi?',
text: `Verranno interrogati i prezzi per ${selectedRows.length} prodotti.`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Procedi',
cancelButtonText: 'Annulla'
}).then(result => {
if (result.isConfirmed) {
Swal.fire({
title: 'Aggiornamento in corso...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
$.ajax({
url: 'update_prices_chatpdf.php',
type: 'POST',
data: {
rows: JSON.stringify(selectedRows),
idclient: clientId
},
success: function(res) {
Swal.close();
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Errore', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Successo', `Aggiornati ${json.updated} prezzi.`, 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Errore', json.message, 'error');
}
},
error: function() {
Swal.fire('Errore', 'Aggiornamento fallito.', 'error');
}
});
}
});
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<?php
header('Content-Type: application/json; charset=utf-8');
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$idclient = isset($_GET['idclient']) && is_numeric($_GET['idclient']) ? intval($_GET['idclient']) : null;
try {
if ($idclient) {
$stmt = $pdo->prepare("SELECT * FROM importdb WHERE idclient = :idclient ORDER BY id DESC");
$stmt->execute([':idclient' => $idclient]);
} else {
$stmt = $pdo->query("SELECT * FROM importdb ORDER BY id DESC");
}
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -0,0 +1,20 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/../class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$id = intval($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'Invalid ID']);
exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM importdb WHERE id = :id");
$stmt->execute([':id' => $id]);
echo json_encode(['status' => 'ok']);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -0,0 +1,47 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$row = json_decode($_POST['row'] ?? '', true);
if (!$row || !isset($row['id'])) {
echo json_encode(['status' => 'error', 'message' => 'Invalid data']);
exit;
}
try {
$sql = "UPDATE importdb SET
collection = :collection,
category = :category,
model = :model,
model_description = :model_description,
description = :description,
finish_code = :finish_code,
finishing_description = :finishing_description,
available_qty = :available_qty,
wh = :wh,
price = :price,
updated_on = CURRENT_TIMESTAMP
WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':collection' => $row['collection'] ?? null,
':category' => $row['category'] ?? null,
':model' => $row['model'] ?? null,
':model_description' => $row['model_description'] ?? null,
':description' => $row['description'] ?? null,
':finish_code' => $row['finish_code'] ?? null,
':finishing_description' => $row['finishing_description'] ?? null,
':available_qty' => $row['available_qty'] ?? null,
':wh' => $row['wh'] ?? null,
':price' => $row['price'] ?? null,
':id' => $row['id']
]);
echo json_encode(['status' => 'ok']);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -0,0 +1,200 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php'); // PDO class
use Smalot\PdfParser\Parser;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// --- Carica clienti ---
$clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC);
// --- Messaggio di stato ---
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['price_pdf'])) {
$idclient = intval($_POST['idclient'] ?? 0);
if ($idclient > 0 && $_FILES['price_pdf']['error'] === UPLOAD_ERR_OK) {
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0777, true);
$filename = basename($_FILES['price_pdf']['name']);
$targetPath = $uploadDir . $filename;
if (move_uploaded_file($_FILES['price_pdf']['tmp_name'], $targetPath)) {
try {
require_once(dirname(__DIR__, 2) . '/vendor/autoload.php');
$parser = new Parser();
$pdf = $parser->parseFile($targetPath);
$text = $pdf->getText();
// Salva testo completo per analisi
file_put_contents(__DIR__ . '/debug_pdf_text.txt', $text);
// Pattern iniziale: "499 1C 23 €123,45" o "499 1C 23 123,45"
$pattern = '/(\d{3})\s([A-Z0-9]{1,2})\s(\d{2})\s*[€]?\s?(\d{1,4},\d{2})/';
preg_match_all($pattern, $text, $matches, PREG_SET_ORDER);
// Salva tutti i match trovati
$debugMatches = "Matches found: " . count($matches) . "\n\n";
foreach ($matches as $m) {
$debugMatches .= implode(' | ', $m) . "\n";
}
file_put_contents(__DIR__ . '/debug_matches.txt', $debugMatches);
$inserted = 0;
$stmt = $pdo->prepare("
INSERT INTO price_mapping (idclient, price_ref, price, source_file)
VALUES (:idclient, :price_ref, :price, :source_file)
");
foreach ($matches as $m) {
$ref = trim($m[1] . $m[2] . $m[3]);
$price = str_replace(',', '.', $m[4]);
$stmt->execute([
':idclient' => $idclient,
':price_ref' => $ref,
':price' => $price,
':source_file' => $filename
]);
$inserted++;
}
$message = "<div class='alert alert-success mt-3'>✅ Imported $inserted prices successfully.</div>";
} catch (Exception $e) {
$message = "<div class='alert alert-danger mt-3'>Error parsing PDF: " . htmlspecialchars($e->getMessage()) . "</div>";
}
} else {
$message = "<div class='alert alert-danger mt-3'>Upload failed.</div>";
}
} else {
$message = "<div class='alert alert-warning mt-3'>Select a client and upload a valid PDF file.</div>";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Import PDF Prices</title>
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<style>
.form-section {
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
}
</style>
</head>
<body>
<?php include('include/navbar.php'); ?>
<div class="dashboard-main-wrapper wrapper">
<?php include('include/topbar.php'); ?>
<div class="dashboard-body">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-3">📄 Import Price List from PDF</h4>
<form method="POST" enctype="multipart/form-data" class="form-section row g-3 align-items-end">
<div class="col-md-4">
<label for="idclient" class="form-label">Select Client</label>
<select name="idclient" id="idclient" class="form-select" required>
<option value="">-- Choose Client --</option>
<?php foreach ($clients as $cl): ?>
<option value="<?= htmlspecialchars($cl['idclient']) ?>">
<?= htmlspecialchars($cl['client_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label for="price_pdf" class="form-label">Upload PDF File</label>
<input type="file" name="price_pdf" id="price_pdf" class="form-control" accept="application/pdf" required>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-upload"></i> Import
</button>
</div>
</form>
<?= $message; ?>
<hr>
<h5 class="mt-4 mb-2">📊 Imported Prices</h5>
<div class="table-responsive">
<table id="priceTable" class="table table-striped table-bordered w-100">
<thead>
<tr>
<th>ID</th>
<th>Client</th>
<th>Code Ref</th>
<th>Price ()</th>
<th>Source File</th>
<th>Created At</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script>
$(document).ready(function() {
$('#priceTable').DataTable({
ajax: {
url: 'price_mapping_list.php',
dataSrc: ''
},
columns: [{
data: 'id'
},
{
data: 'client_name'
},
{
data: 'price_ref'
},
{
data: 'price'
},
{
data: 'source_file'
},
{
data: 'created_at'
}
],
pageLength: 25,
order: [
[0, 'desc']
],
responsive: true,
autoWidth: false
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,76 @@
<?php
include('include/headscript.php');
require_once(__DIR__ . '/class/db-functions.php'); // usa la tua classe PDO
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Ottieni dati dal POST
$rows = isset($_POST['rows']) ? json_decode($_POST['rows'], true) : [];
$idclient = isset($_POST['idclient']) ? intval($_POST['idclient']) : null;
// Recupera utente loggato
$iduser = $_SESSION['iduserlogin'] ?? null;
if (!$rows || !is_array($rows) || count($rows) === 0) {
echo json_encode(['status' => 'error', 'message' => 'No rows to import.']);
exit;
}
if (empty($idclient)) {
echo json_encode(['status' => 'error', 'message' => 'Client not specified.']);
exit;
}
try {
$pdo->beginTransaction();
$sql = "
INSERT INTO importdb
(collection, category, model, model_description, mod_code, description, finish_code, finishing_description, available_qty, wh, price, source_file, iduser, idclient)
VALUES
(:collection, :category, :model, :model_description, :mod_code, :description, :finish_code, :finishing_description, :available_qty, :wh, :price, :source_file, :iduser, :idclient)
ON DUPLICATE KEY UPDATE
collection = VALUES(collection),
category = VALUES(category),
model = VALUES(model),
model_description = VALUES(model_description),
description = VALUES(description),
finish_code = VALUES(finish_code),
finishing_description = VALUES(finishing_description),
available_qty = VALUES(available_qty),
wh = VALUES(wh),
price = VALUES(price),
updated_on = CURRENT_TIMESTAMP()
";
$stmt = $pdo->prepare($sql);
foreach ($rows as $r) {
if (isset($r['avalable_qty']) && !isset($r['available_qty'])) {
$r['available_qty'] = $r['avalable_qty'];
}
$stmt->execute([
':collection' => $r['collection'] ?? null,
':category' => $r['category'] ?? null,
':model' => $r['model'] ?? null,
':model_description' => $r['model_description'] ?? null,
':mod_code' => $r['mod_code'] ?? null,
':description' => $r['description'] ?? null,
':finish_code' => $r['finish_code'] ?? null,
':finishing_description' => $r['finishing_description'] ?? null,
':available_qty' => is_numeric($r['available_qty']) ? intval($r['available_qty']) : null,
':wh' => $r['wh'] ?? null,
':price' => isset($r['price']) && is_numeric($r['price']) ? floatval($r['price']) : null,
':source_file' => basename($_POST['source_file'] ?? 'upload.xlsx'),
':iduser' => $iduser,
':idclient' => $idclient
]);
}
$pdo->commit();
echo json_encode(['status' => 'ok', 'message' => count($rows) . ' rows imported/updated.']);
} catch (Exception $e) {
$pdo->rollBack();
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}

View File

@ -1,303 +1,258 @@
<?php
include('include/headscript.php');
// Controlla se è stato passato un ID valido
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']); // Sanifica l'ID
// Recupera il template dal database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit;
}
// Debug del template
error_log("Loaded template: " . print_r($template, true));
// Recupera lista clienti
$clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Import XLS - Cassina Products</title>
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">
<style>
.table-container {
overflow-x: auto;
max-width: 100%;
margin-bottom: 20px;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 10px;
text-align: left;
border: 1px solid #dee2e6;
min-width: 100px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table th:first-child,
.table td:first-child {
min-width: 50px;
max-width: 50px;
.select2-container {
width: 100% !important;
}
.table td,
.table th {
background-color: #f8f9fa;
position: relative;
cursor: col-resize;
user-select: none;
}
.table th .resize-handle {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
cursor: col-resize;
background: transparent;
}
.table th .resize-handle:hover {
background: #007bff;
}
.search-container {
margin-bottom: 20px;
}
.search-container input {
width: 300px;
padding: 8px;
border: 1px solid #ced4da;
border-radius: 5px;
}
.loader {
display: none;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 10px auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
vertical-align: middle;
}
</style>
<title><?= htmlspecialchars($template['name']) ?> - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/navbar.php'); ?>
<div class="dashboard-main-wrapper">
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?></h6>
<small>Template ID: <?= $id ?>, Start Row: <?= $template['header_row'] ?>, Start Column: <?= $template['start_column'] ?></small>
<div class="dashboard-body">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-4">Import Excel File</h4>
<!-- Upload Form -->
<form id="uploadForm" enctype="multipart/form-data" method="POST">
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label for="xlsFile" class="form-label">Excel file (.xls / .xlsx)</label>
<input type="file" name="xlsFile" id="xlsFile" accept=".xls,.xlsx" class="form-control" required>
</div>
<div class="col-md-2">
<label for="headerRow" class="form-label">Header row</label>
<input type="number" name="headerRow" id="headerRow" value="4" min="1" class="form-control" required>
</div>
<div class="col-md-3">
<label for="idclient" class="form-label">Client</label>
<select name="idclient" id="idclient" class="form-select select2" required>
<option value="">Select client...</option>
<?php foreach ($clients as $cl): ?>
<option value="<?= htmlspecialchars($cl['idclient']) ?>">
<?= htmlspecialchars($cl['client_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-upload"></i> Upload
</button>
</div>
</div>
</div>
<div class="card-body">
<!-- Form per caricare il file -->
<form id="uploadForm" enctype="multipart/form-data" class="mb-4">
<div class="mb-3">
<label for="excel_file" class="form-label">Upload XLS File</label>
<input type="file" class="form-control" id="excel_file" name="excel_file" accept=".xls,.xlsx" required>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
<div class="loader" id="loader"></div>
</form>
</form>
<!-- Contenitore per messaggi di errore -->
<div id="errorContainer" class="alert alert-danger mt-3" style="display: none;"></div>
<hr class="my-4">
<!-- Contenitore per la tabella -->
<div id="tableContainer"></div>
<!-- Filter + Table Section -->
<div id="tableSection" class="d-none">
<div class="mb-3">
<label class="form-label">Filter by Categories</label>
<select id="categorySelect" class="form-select select2" multiple></select>
</div>
<table id="dataTable" class="table table-striped table-bordered w-100">
<thead></thead>
<tbody></tbody>
</table>
<button id="saveData" class="btn btn-success mt-3">
<i class="fas fa-save"></i> Save to Database
</button>
</div>
</div>
</div>
</div>
<!--end page wrapper -->
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<!--end wrapper-->
<!-- search modal -->
<?php //include('include/searchmodal.php');
?>
<!-- end search modal -->
<!--start switcher-->
<?php //include('include/themeswitcher.php');
?>
<!--end switcher-->
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const form = document.getElementById('uploadForm');
const loader = document.getElementById('loader');
const errorContainer = document.getElementById('errorContainer');
const tableContainer = document.getElementById('tableContainer');
$(document).ready(function() {
form.addEventListener('submit', function(e) {
$('.select2').select2();
let fullData = [];
let table;
// === Upload Excel ===
$('#uploadForm').on('submit', function(e) {
e.preventDefault();
loader.style.display = 'block';
errorContainer.style.display = 'none';
tableContainer.innerHTML = '';
let formData = new FormData(this);
const formData = new FormData(this);
formData.append('template_id', <?= $id ?>);
formData.append('header_row', <?= $template['header_row'] ?>);
formData.append('start_column', <?= $template['start_column'] ?>);
fetch('process_import_xls.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
loader.style.display = 'none';
if (data.error) {
errorContainer.textContent = data.error;
errorContainer.style.display = 'block';
} else {
let html = `
<form id="selectRowsForm" action="import_edit.php" method="POST">
<input type="hidden" name="template_id" value="${data.template_id}">
<input type="hidden" name="columns" value='${JSON.stringify(data.columns)}'>
<input type="hidden" name="rows" value='${JSON.stringify(data.rows)}'>
<input type="hidden" name="filename" value="${data.filename}">
<div class="search-container">
<input type="text" id="searchInput" class="form-control" placeholder="Cerca nelle righe...">
</div>
<div class="table-container">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Seleziona</th>
${data.columns.map(col => `<th>${col || 'Colonna senza nome'}<div class="resize-handle"></div></th>`).join('')}
</tr>
</thead>
<tbody>
${data.rows.map((row, index) => `
<tr>
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}"></td>
${row.map(cell => `<td>${cell}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-primary mt-3" id="proceedButton" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html;
// Aggiungi logica per il ridimensionamento delle colonne
const thElements = document.querySelectorAll('.table th');
thElements.forEach((th, index) => {
if (index === 0) return; // Escludi la colonna "Seleziona" dal ridimensionamento
const resizeHandle = th.querySelector('.resize-handle');
if (resizeHandle) {
resizeHandle.addEventListener('mousedown', (e) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = th.offsetWidth;
const onMouseMove = (e) => {
const newWidth = Math.max(50, startWidth + (e.clientX - startX));
th.style.width = `${newWidth}px`;
th.style.minWidth = `${newWidth}px`;
th.style.maxWidth = `${newWidth}px`;
const cells = document.querySelectorAll(`.table td:nth-child(${index + 1})`);
cells.forEach(cell => {
cell.style.width = `${newWidth}px`;
cell.style.minWidth = `${newWidth}px`;
cell.style.maxWidth = `${newWidth}px`;
});
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
});
// Aggiungi event listener per la ricerca
const searchInput = document.getElementById('searchInput');
const rows = document.querySelectorAll('.table tbody tr');
const checkboxes = document.querySelectorAll('.row-checkbox');
const proceedButton = document.getElementById('proceedButton');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
rows.forEach(row => {
const text = Array.from(row.cells).slice(1).map(cell => cell.textContent.toLowerCase()).join(' ');
row.style.display = text.includes(searchTerm) ? '' : 'none';
});
});
// Aggiungi event listener per i checkbox
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
proceedButton.disabled = !Array.from(checkboxes).some(cb => cb.checked);
});
});
$.ajax({
url: 'import_xls_process.php',
type: 'POST',
data: formData,
processData: false,
contentType: false,
beforeSend: () => Swal.fire({
title: 'Processing...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
}),
success: function(res) {
Swal.close();
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', 'Invalid JSON:' + res, 'error');
return;
}
})
.catch(error => {
loader.style.display = 'none';
errorContainer.textContent = 'Errore durante il caricamento del file: ' + error.message;
errorContainer.style.display = 'block';
});
if (json.status === 'ok') {
fullData = json.data;
$('#tableSection').removeClass('d-none');
// Popola Select categorie
$('#categorySelect').empty();
json.categories.forEach(cat => {
$('#categorySelect').append(`<option value="${cat}">${cat}</option>`);
});
// Mostra tabella vuota inizialmente (nessuna categoria selezionata)
renderTable([], json.columns);
Swal.fire('Success', 'File uploaded successfully! Select categories to view data.', 'success');
} else {
Swal.fire('Error', json.message, 'error');
}
},
error: function() {
Swal.fire('Error', 'Upload failed.', 'error');
}
});
});
// === Filtro dinamico categorie ===
$('#categorySelect').on('change', function() {
const selected = $(this).val();
let filtered = [];
if (selected && selected.length) {
filtered = fullData.filter(r => selected.includes(r['category']));
}
renderTable(filtered, fullData.length ? Object.keys(fullData[0]) : []);
});
// === Funzione per DataTable ===
function renderTable(data, columns) {
if ($.fn.DataTable.isDataTable('#dataTable')) {
$('#dataTable').DataTable().destroy();
}
// Aggiungiamo una colonna "Select"
const cols = [{
title: '<input type="checkbox" id="selectAllRows" />',
data: null,
orderable: false,
searchable: false,
className: 'text-center',
render: function() {
return '<input type="checkbox" class="row-select" />';
}
},
...columns.map(c => ({
title: c.replace(/_/g, ' ').toUpperCase(),
data: c
}))
];
table = $('#dataTable').DataTable({
destroy: true,
data: data,
columns: cols,
pageLength: 25,
order: [],
createdRow: function(row, rowData) {
const selectedCats = $('#categorySelect').val() || [];
const cat = (rowData['category'] || '').trim();
if (selectedCats.includes(cat)) {
$(row).find('input.row-select').prop('checked', true);
}
$(row).find('input.row-select').data('row', rowData);
}
});
// --- Gestione "Select All" ---
$('#selectAllRows').off('click').on('click', function() {
const isChecked = this.checked;
$('#dataTable input.row-select').prop('checked', isChecked);
});
}
// === Salvataggio selezione ===
$('#saveData').on('click', function() {
const selectedRows = [];
$('#dataTable input.row-select:checked').each(function() {
const rowData = $(this).data('row');
if (rowData) selectedRows.push(rowData);
});
if (selectedRows.length === 0) {
Swal.fire('Notice', 'No rows selected for import.', 'info');
return;
}
$.ajax({
url: 'import_save.php',
type: 'POST',
data: {
rows: JSON.stringify(selectedRows),
idclient: $('#idclient').val(),
source_file: $('#xlsFile')[0].files[0]?.name || ''
},
success: function(res) {
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') Swal.fire('Success', 'Rows imported successfully!', 'success');
else Swal.fire('Error', json.message, 'error');
},
error: function() {
Swal.fire('Error', 'Failed to import data.', 'error');
}
});
});
});
</script>
</body>

View File

@ -1,413 +0,0 @@
<?php
include('include/headscript.php');
// Controlla se è stato passato un ID valido
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Invalid ID"));
exit;
}
$id = intval($_GET['id']); // Sanifica l'ID
// Recupera il template dal database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
header("Location: template_dashboard.php?status=error&message=" . urlencode("Template not found"));
exit;
}
// Verifica i mapping
$stmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
$stmt->execute([$id]);
$hasMappings = $stmt->fetch(PDO::FETCH_ASSOC);
// Debug del template
error_log("Loaded template: " . print_r($template, true));
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.table-container {
overflow-x: auto;
max-width: 100%;
margin-bottom: 20px;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 10px;
text-align: left;
border: 1px solid #dee2e6;
min-width: 100px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table th:first-child,
.table td:first-child {
min-width: 50px;
max-width: 50px;
}
.table th {
background-color: #f8f9fa;
position: relative;
cursor: col-resize;
user-select: none;
}
.table th .resize-handle {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
cursor: col-resize;
background: transparent;
}
.table th .resize-handle:hover {
background: #007bff;
}
.search-container {
margin-bottom: 20px;
}
.search-container input {
width: 300px;
padding: 8px;
border: 1px solid #ced4da;
border-radius: 5px;
}
.loader {
display: none;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 10px auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<title><?= htmlspecialchars($template['name']) ?> - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="mb-3 text">
<a href="historical_trf.php?id=<?= $id ?>&status=i" class="btn btn-warning me-2">Imported (i)</a>
<a href="historical_trf.php?id=<?= $id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a>
<a href="historical_trf.php?id=<?= $id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?></h6>
<small>Template ID: <?= $id ?>, Start Row: <?= $template['header_row'] ?>, Start Column: <?= $template['start_column'] ?></small>
</div>
</div>
</div>
<div class="card-body">
<?php if (!$hasMappings): ?>
<div class="alert alert-warning" role="alert">
Nessun mapping trovato per questo template. Configura i mapping prima di procedere.
</div>
<?php endif; ?>
<form id="uploadForm" enctype="multipart/form-data" class="mb-4">
<div class="mb-3">
<label for="excel_file" class="form-label">Upload XLS File</label>
<input type="file" class="form-control" id="excel_file" name="excel_file" accept=".xls,.xlsx" required>
</div>
<button type="submit" class="btn btn-primary" <?= !$hasMappings ? 'disabled' : '' ?>>Upload</button>
<div class="loader" id="loader"></div>
</form>
<div id="errorContainer" class="alert alert-danger mt-3" style="display: none;"></div>
<div id="tableContainer"></div>
<div class="modal fade" id="routineConfirmModal" tabindex="-1" aria-labelledby="routineConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="routineConfirmModalLabel">Conferma Applicazione Routine</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p><strong>Routine:</strong> <span id="routineName"></span></p>
<p><strong>Descrizione:</strong> <span id="routineDescription"></span></p>
<p>Vuoi applicare questa routine al file caricato?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="cancelRoutineBtn">Annulla</button>
<button type="button" class="btn btn-primary" id="confirmRoutineBtn">Applica</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
<?php include('jsinclude.php'); ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
const form = document.getElementById('uploadForm');
const loader = document.getElementById('loader');
const errorContainer = document.getElementById('errorContainer');
const tableContainer = document.getElementById('tableContainer');
const routineModal = new bootstrap.Modal(document.getElementById('routineConfirmModal'));
const confirmRoutineBtn = document.getElementById('confirmRoutineBtn');
const cancelRoutineBtn = document.getElementById('cancelRoutineBtn');
let routineData = null;
let excelData = null;
let responseData = null;
form.addEventListener('submit', function(e) {
e.preventDefault();
loader.style.display = 'block';
errorContainer.style.display = 'none';
tableContainer.innerHTML = '';
const formData = new FormData(this);
const templateId = <?= $id ?>;
console.log('Template ID passed to formData:', templateId);
formData.append('template_id', templateId);
formData.append('header_row', <?= $template['header_row'] ?>);
formData.append('start_column', <?= $template['start_column'] ?>);
fetch('process_import_xls2.php', {
method: 'POST',
body: formData
})
.then(response => {
console.log('Stato risposta:', response.status);
return response.json();
})
.then(data => {
console.log('Risposta JSON:', data);
loader.style.display = 'none';
if (data.error) {
errorContainer.textContent = data.error;
errorContainer.style.display = 'block';
} else if (data.apply_routine) {
console.log('Routine rilevata:', data.routine_data);
routineData = data.routine_data;
excelData = data.excel_data;
responseData = data;
document.getElementById('routineName').textContent = routineData.name || 'Sconosciuta';
document.getElementById('routineDescription').textContent = routineData.instruction || 'Nessuna descrizione';
routineModal.show();
} else {
console.log('Nessuna routine, procedo con tabella');
showTable(data);
}
})
.catch(error => {
console.log('Errore fetch:', error);
loader.style.display = 'none';
errorContainer.textContent = 'Errore durante il caricamento del file: ' + error.message;
errorContainer.style.display = 'block';
});
});
confirmRoutineBtn.addEventListener('click', function() {
console.log('Conferma routine:', routineData);
routineModal.hide();
fetch('apply_routine.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?= $id ?>,
filename: routineData.filename,
headerrow: routineData.headerrow,
excel_data: excelData,
routine_data: routineData
})
})
.then(response => {
console.log('Stato apply_routine:', response.status);
return response.json();
})
.then(data => {
console.log('Risposta apply_routine:', data);
if (data.error) {
errorContainer.textContent = data.error;
errorContainer.style.display = 'block';
} else {
showTable(data);
}
})
.catch(error => {
console.log('Errore apply_routine:', error);
errorContainer.textContent = 'Errore durante l\'applicazione della routine: ' + error.message;
errorContainer.style.display = 'block';
});
});
cancelRoutineBtn.addEventListener('click', function() {
console.log('Routine annullata, procedo con tabella');
routineModal.hide();
showTable(responseData);
});
function showTable(data) {
console.log('Mostro tabella con dati:', data);
let html = `
<form id="selectRowsForm" action="import_insert.php" method="POST">
<input type="hidden" name="template_id" value="${data.template_id}">
<input type="hidden" name="columns" value='${JSON.stringify(data.columns)}'>
<input type="hidden" name="rows" value='${JSON.stringify(data.rows)}'>
<input type="hidden" name="excelrows" value='${JSON.stringify(data.excel_data.map(row => row.excelrow))}'>
<input type="hidden" name="filename" value="${data.filename}">
<div class="search-container">
<input type="text" id="searchInput" class="form-control" placeholder="Cerca nelle righe...">
</div>
<div class="table-container">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"> Seleziona</th>
${data.columns.map(col => `<th>${col || 'Colonna senza nome'}<div class="resize-handle"></div></th>`).join('')}
</tr>
</thead>
<tbody>
${data.excel_data.map((row, index) => `
<tr>
<td><input type="checkbox" class="row-checkbox" name="selected_rows[]" value="${index}" data-excelrow="${row.excelrow}"></td>
${row.data.map(cell => `<td>${cell}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
<button type="submit" class="btn btn-primary mt-3" id="proceedButton" disabled>Prosegui</button>
</form>
`;
tableContainer.innerHTML = html;
const proceedButton = document.getElementById('proceedButton');
const selectAllCheckbox = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.row-checkbox');
function updateProceedButton() {
proceedButton.disabled = !Array.from(checkboxes).some(cb => cb.checked);
}
selectAllCheckbox.addEventListener('change', function() {
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateProceedButton();
});
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
console.log('Checkbox changed, checked:', this.checked, 'excelrow:', this.dataset.excelrow);
selectAllCheckbox.checked = Array.from(checkboxes).every(cb => cb.checked);
updateProceedButton();
});
});
const thElements = document.querySelectorAll('.table th');
thElements.forEach((th, index) => {
if (index === 0) return;
const resizeHandle = th.querySelector('.resize-handle');
if (resizeHandle) {
resizeHandle.addEventListener('mousedown', (e) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = th.offsetWidth;
const onMouseMove = (e) => {
const newWidth = Math.max(50, startWidth + (e.clientX - startX));
th.style.width = `${newWidth}px`;
th.style.minWidth = `${newWidth}px`;
th.style.maxWidth = `${newWidth}px`;
const cells = document.querySelectorAll(`.table td:nth-child(${index + 1})`);
cells.forEach(cell => {
cell.style.width = `${newWidth}px`;
cell.style.minWidth = `${newWidth}px`;
cell.style.maxWidth = `${newWidth}px`;
});
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
});
const searchInput = document.getElementById('searchInput');
const rows = document.querySelectorAll('.table tbody tr');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
rows.forEach(row => {
const text = Array.from(row.cells).slice(1).map(cell => cell.textContent.toLowerCase()).join(' ');
row.style.display = text.includes(searchTerm) ? '' : 'none';
});
});
updateProceedButton();
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,63 @@
<?php
include('include/headscript.php');
require __DIR__ . '/../../vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['xlsFile'])) {
$headerRow = intval($_POST['headerRow']) - 1;
$fileTmp = $_FILES['xlsFile']['tmp_name'];
if (!$fileTmp) {
echo json_encode(['status' => 'error', 'message' => 'No file uploaded.']);
exit;
}
try {
$spreadsheet = IOFactory::load($fileTmp);
$sheet = $spreadsheet->getActiveSheet();
$data = $sheet->toArray(null, true, true, true);
// Normalize headers
$headers = $data[$headerRow + 1];
$normalizedHeaders = [];
foreach ($headers as $k => $v) {
$key = strtolower(trim($v));
$key = str_replace([' ', '.', '-', '/'], '_', $key);
$normalizedHeaders[$k] = $key;
}
// Build data array
$rows = array_slice($data, $headerRow + 1);
$array = [];
foreach ($rows as $row) {
$item = [];
foreach ($normalizedHeaders as $k => $key) {
$item[$key] = trim($row[$k]);
}
$array[] = $item;
}
// Extract categories
$categories = [];
foreach ($array as $row) {
if (!empty($row['category'])) {
$cat = trim($row['category']);
if ($cat !== '' && !in_array($cat, $categories)) {
$categories[] = $cat;
}
}
}
sort($categories);
echo json_encode([
'status' => 'ok',
'columns' => array_keys($array[0] ?? []),
'categories' => $categories,
'data' => $array
]);
} catch (Exception $e) {
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
}

View File

@ -1,396 +0,0 @@
<?php include('include/headscript.php');
// Recupera tutte le routine dal database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT * FROM routine");
$stmt->execute();
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<?php include('cssinclude.php'); ?>
<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>
<title>Insert XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Insert new XLS Template</h5>
</div>
<div class="card-body">
<p class="mb-2">Fill the following form in order to create a new import XLS template</p>
<p class="mb-2">Mandatory Fields</p>
<ul class="mb-0">
<li>Template Name</li>
<li>Row Header and Column Header: where the title of the excel starts</li>
<li>Schema and client</li>
</ul>
</div>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Fill the form and click "Create Template"</h6>
</div>
</div>
</div>
<div class="card-body">
<div class="col-12">
<form id="insertTemplateForm" method="POST">
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="start_column" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"></textarea>
</div>
<input type="hidden" name="target_table" value="datadb">
<div class="mb-3">
<label class="form-label">Button Size</label>
<select name="button_size" class="form-control">
<option value="small">Small</option>
<option value="medium" selected>Medium</option>
<option value="large">Large</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control" value="#007bff">
</div>
<div class="mb-3">
<label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control" value="#ffffff">
</div>
<div class="mb-3">
<label class="form-label">Button Label</label>
<input type="text" name="button_label" class="form-control" value="Click Me">
</div>
<div class="mb-3">
<label class="form-label">Select Client *</label>
<select name="client_id" id="clientSelect" class="form-control" required>
<option value="">Select a client...</option>
</select>
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
</div>
<div class="mb-3">
<label class="form-label">Select Schema *</label>
<select name="schema_id" id="schemaSelect" class="form-control" required>
<option value="">Select a schema...</option>
</select>
<span id="schemaLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Caricamento schemi in corso...</span>
</div>
<div class="mb-3">
<label class="form-label">Select Routine</label>
<select name="idroutine" id="routineSelect" class="form-control">
<option value="">Select a routine...</option>
<?php foreach ($routines as $routine): ?>
<option value="<?php echo $routine['idroutine']; ?>">
<?php echo htmlspecialchars($routine['name']); ?>
</option>
<?php endforeach; ?>
</select>
<div id="routineDetails" class="mt-2" style="display: none;">
<h6>Routine Details</h6>
<p><strong>Name:</strong> <span id="routineName"></span></p>
<p><strong>Description:</strong> <span id="routineDescription"></span></p>
<p><strong>Action 1:</strong> <span id="routineAction1"></span></p>
<p><strong>Action 2:</strong> <span id="routineAction2"></span></p>
<p><strong>Action 3:</strong> <span id="routineAction3"></span></p>
</div>
</div>
<br>
<button type="submit" class="btn btn-primary">Create Template</button>
<a href="templates_dashboard.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof jQuery === 'undefined') {
alert("Errore: jQuery non è caricato. Contatta l'amministratore.");
return;
}
const form = document.getElementById("insertTemplateForm");
const clientLoadingStatus = document.getElementById("clientLoadingStatus");
const schemaLoadingStatus = document.getElementById("schemaLoadingStatus");
const routineSelect = document.getElementById("routineSelect");
const routineDetails = document.getElementById("routineDetails");
const routineName = document.getElementById("routineName");
const routineDescription = document.getElementById("routineDescription");
const routineAction1 = document.getElementById("routineAction1");
const routineAction2 = document.getElementById("routineAction2");
const routineAction3 = document.getElementById("routineAction3");
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
return;
}
$('#clientSelect').select2({
placeholder: "Search for a client...",
allowClear: true
});
$('#schemaSelect').select2({
placeholder: "Search for a schema...",
allowClear: true
});
$('#routineSelect').select2({
placeholder: "Select a routine...",
allowClear: true
});
async function loadClients() {
try {
clientLoadingStatus.style.display = 'inline';
clientLoadingStatus.textContent = 'Recupero clienti in corso...';
const response = await fetch("get_clienti.php", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
const select = document.getElementById("clientSelect");
select.innerHTML = '<option value="">Select a client...</option>';
data.value.forEach(client => {
const nome = client.Nominativo || "Nome non disponibile";
const id = client.IdCliente || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
select.add(option);
});
$(select).trigger('change');
clientLoadingStatus.textContent = "Clienti caricati.";
} catch (error) {
clientLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare i clienti: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
}
}
async function loadSchemas() {
try {
schemaLoadingStatus.style.display = 'inline';
schemaLoadingStatus.textContent = 'Caricamento schemi in corso...';
const response = await fetch("get_schemi.php", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || `Errore HTTP: ${response.status}`);
const select = document.getElementById("schemaSelect");
select.innerHTML = '<option value="">Select a schema...</option>';
data.value.forEach(schema => {
const nome = schema.Nome || "Nome non disponibile";
const id = schema.IdSchemaCustomFields || "ID non disponibile";
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
select.add(option);
});
$(select).trigger('change');
schemaLoadingStatus.textContent = "Schemi caricati.";
} catch (error) {
schemaLoadingStatus.textContent = "Errore nel caricamento.";
Swal.fire({
title: "Errore!",
text: "Impossibile caricare gli schemi: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
} finally {
setTimeout(() => schemaLoadingStatus.style.display = 'none', 2000);
}
}
async function loadData() {
try {
await loadClients();
await loadSchemas();
} catch (error) {
Swal.fire({
title: "Errore!",
text: "Errore nel caricamento dei dati: " + error.message,
icon: "error",
confirmButtonText: "OK"
});
}
}
loadData();
const routines = <?php echo json_encode($routines); ?>;
function updateRoutineDetails() {
const selectedId = routineSelect.value;
routineDetails.style.display = selectedId ? 'block' : 'none';
if (selectedId) {
const routine = routines.find(r => r.idroutine == selectedId);
if (routine) {
routineName.textContent = routine.name || 'N/A';
routineDescription.textContent = routine.description || 'N/A';
routineAction1.textContent = routine.action1 || 'N/A';
routineAction2.textContent = routine.action2 || 'N/A';
routineAction3.textContent = routine.action3 || 'N/A';
} else {
routineName.textContent = 'N/A';
routineDescription.textContent = 'N/A';
routineAction1.textContent = 'N/A';
routineAction2.textContent = 'N/A';
routineAction3.textContent = 'N/A';
}
} else {
routineName.textContent = '';
routineDescription.textContent = '';
routineAction1.textContent = '';
routineAction2.textContent = '';
routineAction3.textContent = '';
}
}
routineSelect.addEventListener('change', updateRoutineDetails);
updateRoutineDetails();
form.addEventListener("submit", function(e) {
e.preventDefault();
let formData = new FormData(this);
const clientSelect = document.getElementById("clientSelect");
const clientId = clientSelect.value;
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
if (!clientId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona un cliente.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
let clientName = "";
if (selectedClientOption) {
const optionText = selectedClientOption.text.trim();
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
clientName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("client_name", clientName);
const schemaSelect = document.getElementById("schemaSelect");
const schemaId = schemaSelect.value;
const selectedSchemaOption = schemaSelect.options[schemaSelect.selectedIndex];
if (!schemaId) {
Swal.fire({
title: "Errore!",
text: "Per favore seleziona uno schema.",
icon: "error",
confirmButtonText: "OK"
});
return;
}
let schemaName = "";
if (selectedSchemaOption) {
const optionText = selectedSchemaOption.text.trim();
const nameMatch = optionText.match(/^(.+?)(?:\s*\(ID:\s*\d+\))?$/);
schemaName = nameMatch ? nameMatch[1].trim() : optionText;
}
formData.append("idschema", schemaId);
formData.append("schemaname", schemaName);
const routineId = routineSelect.value;
formData.append("idroutine", routineId);
fetch("process_insert_template_xls.php", {
method: "POST",
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
title: "Successo!",
text: "Template creato con successo!",
icon: "success",
confirmButtonText: "OK"
}).then(() => {
window.location.href = "templates_dashboard.php";
});
} else {
Swal.fire({
title: "Errore!",
text: data.message,
icon: "error",
confirmButtonText: "OK"
});
}
})
.catch(error => {
Swal.fire({
title: "Errore!",
text: "Si è verificato un errore imprevisto.",
icon: "error",
confirmButtonText: "OK"
});
});
});
});
</script>
</body>
</html>

View File

@ -1,411 +0,0 @@
<?php include('include/headscript.php'); ?>
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--favicon-->
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Insert XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<!--wrapper-->
<div class="wrapper">
<!--sidebar wrapper -->
<?php include('include/navbar.php'); ?>
<!--end sidebar wrapper -->
<!--start header -->
<?php include('include/topbar.php'); ?>
<!--end header -->
<!--start page wrapper -->
<div class="page-wrapper">
<div class="page-content">
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
<div class="col">
<div class="card radius-10 border-start border-0 border-4 border-info">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0 text-secondary">Total Orders</p>
<h4 class="my-1 text-info">4805</h4>
<p class="mb-0 font-13">+2.5% from last week</p>
</div>
<div class="widgets-icons-2 rounded-circle bg-gradient-blues text-white ms-auto"><i class='bx bxs-cart'></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 border-start border-0 border-4 border-danger">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0 text-secondary">Total Revenue</p>
<h4 class="my-1 text-danger">$84,245</h4>
<p class="mb-0 font-13">+5.4% from last week</p>
</div>
<div class="widgets-icons-2 rounded-circle bg-gradient-burning text-white ms-auto"><i class='bx bxs-wallet'></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 border-start border-0 border-4 border-success">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0 text-secondary">Bounce Rate</p>
<h4 class="my-1 text-success">34.6%</h4>
<p class="mb-0 font-13">-4.5% from last week</p>
</div>
<div class="widgets-icons-2 rounded-circle bg-gradient-ohhappiness text-white ms-auto"><i class='bx bxs-bar-chart-alt-2'></i>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card radius-10 border-start border-0 border-4 border-warning">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0 text-secondary">Total Customers</p>
<h4 class="my-1 text-warning">8.4K</h4>
<p class="mb-0 font-13">+8.4% from last week</p>
</div>
<div class="widgets-icons-2 rounded-circle bg-gradient-orange text-white ms-auto"><i class='bx bxs-group'></i>
</div>
</div>
</div>
</div>
</div>
</div><!--end row-->
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Insert New XLS Template</h6>
</div>
</div>
</div>
<div class="card-body">
<div class="col-12">
<form id="insertTemplateForm" method="POST">
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($templatename, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($rowheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="number" name="header_row" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($columnheader, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="start_column" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
<textarea name="description" class="form-control"></textarea>
</div>
<div class="mb-3">
<label class="form-label"><?= htmlspecialchars($desttable, ENT_QUOTES, 'UTF-8'); ?> *</label>
<input type="text" name="target_table" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Button Size</label>
<select name="button_size" class="form-control">
<option value="small">Small</option>
<option value="medium" selected>Medium</option>
<option value="large">Large</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Button Background Color</label>
<input type="color" name="button_bg_color" class="form-control" value="#007bff">
</div>
<div class="mb-3">
<label class="form-label">Button Text Color</label>
<input type="color" name="button_text_color" class="form-control" value="#ffffff">
</div>
<div class="mb-3">
<label class="form-label">Button Label</label>
<input type="text" name="button_label" class="form-control" value="Click Me">
</div>
<!-- Nuova sezione per i campi specifici del cliente -->
<div class="mb-3">
<label class="form-label">Client-Specific Fields</label>
<div id="clientSpecificFields">
<!-- Contenitore per i campi dinamici -->
<div class="client-field-row mb-2">
<div class="row">
<div class="col-md-3">
<input type="text" name="specific_fields[0][name]" class="form-control" placeholder="Field Name (e.g., SKU)">
</div>
<div class="col-md-2">
<select name="specific_fields[0][type]" class="form-control">
<option value="text">Text</option>
<option value="dropdown">Dropdown</option>
<option value="date">Date</option>
<option value="boolean">Yes/No</option>
</select>
</div>
<div class="col-md-2 d-none dropdown-values" style="display: none;">
<input type="text" name="specific_fields[0][possible_values]" class="form-control" placeholder="Values (e.g., Red, Blue, Green)">
</div>
<div class="col-md-1">
<select name="specific_fields[0][required]" class="form-control">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-2">
<input type="text" name="specific_fields[0][export_column_name]" class="form-control" placeholder="Export Column Name (e.g., MONCLER_SKU)">
</div>
<div class="col-md-1">
<input type="text" name="specific_fields[0][default_value]" class="form-control" placeholder="Default Value (optional)">
</div>
<div class="col-md-1">
<button type="button" class="btn btn-danger remove-field">-</button>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-primary mt-2" id="addField">Add Field</button>
</div>
<br>
<button type="submit" class="btn btn-primary">Create Template</button>
<a href="templeates_dashboard.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</div>
</div>
<!--end page wrapper -->
<!--start overlay-->
<div class="overlay toggle-icon"></div>
<!--end overlay-->
<!--Start Back To Top Button-->
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<!--End Back To Top Button-->
<?php include('include/footer.php'); ?>
</div>
<!--end wrapper-->
<!-- search modal -->
<?php //include('include/searchmodal.php');
?>
<!-- end search modal -->
<!--start switcher-->
<?php //include('include/themeswitcher.php');
?>
<!--end switcher-->
<?php include('jsinclude.php'); ?>
<script>
// Debug iniziale
console.log("JavaScript is loaded and running!");
document.addEventListener("DOMContentLoaded", function() {
console.log("DOM is loaded");
const form = document.getElementById("insertTemplateForm");
const addFieldButton = document.getElementById("addField");
const container = document.getElementById("clientSpecificFields");
if (!form || !addFieldButton || !container) {
console.error("One or more DOM elements not found:", {
form,
addFieldButton,
container
});
return;
}
console.log("All DOM elements found");
// Gestione dinamica dei campi specifici
addFieldButton.addEventListener("click", function() {
console.log("Add Field button clicked");
const fieldCount = container.getElementsByClassName("client-field-row").length;
const newField = document.createElement("div");
newField.className = "client-field-row mb-2";
newField.innerHTML = `
<div class="row">
<div class="col-md-3">
<input type="text" name="specific_fields[${fieldCount}][name]" class="form-control" placeholder="Field Name (e.g., SKU)">
</div>
<div class="col-md-2">
<select name="specific_fields[${fieldCount}][type]" class="form-control" onchange="toggleDropdownValues(this)">
<option value="text">Text</option>
<option value="dropdown">Dropdown</option>
<option value="date">Date</option>
<option value="boolean">Yes/No</option>
</select>
</div>
<div class="col-md-2 d-none dropdown-values" style="display: none;">
<input type="text" name="specific_fields[${fieldCount}][possible_values]" class="form-control" placeholder="Values (e.g., Red, Blue, Green)">
</div>
<div class="col-md-1">
<select name="specific_fields[${fieldCount}][required]" class="form-control">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-2">
<input type="text" name="specific_fields[${fieldCount}][export_column_name]" class="form-control" placeholder="Export Column Name (e.g., MONCLER_SKU)">
</div>
<div class="col-md-1">
<input type="text" name="specific_fields[${fieldCount}][default_value]" class="form-control" placeholder="Default Value (optional)">
</div>
<div class="col-md-1">
<button type="button" class="btn btn-danger remove-field">-</button>
</div>
</div>
`;
container.appendChild(newField);
newField.querySelector(".remove-field").addEventListener("click", function() {
console.log("Remove Field button clicked");
container.removeChild(newField);
updateFieldIndices();
});
});
window.toggleDropdownValues = function(selectElement) {
console.log("Toggling dropdown values for:", selectElement.value);
const row = selectElement.closest(".row");
const dropdownValues = row.querySelector(".dropdown-values");
if (selectElement.value === "dropdown") {
dropdownValues.style.display = "block";
} else {
dropdownValues.style.display = "none";
}
};
document.querySelectorAll(".remove-field").forEach(button => {
button.addEventListener("click", function() {
console.log("Existing remove button clicked");
const container = document.getElementById("clientSpecificFields");
container.removeChild(button.closest(".client-field-row"));
updateFieldIndices();
});
});
function updateFieldIndices() {
console.log("Updating field indices");
const rows = container.getElementsByClassName("client-field-row");
for (let i = 0; i < rows.length; i++) {
const inputs = rows[i].querySelectorAll("input, select");
inputs.forEach(input => {
const name = input.name.replace(/\[\d+\]/, `[${i}]`);
input.name = name;
});
}
}
form.addEventListener("submit", function(e) {
e.preventDefault();
console.log("Form submitted");
let formData = new FormData(this);
// Genera il JSON per client_specific_fields
let finalSpecificFields = {};
// Raccolta dei dati direttamente dal DOM
const fieldRows = container.getElementsByClassName("client-field-row");
for (let i = 0; i < fieldRows.length; i++) {
const row = fieldRows[i];
const inputs = row.querySelectorAll("input, select");
let fieldData = {};
inputs.forEach(input => {
const nameMatch = input.name.match(/specific_fields\[\d+\]\[(.*?)\]/);
if (nameMatch) {
const fieldName = nameMatch[1];
fieldData[fieldName] = input.value.trim();
}
});
if (fieldData.name) {
finalSpecificFields[fieldData.name] = {
type: fieldData.type || "text",
possible_values: (fieldData.possible_values && fieldData.type === "dropdown") ? fieldData.possible_values.split(",").map(v => v.trim()) : [],
is_required: fieldData.required === "1",
export_column_name: fieldData.export_column_name || "",
default_value: fieldData.default_value || ""
};
console.log(`Field ${fieldData.name}:`, finalSpecificFields[fieldData.name]);
}
}
console.log("Generated JSON for client_specific_fields:", JSON.stringify(finalSpecificFields));
// Aggiungi il JSON al FormData
formData.append("client_specific_fields", JSON.stringify(finalSpecificFields));
// Debug del FormData
console.log("FormData contents:");
for (let pair of formData.entries()) {
console.log(pair[0] + ': ' + pair[1]);
}
fetch("process_insert_template_xls.php", {
method: "POST",
body: formData
})
.then(response => response.json())
.then(data => {
console.log("Fetch response:", data);
if (data.success) {
Swal.fire({
title: "Success!",
text: "Template created successfully!",
icon: "success",
confirmButtonText: "OK"
}).then(() => {
window.location.href = "templates_dashboard.php";
});
} else {
Swal.fire({
title: "Error!",
text: data.message,
icon: "error",
confirmButtonText: "OK"
});
}
})
.catch(error => {
console.error("Fetch error:", error);
Swal.fire({
title: "Error!",
text: "An unexpected error occurred.",
icon: "error",
confirmButtonText: "OK"
});
});
});
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
<?php
header('Content-Type: application/json');
require_once 'class/db-functions.php';
$response = ["success" => false, "data" => [], "message" => ""];
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if (!$pdo) {
throw new Exception('Database connection failed.');
}
// 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'");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
$response["success"] = true;
$response["data"] = $templates;
} catch (PDOException $e) {
$response["message"] = "Database error: " . $e->getMessage();
} catch (Exception $e) {
$response["message"] = "Error: " . $e->getMessage();
}
echo json_encode($response);

View File

@ -1,60 +0,0 @@
<?php
require_once(__DIR__ . '/class/db-functions.php');
header('Content-Type: application/json');
if (!isset($_GET['template_id']) || !is_numeric($_GET['template_id'])) {
echo json_encode(["success" => false, "message" => "Invalid template ID"]);
exit;
}
$template_id = intval($_GET['template_id']);
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// 1⃣ Recuperiamo il nome della tabella target da `excel_templates`
$stmt = $pdo->prepare("SELECT target_table FROM excel_templates WHERE id = ?");
$stmt->execute([$template_id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template || empty($template['target_table'])) {
echo json_encode(["success" => false, "message" => "Template not found or missing target table"]);
exit;
}
$target_table = $template['target_table'];
// 2⃣ Recuperiamo le associazioni già esistenti per il template_id
$stmt = $pdo->prepare("SELECT excel_column, mysql_column, headerexcel FROM excel_column_mappings WHERE template_id = ?");
$stmt->execute([$template_id]);
$existing_mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Creiamo gli array delle colonne già mappate
$mapped_xls_columns = array_column($existing_mappings, 'excel_column');
$mapped_mysql_columns = array_column($existing_mappings, 'mysql_column'); // CORRETTO PER FILTRARE!
// 3⃣ Recuperiamo tutte le colonne disponibili nella tabella MySQL target
$stmt = $pdo->prepare("SHOW COLUMNS FROM `$target_table`");
$stmt->execute();
$table_columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
// 🔥 FIX: Rimuoviamo le colonne MySQL che sono già state mappate!
$remaining_mysql_columns = array_values(array_diff($table_columns, $mapped_mysql_columns));
// 4⃣ Se abbiamo salvato gli header XLSX in `headerexcel`, li usiamo per calcolare le colonne XLSX non mappate
$headerexcel = !empty($existing_mappings) ? $existing_mappings[0]['headerexcel'] : '';
$all_xls_columns = !empty($headerexcel) ? explode(',', $headerexcel) : [];
$remaining_xls_columns = array_values(array_diff($all_xls_columns, $mapped_xls_columns));
// 5⃣ Invio dei dati al frontend
echo json_encode([
"success" => true,
"mappings" => $existing_mappings,
"remaining_xls_columns" => $remaining_xls_columns,
"remaining_mysql_columns" => $remaining_mysql_columns // 🔥 ORA LE COLONNE MYSQL SONO FILTRATE!
]);
} catch (PDOException $e) {
echo json_encode(["success" => false, "message" => "Database error: " . $e->getMessage()]);
}

View File

@ -1,23 +0,0 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$template_id = $_GET['template_id'] ?? 0;
try {
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, field_label FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(["success" => true, "mappings" => $mappings]);
} catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
}
exit;

View File

@ -1,69 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
// Parametri dalla richiesta AJAX
$template_id = intval($_GET['template_id'] ?? 0);
$status = $_GET['status'] ?? 'i';
$offset = intval($_GET['offset'] ?? 0);
$limit = intval($_GET['limit'] ?? 20);
if (!$template_id || !in_array($status, ['i', 'P', 'l'])) {
error_log("Errore in load_more_rows.php: Parametri non validi - template_id: $template_id, status: $status");
echo json_encode(['success' => false, 'message' => 'Parametri non validi']);
exit;
}
// Connessione al database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Recupera i dati
try {
$stmt = $pdo->prepare("
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
FROM datadb d
LEFT JOIN auth_users u ON d.user_id = u.id
WHERE d.templateid = ? AND d.status = ?
LIMIT ? OFFSET ?
");
$stmt->execute([$template_id, $status, $limit, $offset]);
$importedData = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Recupera i dettagli manuali
$insertedIds = array_column($importedData, 'iddatadb');
$manualDetails = [];
if (!empty($insertedIds)) {
$placeholders = implode(',', array_fill(0, count($insertedIds), '?'));
$stmt = $pdo->prepare("
SELECT d.id AS detail_id, d.id AS datadb_id, d.mapping_id, d.field_value, m.field_label, m.data_type, m.is_required, m.manual_default
FROM import_data_details d
JOIN template_mapping m ON d.mapping_id = m.id
WHERE d.id IN ($placeholders)
");
$stmt->execute($insertedIds);
$manualDetails = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Prepara i dati per il JSON
$rows = [];
foreach ($importedData as $row) {
$rowData = [
'iddatadb' => $row['iddatadb'] ?? '',
'importreferencecode' => $row['importreferencecode'] ?? '',
'filename_import' => $row['filename_import'] ?? '',
'status' => $row['status'] ?? '',
'importdate' => $row['importdate'] ?? '',
'details' => array_filter($manualDetails, fn($d) => $d['datadb_id'] == $row['iddatadb'])
];
$rows[] = $rowData;
}
error_log("load_more_rows.php: Caricate " . count($rows) . " righe per template_id=$template_id, status=$status, offset=$offset");
echo json_encode(['success' => true, 'rows' => $rows]);
} catch (Exception $e) {
error_log("Errore in load_more_rows.php: " . $e->getMessage());
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}
exit;

View File

@ -1,24 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$iddatadb = $_GET['iddatadb'] ?? null;
if (!$iddatadb) {
echo json_encode(['success' => false, 'message' => 'ID TRF mancante']);
exit;
}
try {
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
$stmt->execute([':iddatadb' => $iddatadb]);
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'parts' => $parts]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}

View File

@ -1,23 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$idquotations = $_GET['idquotations'] ?? null;
if (!$idquotations) {
echo json_encode(['success' => false, 'message' => 'ID quotations mancante']);
exit;
}
try {
$stmt = $pdo->prepare("SELECT id, idquotations, part_number, part_description FROM identification_parts WHERE idquotations = :idquotations ORDER BY part_number ASC");
$stmt->execute([':idquotations' => $idquotations]);
$parts = $stmt->fetchAll();
echo json_encode(['success' => true, 'parts' => $parts]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}

View File

@ -1,34 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$iddatadb = $_GET['iddatadb'] ?? null;
if (!$iddatadb) {
echo json_encode(['success' => false, 'message' => 'ID TRF mancante']);
exit;
}
try {
// Adjust the query to select all photo paths for the given iddatadb
$stmt = $pdo->prepare("SELECT file_path FROM datadb_photos WHERE iddatadb = :iddatadb");
$stmt->execute([':iddatadb' => $iddatadb]);
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($photos && count($photos) > 0) {
// Prepare an array of full file paths
$photoPaths = array_map(function($photo) {
return '../photostrf/' . $photo['file_path']; // Assuming the photos are stored in the "photostrf" folder
}, $photos);
echo json_encode(['success' => true, 'photos' => $photoPaths]); // Return an array of photo paths
} else {
echo json_encode(['success' => false, 'message' => 'Nessuna foto trovata']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}

View File

@ -1,33 +0,0 @@
<?php
// load_photo_quotation.php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$idquotations = isset($_GET['idquotations']) ? intval($_GET['idquotations']) : null;
if (!$idquotations) {
echo json_encode(['success' => false, 'message' => 'ID quotation mancante']);
exit;
}
try {
// Seleziona le foto per il dato idquotations dalla tabella datadb_photos
$stmt = $pdo->prepare("SELECT id, file_path FROM datadb_photos WHERE idquotations = ?");
$stmt->execute([$idquotations]);
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($photos && count($photos) > 0) {
$photoPaths = array_map(function ($photo) {
return '../photostrf/' . $photo['file_path'];
}, $photos);
echo json_encode(['success' => true, 'photos' => $photoPaths]);
} else {
echo json_encode(['success' => false, 'message' => 'Nessuna foto trovata']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento: ' . $e->getMessage()]);
}

View File

@ -1,32 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Recupera l'ID dell'utente loggato
$user_id = $iduserlogin ?? 1;
if (!$user_id) {
echo json_encode(['success' => false, 'message' => "ID dell'utente autenticato mancante"]);
exit;
}
try {
$stmt = $pdo->prepare(
"SELECT DISTINCT q.*
FROM quotations q
INNER JOIN identification_parts ip
ON ip.idquotations = q.id
AND ip.iddatadb IS NULL
WHERE q.iduser = :iduser"
);
$stmt->execute([':iduser' => $user_id]);
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'quotations' => $quotations]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Errore nel caricamento delle quotations: ' . $e->getMessage()]);
}

View File

@ -1,35 +0,0 @@
<?php
header('Content-Type: application/json');
require_once 'class/db-functions.php';
$response = ["success" => false, "columns" => []];
try {
if (!isset($_GET['table']) || empty($_GET['table'])) {
throw new Exception("Invalid table name.");
}
$table = preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['table']); // Sanitize table name
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Retrieve table columns
$stmt = $pdo->query("SHOW COLUMNS FROM `$table`");
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
if (!$columns) {
throw new Exception("No columns found in table.");
}
$response["success"] = true;
$response["columns"] = $columns;
} catch (Exception $e) {
$response["message"] = $e->getMessage();
}
// Log per debugging
error_log(json_encode($response));
// Restituisce la risposta JSON
echo json_encode($response);

View File

@ -1,39 +0,0 @@
<?php
// Disabilita l'output automatico degli errori PHP
ini_set('display_errors', 1);
error_reporting(E_ALL);
// Assicurati che non ci sia output prima del header
ob_start();
ob_clean();
header('Content-Type: application/json');
// Connessione al database usando DBHandlerSelect
try {
// Usa un percorso relativo corretto
require_once 'class/db-functions.php';
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
if (!$pdo) {
throw new Exception('Connessione al database non valida');
}
// Query per selezionare i template dalla tabella excel_templates
$stmt = $pdo->query("SELECT * FROM excel_templates ORDER BY created_at DESC");
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $templates]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Errore PDO: ' . $e->getMessage()]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Errore interno: ' . $e->getMessage()]);
} finally {
ob_end_flush();
}
error_log('Errore in load_templates.php: ' . (isset($e) ? $e->getMessage() : 'Nessun errore catturato'));

View File

@ -1,448 +0,0 @@
<?php include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die("Template not found");
}
?>
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--favicon-->
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Mapping XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
<!--wrapper-->
<div class="wrapper">
<!--sidebar wrapper -->
<?php include('include/navbar.php'); ?>
<!--end sidebar wrapper -->
<!--start header -->
<?php include('include/topbar.php'); ?>
<!--end header -->
<!--start page wrapper -->
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Associate Columns - Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></h6>
<p>Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> | Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span></p>
</div>
</div>
</div>
<div class="card-body">
<!-- Upload Section -->
<div class="mb-4">
<label class="form-label">Upload XLS Example:</label>
<input type="file" id="xlsUpload" class="form-control">
<small id="uploadStatus" class="text-muted">
<?php if (!empty($template['sample_xlsx'])): ?>
Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank">
<?php echo htmlspecialchars($template['sample_xlsx']); ?>
</a>
<?php else: ?>
No file uploaded yet.
<?php endif; ?>
</small>
</div>
<!-- Association Section -->
<div class="row">
<div class="col-md-5">
<h5>XLS Column</h5>
<ul id="xlsColumns" class="list-group border p-3" style="height: 300px; overflow-y: auto;"></ul>
</div>
<div class="col-md-2 text-center d-flex align-items-center justify-content-center">
<button class="btn btn-dark" id="addAssociation"> Add</button>
</div>
<div class="col-md-5">
<h5>Table Column</h5>
<ul id="tableColumns" class="list-group border p-3" style="height: 300px; overflow-y: auto;"></ul>
</div>
</div>
<!-- Associations List -->
<div class="mt-4">
<h5>Current Associations</h5>
<ul id="associationsList" class="list-group border p-3"></ul>
</div>
<!-- Save Button -->
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<!--end page wrapper -->
<!--start overlay-->
<div class="overlay toggle-icon"></div>
<!--end overlay-->
<!--Start Back To Top Button-->
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<!--End Back To Top Button-->
<?php include('include/footer.php'); ?>
</div>
<!--end wrapper-->
<!-- search modal -->
<?php //include('include/searchmodal.php');
?>
<!-- end search modal -->
<!--start switcher-->
<?php //include('include/themeswitcher.php');
?>
<!--end switcher-->
<?php include('jsinclude.php'); ?>
<script>
document.getElementById('xlsUpload').addEventListener('change', function(event) {
let file = event.target.files[0];
if (!file) {
console.error("❌ No file selected");
return;
}
console.log("📂 File selected:", file.name);
// 🔹 UPLOAD DEL FILE PRIMA DI PROCESSARE I DATI
let formData = new FormData();
formData.append("xls_file", file);
formData.append("template_id", <?php echo $id; ?>);
let statusText = document.getElementById('uploadStatus');
statusText.innerText = "Uploading...";
fetch('upload_xls_example.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error uploading file:", data.message);
statusText.innerText = "❌ Upload failed: " + data.message;
return;
}
console.log("✅ File uploaded successfully:", data.filepath);
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
// 🔹 UNA VOLTA CARICATO, ESTRAIAMO LE COLONNE
processXLSX(file);
})
.catch(error => {
console.error("❌ Fetch error:", error);
statusText.innerText = "❌ Upload failed. Check console.";
});
});
// 🔹 FUNZIONE PER PROCESSARE IL FILE ESTRARRE LE COLONNE
function processXLSX(file) {
let reader = new FileReader();
reader.onload = function(e) {
let data = new Uint8Array(e.target.result);
let workbook = XLSX.read(data, {
type: 'array'
});
let sheet = workbook.Sheets[workbook.SheetNames[0]];
console.log("📄 Sheet found:", workbook.SheetNames[0]);
// Recupera "Header Row" e "Start Column" dal template
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1;
console.log("📌 Using Header Row:", rowIndex, "Start Column:", startColumn);
// Legge il foglio SENZA eliminare righe vuote
let sheetData = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: "",
raw: false,
range: 0
});
console.log("📊 Sheet Data:", sheetData);
// Verifica che la riga richiesta esista
if (!sheetData[rowIndex - 1]) {
console.warn("⚠️ No headers found in the specified row:", rowIndex);
document.getElementById('xlsColumns').innerHTML = "<li class='list-group-item text-danger'>No headers found</li>";
return;
}
// Prende la riga esatta specificata nel template
let headers = sheetData[rowIndex - 1] || [];
console.log("🔎 Raw Headers:", headers);
// Se la riga specificata è vuota, avvisa l'utente
if (!headers.length || headers.every(h => h.trim() === "")) {
console.warn("⚠️ Header row is empty at row:", rowIndex);
document.getElementById('xlsColumns').innerHTML = "<li class='list-group-item text-warning'>No headers found in the specified row</li>";
return;
}
// Rimuove le colonne prima di "Start Column"
let adjustedHeaders = headers.slice(startColumn - 1).filter(header => header !== undefined && header.trim() !== "");
console.log("✅ Extracted Headers:", adjustedHeaders);
// Popola la lista delle colonne XLSX
let xlsColumns = document.getElementById('xlsColumns');
xlsColumns.innerHTML = '';
adjustedHeaders.forEach(header => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = header;
xlsColumns.appendChild(li);
});
};
reader.readAsArrayBuffer(file);
}
document.addEventListener("DOMContentLoaded", function() {
let selectedXLSColumn = null;
let selectedTableColumn = null;
let templateId = <?php echo $id; ?>;
/** =======================
* CARICAMENTO COLONNE MYSQL DAL DATABASE
* ======================= */
/* fetch('load_table_columns.php?table=<?php echo urlencode($template['target_table']); ?>')
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("Error:", data.message);
return;
}
let tableColumns = document.getElementById('tableColumns');
tableColumns.innerHTML = '';
if (Array.isArray(data.columns)) {
data.columns.forEach(column => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = column;
tableColumns.appendChild(li);
});
} else {
console.error("Unexpected data format:", data);
}
})
.catch(error => console.error('Error loading table columns:', error));
*/
/** =======================
* CARICAMENTO MAPPATURE GIÀ ESISTENTI
* ======================= */
function loadMappings() {
fetch('load_existing_mappings.php?template_id=' + templateId)
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error loading mappings:", data.message);
return;
}
let associationsList = document.getElementById('associationsList');
let xlsColumnsList = document.getElementById('xlsColumns');
let tableColumnsList = document.getElementById('tableColumns');
// 🔥 FIX: Pulisce sempre la lista prima di aggiornare
associationsList.innerHTML = '';
xlsColumnsList.innerHTML = '';
tableColumnsList.innerHTML = '';
// Carica le associazioni esistenti
data.mappings.forEach(mapping => {
let li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.innerHTML = `
${mapping.excel_column} ${mapping.mysql_column}
<button class="btn btn-danger btn-sm removeAssociation" data-excel="${mapping.excel_column}" data-mysql="${mapping.mysql_column}">X</button>
`;
associationsList.appendChild(li);
});
// 🔥 FIX: Ora carica SOLO le colonne MySQL rimaste disponibili!
if (Array.isArray(data.remaining_mysql_columns)) {
data.remaining_mysql_columns.forEach(column => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = column;
tableColumnsList.appendChild(li);
});
}
// Carica le colonne XLSX rimanenti
if (Array.isArray(data.remaining_xls_columns)) {
data.remaining_xls_columns.forEach(header => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = header;
xlsColumnsList.appendChild(li);
});
}
console.log("✅ Loaded existing mappings and remaining columns");
})
.catch(error => console.error("❌ Error fetching mappings:", error));
}
// 🔥 Chiama il caricamento iniziale
loadMappings();
/** =======================
* SELEZIONE COLONNE XLS & MYSQL
* ======================= */
document.getElementById('xlsColumns').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
document.querySelectorAll('#xlsColumns li').forEach(el => el.classList.remove('active'));
event.target.classList.add('active');
selectedXLSColumn = event.target.textContent;
console.log("📌 Selected XLS Column:", selectedXLSColumn);
}
});
document.getElementById('tableColumns').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
document.querySelectorAll('#tableColumns li').forEach(el => el.classList.remove('active'));
event.target.classList.add('active');
selectedTableColumn = event.target.textContent;
console.log("📌 Selected MySQL Column:", selectedTableColumn);
}
});
/** =======================
* AGGIUNTA ASSOCIAZIONE
* ======================= */
document.getElementById('addAssociation').addEventListener('click', function() {
if (!selectedXLSColumn || !selectedTableColumn) {
alert("Please select both an XLS and MySQL column.");
return;
}
// Salva l'associazione nel database
saveAssociation(selectedXLSColumn, selectedTableColumn);
// Reset selezioni
selectedXLSColumn = null;
selectedTableColumn = null;
});
function saveAssociation(excelColumn, mysqlColumn) {
let allHeaders = [...document.querySelectorAll('#xlsColumns li')].map(el => el.textContent).join(",");
fetch('save_column_mapping.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
tablename: "<?php echo $template['target_table']; ?>",
excel_column: excelColumn,
mysql_column: mysqlColumn,
data_type: "VARCHAR",
is_required: 0,
default_value: "",
headerexcel: allHeaders
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error saving mapping:", data.message);
alert("Error saving mapping: " + data.message);
} else {
console.log("✅ Association saved:", data);
loadMappings(); // Ricarica le colonne dopo il salvataggio
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
/** =======================
* RIMOZIONE ASSOCIAZIONE
* ======================= */
document.getElementById('associationsList').addEventListener('click', function(event) {
if (event.target.classList.contains('removeAssociation')) {
let excelColumn = event.target.getAttribute("data-excel");
let mysqlColumn = event.target.getAttribute("data-mysql");
removeAssociation(excelColumn, mysqlColumn);
}
});
function removeAssociation(excelColumn, mysqlColumn) {
fetch('remove_column_mapping.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
excel_column: excelColumn,
mysql_column: mysqlColumn,
tablename: "<?php echo $template['target_table']; ?>"
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error removing mapping:", data.message);
alert("Error removing mapping: " + data.message);
} else {
console.log("🗑️ Association removed:", data);
loadMappings(); // Ricarica la lista dopo la rimozione
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
});
</script>
</body>
</html>

View File

@ -1,614 +0,0 @@
<?php include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx, idclient, clientname, idschema, schemaname, schemajson FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die("Template not found");
}
// Opzionale: se clientname o schemaname non sono disponibili, potresti volerli recuperare dinamicamente
$clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ?: ''; // Recupera il valore di schemajson
$isSchemajsonEmpty = empty(trim($template['schemajson']));
?>
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--favicon-->
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Mapping XLS Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
<!--wrapper-->
<div class="wrapper">
<!--sidebar wrapper -->
<?php include('include/navbar.php'); ?>
<!--end sidebar wrapper -->
<!--start header -->
<?php include('include/topbar.php'); ?>
<!--end header -->
<!--start page wrapper -->
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Associate Columns - Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></span></h6>
<p>
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
</p>
</div>
</div>
</div>
<div class="card-body">
<!-- Upload Section -->
<div class="mb-4">
<label class="form-label">Upload XLS Example:</label>
<input type="file" id="xlsUpload" class="form-control">
<small id="uploadStatus" class="text-muted">
<?php if (!empty($template['sample_xlsx'])): ?>
Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank">
<?php echo htmlspecialchars($template['sample_xlsx']); ?>
</a>
<?php else: ?>
No file uploaded yet.
<?php endif; ?>
</small>
</div>
<!-- Association Section -->
<div class="row">
<div class="col-md-5">
<h5>XLS Column</h5>
<ul id="xlsColumns" class="list-group border p-3" style="height: 300px; overflow-y: auto;"></ul>
</div>
<div class="col-md-2 text-center d-flex align-items-center justify-content-center">
<button class="btn btn-dark" id="addAssociation"> Add</button>
</div>
<div class="col-md-5">
<div class="d-flex align-items-center mb-2">
<h5>Schema Fields</h5>
<button id="loadSchemaButton" class="btn btn-primary ms-2" data-empty="<?php echo $isSchemajsonEmpty ? 'true' : 'false'; ?>">
<?php echo $isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details'; ?>
</button>
</div>
<table id="schemaFieldsTable" class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>ID</th>
<th>Type</th>
</tr>
</thead>
<tbody id="schemaFieldsBody">
<!-- I dettagli dello schema verranno caricati qui -->
</tbody>
</table>
</div>
</div>
<!-- Associations List -->
<div class="mt-4">
<h5>Current Associations</h5>
<ul id="associationsList" class="list-group border p-3"></ul>
</div>
<!-- Save Button -->
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<!--end page wrapper -->
<!--start overlay-->
<div class="overlay toggle-icon"></div>
<!--end overlay-->
<!--Start Back To Top Button-->
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<!--End Back To Top Button-->
<?php include('include/footer.php'); ?>
</div>
<!--end wrapper-->
<!-- search modal -->
<?php //include('include/searchmodal.php');
?>
<!-- end search modal -->
<!--start switcher-->
<?php //include('include/themeswitcher.php');
?>
<!--end switcher-->
<?php include('jsinclude.php'); ?>
<script>
document.getElementById('xlsUpload').addEventListener('change', function(event) {
let file = event.target.files[0];
if (!file) {
console.error("❌ No file selected");
return;
}
console.log("📂 File selected:", file.name);
let formData = new FormData();
formData.append("xls_file", file);
formData.append("template_id", <?php echo $id; ?>);
let statusText = document.getElementById('uploadStatus');
statusText.innerText = "Uploading...";
fetch('upload_xls_example.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error uploading file:", data.message);
statusText.innerText = "❌ Upload failed: " + data.message;
return;
}
console.log("✅ File uploaded successfully:", data.filepath);
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
processXLSX(file);
})
.catch(error => {
console.error("❌ Fetch error:", error);
statusText.innerText = "❌ Upload failed. Check console.";
});
});
function processXLSX(file) {
let reader = new FileReader();
reader.onload = function(e) {
let data = new Uint8Array(e.target.result);
let workbook = XLSX.read(data, {
type: 'array'
});
let sheet = workbook.Sheets[workbook.SheetNames[0]];
console.log("📄 Sheet found:", workbook.SheetNames[0]);
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1;
console.log("📌 Using Header Row:", rowIndex, "Start Column:", startColumn);
let sheetData = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: "",
raw: false,
range: 0
});
console.log("📊 Sheet Data:", sheetData);
if (!sheetData[rowIndex - 1]) {
console.warn("⚠️ No headers found in the specified row:", rowIndex);
document.getElementById('xlsColumns').innerHTML = "<li class='list-group-item text-danger'>No headers found</li>";
return;
}
let headers = sheetData[rowIndex - 1] || [];
console.log("🔎 Raw Headers:", headers);
if (!headers.length || headers.every(h => h.trim() === "")) {
console.warn("⚠️ Header row is empty at row:", rowIndex);
document.getElementById('xlsColumns').innerHTML = "<li class='list-group-item text-warning'>No headers found in the specified row</li>";
return;
}
let adjustedHeaders = headers.slice(startColumn - 1).filter(header => header !== undefined && header.trim() !== "");
console.log("✅ Extracted Headers:", adjustedHeaders);
let xlsColumns = document.getElementById('xlsColumns');
xlsColumns.innerHTML = '';
adjustedHeaders.forEach(header => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = header;
xlsColumns.appendChild(li);
});
};
reader.readAsArrayBuffer(file);
}
document.addEventListener("DOMContentLoaded", function() {
let selectedXLSColumn = null;
let selectedSchemaField = null;
let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
let clientId = <?php echo json_encode($template['idclient'] ?? 0); ?>;
let isSchemajsonEmpty = <?php echo json_encode($isSchemajsonEmpty); ?>;
async function loadClientAndSchemaNames() {
if (clientId && clientId > 0) {
try {
const response = await fetch(`get_clienti.php?id=${clientId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (response.ok && data.value && data.value.length > 0) {
document.getElementById('clientName').textContent = data.value[0].Nominativo || 'N/A';
}
} catch (error) {
console.error("❌ Error loading client name:", error);
}
}
if (schemaId && schemaId > 0) {
try {
const response = await fetch(`get_schemi.php?id=${schemaId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (response.ok && data.value && data.value.length > 0) {
document.getElementById('schemaName').textContent = data.value[0].Nome || 'N/A';
}
} catch (error) {
console.error("❌ Error loading schema name:", error);
}
}
}
loadClientAndSchemaNames();
async function loadOrUpdateSchemaDetails() {
if (!schemaId || schemaId === 0) {
console.warn("⚠️ No schema ID available for template:", templateId);
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="3" class="text-warning">No schema associated with this template.</td></tr>';
return;
}
const loadSchemaButton = document.getElementById('loadSchemaButton');
loadSchemaButton.disabled = true;
loadSchemaButton.textContent = 'Loading...';
try {
// Chiamata a get_schema_details.php
const response = await fetch(`get_schema_details.php?id=${schemaId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
console.log("📡 HTTP Status (get_schema_details):", response.status);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const rawResponse = await response.text();
console.log("📡 Raw Response (get_schema_details):", rawResponse);
let data;
try {
data = JSON.parse(rawResponse);
} catch (e) {
throw new Error('Invalid JSON response from get_schema_details: ' + e.message);
}
console.log("📡 Parsed Schema Details Response:", data);
if (!data || typeof data !== 'object') {
throw new Error('Invalid schema details response: Response is not an object');
}
if (!data.SchemiCustomFieldsDettagli) {
throw new Error('Invalid schema details response: Missing "SchemiCustomFieldsDettagli"');
}
const schemaJson = JSON.stringify(data);
// Salva il JSON
await saveSchemaJson(templateId, schemaJson);
// Rileggi i dati aggiornati
const updatedTemplateResponse = await fetch(`mapping_template_xls_scheme.php?id=${templateId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
console.log("📡 HTTP Status (updated_template):", updatedTemplateResponse.status);
const rawUpdatedResponse = await updatedTemplateResponse.text();
console.log("📡 Raw Response (updated_template):", rawUpdatedResponse);
let updatedTemplateData;
try {
updatedTemplateData = JSON.parse(rawUpdatedResponse);
} catch (e) {
throw new Error('Invalid JSON response from updated_template: ' + e.message);
}
const updatedSchemaJson = updatedTemplateData.schemajson;
const schemaDetails = JSON.parse(updatedSchemaJson);
if (schemaDetails && schemaDetails.SchemiCustomFieldsDettagli) {
const schemaFieldsBody = document.getElementById('schemaFieldsBody');
schemaFieldsBody.innerHTML = '';
schemaDetails.SchemiCustomFieldsDettagli.forEach(detail => {
const field = detail.CustomField;
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${field.Titolo || 'N/A'}</td>
<td>${field.IdCustomField || 'N/A'}</td>
<td>${field.Tipo || 'N/A'}</td>
`;
tr.setAttribute('data-field', JSON.stringify({
id: field.IdCustomField,
title: field.Titolo,
type: field.Tipo
}));
tr.addEventListener('click', function() {
document.querySelectorAll('#schemaFieldsBody tr').forEach(el => el.classList.remove('table-active'));
tr.classList.add('table-active');
selectedSchemaField = JSON.parse(tr.getAttribute('data-field'));
console.log("📌 Selected Schema Field:", selectedSchemaField);
});
schemaFieldsBody.appendChild(tr);
});
} else {
throw new Error('No schema details available after update');
}
loadSchemaButton.textContent = 'Update Schema Details';
} catch (error) {
console.error("❌ Error details:", error);
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="3" class="text-danger">Error: ' + error.message + '</td></tr>';
} finally {
loadSchemaButton.disabled = false; // Assicura che il pulsante torni attivo
loadSchemaButton.textContent = isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details';
}
}
async function saveSchemaJson(templateId, schemaJson) {
console.log("Saving Schema JSON - templateId:", templateId, "schemaJson:", schemaJson); // Debug
try {
const response = await fetch('update_schemajson.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
schemajson: schemaJson
})
});
const data = await response.json();
console.log("Response from update_schemajson.php:", data); // Debug
if (!data.success) {
throw new Error(data.message || 'Failed to save schema JSON');
}
console.log("✅ Schema JSON saved:", data);
} catch (error) {
console.error("❌ Error saving schema JSON:", error);
throw error;
}
}
const loadSchemaButton = document.getElementById('loadSchemaButton');
if (isSchemajsonEmpty) {
loadSchemaButton.textContent = 'Load Schema Details';
} else {
const initialSchema = JSON.parse('<?php echo addslashes($schemajson); ?>');
if (initialSchema && initialSchema.SchemiCustomFieldsDettagli) {
const schemaFieldsBody = document.getElementById('schemaFieldsBody');
schemaFieldsBody.innerHTML = '';
initialSchema.SchemiCustomFieldsDettagli.forEach(detail => {
const field = detail.CustomField;
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${field.Titolo || 'N/A'}</td>
<td>${field.IdCustomField || 'N/A'}</td>
<td>${field.Tipo || 'N/A'}</td>
`;
tr.setAttribute('data-field', JSON.stringify({
id: field.IdCustomField,
title: field.Titolo,
type: field.Tipo
}));
tr.addEventListener('click', function() {
document.querySelectorAll('#schemaFieldsBody tr').forEach(el => el.classList.remove('table-active'));
tr.classList.add('table-active');
selectedSchemaField = JSON.parse(tr.getAttribute('data-field'));
console.log("📌 Selected Schema Field:", selectedSchemaField);
});
schemaFieldsBody.appendChild(tr);
});
}
}
loadSchemaButton.addEventListener('click', loadOrUpdateSchemaDetails);
function loadMappings() {
fetch('load_existing_mappings.php?template_id=' + templateId)
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error loading mappings:", data.message);
return;
}
let associationsList = document.getElementById('associationsList');
let xlsColumnsList = document.getElementById('xlsColumns');
data.mappings.forEach(mapping => {
let li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.innerHTML = `
${mapping.excel_column} ${mapping.schema_field_id || 'N/A'}
<button class="btn btn-danger btn-sm removeAssociation" data-excel="${mapping.excel_column}" data-mysql="${mapping.schema_field_id}">X</button>
`;
associationsList.appendChild(li);
});
if (Array.isArray(data.remaining_xls_columns)) {
data.remaining_xls_columns.forEach(header => {
let li = document.createElement('li');
li.className = 'list-group-item';
li.textContent = header;
xlsColumnsList.appendChild(li);
});
}
// ❌ RIMOSSA: non ricaricare i dati dello schema automaticamente!
// if (!isSchemajsonEmpty) {
// loadOrUpdateSchemaDetails();
// }
})
.catch(error => console.error("❌ Error fetching mappings:", error));
}
loadMappings();
document.getElementById('xlsColumns').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
document.querySelectorAll('#xlsColumns li').forEach(el => el.classList.remove('active'));
event.target.classList.add('active');
selectedXLSColumn = event.target.textContent;
console.log("📌 Selected XLS Column:", selectedXLSColumn);
}
});
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
if (event.target.tagName === 'TR') {
document.querySelectorAll('#schemaFieldsBody tr').forEach(el => el.classList.remove('table-active'));
event.target.classList.add('table-active');
selectedSchemaField = JSON.parse(event.target.getAttribute('data-field') || '{}');
console.log("📌 Selected Schema Field:", selectedSchemaField);
}
});
document.getElementById('addAssociation').addEventListener('click', function() {
if (!selectedXLSColumn || !selectedSchemaField) {
alert("Please select both an XLS column and a schema field.");
return;
}
saveAssociation(selectedXLSColumn, selectedSchemaField.id);
selectedXLSColumn = null;
selectedSchemaField = null;
});
function saveAssociation(excelColumn, schemaFieldId) {
let allHeaders = [...document.querySelectorAll('#xlsColumns li')].map(el => el.textContent).join(",");
fetch('save_column_mapping.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
tablename: "<?php echo $template['target_table']; ?>",
excel_column: excelColumn,
schema_field_id: schemaFieldId,
data_type: "VARCHAR",
is_required: 0,
default_value: "",
headerexcel: allHeaders
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error saving mapping:", data.message);
alert("Error saving mapping: " + data.message);
} else {
console.log("✅ Association saved:", data);
loadMappings();
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
document.getElementById('associationsList').addEventListener('click', function(event) {
if (event.target.classList.contains('removeAssociation')) {
let excelColumn = event.target.getAttribute("data-excel");
let mysqlColumn = event.target.getAttribute("data-mysql");
removeAssociation(excelColumn, mysqlColumn);
}
});
function removeAssociation(excelColumn, mysqlColumn) {
fetch('remove_column_mapping.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
excel_column: excelColumn,
mysql_column: mysqlColumn,
tablename: "<?php echo $template['target_table']; ?>"
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error removing mapping:", data.message);
alert("Error removing mapping: " + data.message);
} else {
console.log("🗑️ Association removed:", data);
loadMappings();
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
});
</script>
</body>
</html>

View File

@ -1,721 +0,0 @@
<?php include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$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->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die("Template not found");
}
$clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
$isSchemajsonEmpty = empty(trim($template['schemajson']));
// Recupera i campi dalla tabella template_mapping
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, 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 FROM template_mapping WHERE template_id = ?");
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Recupera le colonne già associate nel database
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
// Decodifica l'header XLS salvato, se presente
$xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : [];
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
.dropdown-select {
width: 100%;
box-sizing: border-box;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
font-size: 14px;
appearance: none;
background: white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="%23333"><path d="M7.293 4.293a1 1 0 011.414 0L10 6.586l1.293-1.293a1 1 0 111.414 1.414l-2 2a1 1 0 01-1.414 0l-2-2a1 1 0 010-1.414z"/></svg>') no-repeat right 5px center;
}
.dropdown-select:focus {
outline: none;
border-color: #80bdff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
#loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
justify-content: center;
align-items: center;
}
#loading-overlay div {
color: white;
font-size: 24px;
padding: 20px;
background: #333;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
#schemaFieldsTable th:first-child,
#schemaFieldsTable td:first-child,
#schemaFieldsTable th:nth-child(2),
#schemaFieldsTable td:nth-child(2) {
width: 100px;
text-align: center;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Configure Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></span></h6>
<p>
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
</p>
</div>
</div>
</div>
<div class="card-body">
<div class="mb-4">
<label class="form-label">Upload XLS Example:</label>
<input type="file" id="xlsUpload" class="form-control">
<small id="uploadStatus">
<?php if (!empty($template['sample_xlsx'])): ?>
Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
<?php else: ?>
No file uploaded yet.
<?php endif; ?>
</small>
</div>
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center mb-2">
<h5>Schema Fields Configuration</h5>
<button id="updateSchemaButton" class="btn btn-primary ms-2" data-empty="<?php echo $isSchemajsonEmpty ? 'true' : 'false'; ?>">
<?php echo $isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details'; ?>
</button>
</div>
<table id="schemaFieldsTable" class="table table-striped">
<thead>
<tr>
<th>Main</th>
<th>Visible on Import</th>
<th>Title</th>
<th>Type</th>
<th>Mapping</th>
<th>Default Value</th>
</tr>
</thead>
<tbody id="schemaFieldsBody">
<?php foreach ($mappings as $mapping): ?>
<tr>
<td>
<input type="checkbox" class="main-field-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['main_field'] == 1 ? 'checked' : ''; ?>>
</td>
<td>
<input type="checkbox" class="visible-import-checkbox" data-mapping-id="<?php echo $mapping['id']; ?>" <?php echo (isset($mapping['is_visible_import']) ? $mapping['is_visible_import'] : 1) == 1 ? 'checked' : ''; ?>>
</td>
<td>
<?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?>
<?php if ($mapping['is_required'] == 1): ?>
<span class="badge bg-danger ms-2">Required</span>
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td>
<?php
$isSceltaMultipla = $mapping['data_type'] === 'SceltaMultipla';
$mappingValue = $isSceltaMultipla ? 'manual' : ($mapping['excel_column'] ? 'xls' : ($mapping['is_manual'] ? 'manual' : ''));
?>
<select class="form-select mapping-select" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>" <?php echo $isSceltaMultipla ? 'disabled' : ''; ?>>
<?php if (!$isSceltaMultipla): ?>
<option value="">Select Option</option>
<option value="xls" <?php echo $mappingValue === 'xls' ? 'selected' : ''; ?>>Map to XLS Column</option>
<?php endif; ?>
<option value="manual" <?php echo $mappingValue === 'manual' ? 'selected' : ''; ?>>Manual Entry</option>
</select>
<select class="form-select xls-columns" style="display:<?php echo $mappingValue === 'xls' ? 'block' : 'none'; ?>" data-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>></select>
<?php if ($mapping['excel_column']): ?>
<span class="mapped-column" style="margin-left: 5px;"><?php echo htmlspecialchars($mapping['excel_column']); ?></span>
<button class="btn btn-danger btn-sm remove-xls" data-id="<?php echo $mapping['id']; ?>" style="margin-left: 5px;">X</button>
<?php endif; ?>
</td>
<td>
<?php if ($mapping['data_type'] === 'SceltaMultipla'): ?>
<select class="form-select dropdown-select manual-default" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>" data-manual-default="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:block;">
<option value="">Seleziona...</option>
</select>
<?php else: ?>
<input type="text" class="form-control manual-default" placeholder="Default value" value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:<?php echo $mapping['is_manual'] ? 'block' : 'none'; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || [];
let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || [];
document.getElementById('xlsUpload').addEventListener('change', function(event) {
let file = event.target.files[0];
if (!file) return;
let formData = new FormData();
formData.append("xls_file", file);
formData.append("template_id", <?php echo $id; ?>);
let statusText = document.getElementById('uploadStatus');
statusText.innerText = "Uploading...";
fetch('upload_xls_example.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
statusText.innerText = "❌ Upload failed: " + data.message;
return;
}
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
processXLSX(file);
})
.catch(error => {
statusText.innerText = "❌ Upload failed. Check console.";
console.error(error);
});
});
function processXLSX(file) {
let reader = new FileReader();
reader.onload = function(e) {
let data = new Uint8Array(e.target.result);
let workbook = XLSX.read(data, {
type: 'array'
});
let sheet = workbook.Sheets[workbook.SheetNames[0]];
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1;
let sheetData = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: "",
raw: false,
range: 0
});
console.log("Dati della riga " + rowIndex + ":", sheetData[rowIndex - 1]);
if (!sheetData[rowIndex - 1]) {
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
});
return;
}
let headers = sheetData[rowIndex - 1].slice(startColumn - 1).map(header => header === undefined ? "" : header);
console.log("Intestazioni estratte:", headers);
availableXlsColumns = [...headers];
usedColumnsFromDB = [];
saveXlsHeaders(headers);
updateXlsDropdowns();
};
reader.readAsArrayBuffer(file);
}
function saveXlsHeaders(headers) {
fetch('update_xls_headers.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?php echo $id; ?>,
xls_headers: JSON.stringify(headers)
})
}).then(response => response.json())
.then(data => {
if (!data.success) console.error("❌ Error saving XLS headers:", data.message);
})
.catch(error => console.error("❌ Fetch error:", error));
}
function updateXlsDropdowns() {
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
.filter(select => select.style.display === 'block' && select.value)
.map(select => select.value)
.concat(usedColumnsFromDB);
document.querySelectorAll('select.xls-columns').forEach(select => {
let currentValue = select.value || select.dataset.currentXls || '';
let options = availableXlsColumns
.filter(col => !usedColumns.includes(col) || col === currentValue)
.map(col => `<option value="${col}" ${col === currentValue ? 'selected' : ''}>${col}</option>`)
.join('');
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
select.dataset.currentXls = currentValue;
if (currentValue && !options.includes(currentValue)) {
select.value = '';
}
});
}
document.addEventListener("DOMContentLoaded", function() {
let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
let isSchemajsonEmpty = <?php echo json_encode($isSchemajsonEmpty); ?>;
// Crea l'overlay di caricamento
const loadingOverlay = document.createElement('div');
loadingOverlay.id = 'loading-overlay';
loadingOverlay.innerHTML = '<div>Loading Dropdown Options...</div>';
document.body.appendChild(loadingOverlay);
async function populateDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown-select');
if (dropdowns.length === 0) {
console.warn('Nessun dropdown di tipo SceltaMultipla trovato.');
return;
}
console.log(`Trovati ${dropdowns.length} dropdown da popolare.`);
const uniqueFieldIds = [...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))].filter(fieldId => fieldId);
if (uniqueFieldIds.length === 0) {
console.warn('Nessun field_id valido trovato per i dropdown.');
dropdowns.forEach(dropdown => {
dropdown.innerHTML = '<option value="">Nessun field_id valido</option>';
dropdown.disabled = true;
});
return;
}
console.log('Field IDs univoci:', uniqueFieldIds);
// Mostra l'overlay di caricamento
const loadingOverlay = document.getElementById('loading-overlay');
loadingOverlay.style.display = 'flex';
let dropdownData = {};
try {
// Usa field_ids come previsto dal backend
const fieldIdsQuery = uniqueFieldIds.join(',');
console.log(`Recupero dati per field_ids: ${fieldIdsQuery}`);
const response = await fetch(`get_customfield_values.php?field_ids=${fieldIdsQuery}`);
if (!response.ok) {
throw new Error(`Errore HTTP: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (data.error) {
throw new Error(`Errore API: ${data.error}`);
}
dropdownData = data; // data è un oggetto come { "146": [], "156": [...] }
console.log('Dati totali restituiti:', dropdownData);
// Popola i dropdown
dropdowns.forEach(dropdown => {
const fieldId = dropdown.getAttribute('data-field-id');
const manualDefault = dropdown.getAttribute('data-manual-default') || '';
console.log(`Popolamento dropdown per field_id: ${fieldId}, manual_default: ${manualDefault}`);
dropdown.innerHTML = '<option value="">Seleziona...</option>';
if (!fieldId || !dropdownData[fieldId] || dropdownData[fieldId].length === 0) {
console.warn(`Nessun dato disponibile per field_id ${fieldId}`);
dropdown.innerHTML = `<option value="">Nessun valore (field_id ${fieldId} vuoto)</option>`;
dropdown.disabled = true; // Disabilita per evitare selezioni inutili
return;
}
dropdownData[fieldId].forEach(value => {
const option = document.createElement('option');
option.value = value.IdCustomFieldsValue;
option.textContent = value.Valore;
if (manualDefault && String(value.IdCustomFieldsValue) === String(manualDefault)) {
option.selected = true;
}
dropdown.appendChild(option);
});
dropdown.disabled = false;
});
} catch (error) {
console.error('Errore generale nel caricamento dei dropdown:', error);
dropdowns.forEach(dropdown => {
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
dropdown.disabled = true;
});
} finally {
console.log('Nascondo overlay di caricamento.');
loadingOverlay.style.display = 'none';
}
}
// Carica i dropdown con overlay
async function loadDropdownsWithOverlay() {
console.log('Inizio caricamento tendine');
loadingOverlay.style.display = 'flex';
await new Promise(resolve => setTimeout(resolve, 500));
await populateDropdowns();
console.log('Caricamento tendine completato');
loadingOverlay.style.display = 'none';
}
loadDropdownsWithOverlay();
async function loadClientAndSchemaNames() {
if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) {
let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('clientName').textContent = data.value[0].Nominativo || 'N/A';
}
if (schemaId > 0) {
let response = await fetch(`get_schemi.php?id=${schemaId}`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('schemaName').textContent = data.value[0].Nome || 'N/A';
}
}
loadClientAndSchemaNames();
async function updateSchemaDetails() {
if (!schemaId) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-warning">No schema associated.</td></tr>';
return;
}
let updateSchemaButton = document.getElementById('updateSchemaButton');
updateSchemaButton.disabled = true;
updateSchemaButton.textContent = 'Loading...';
try {
let response = await fetch(`get_schema_details.php?id=${schemaId}`);
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
let data = JSON.parse(await response.text());
if (!data.SchemiCustomFieldsDettagli) throw new Error('Missing "SchemiCustomFieldsDettagli"');
await saveSchemaJson(templateId, JSON.stringify(data));
alert('Schema updated successfully. Refresh the page to see changes.');
} catch (error) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-danger">Error: ' + error.message + '</td></tr>';
} finally {
updateSchemaButton.disabled = false;
updateSchemaButton.textContent = 'Update Schema Details';
}
}
async function saveSchemaJson(templateId, schemaJson) {
let response = await fetch('update_schemajson.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
schemajson: schemaJson
})
});
let data = await response.json();
if (!data.success) throw new Error(data.message || 'Failed to save schema JSON');
}
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('mapping-select')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
let manualInput = tr.querySelector('.manual-default');
let mappedColumn = tr.querySelector('.mapped-column');
let removeBtn = tr.querySelector('.remove-xls');
if (event.target.value === 'xls') {
xlsSelect.style.display = 'block';
manualInput.style.display = 'none';
manualInput.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
} else if (event.target.value === 'manual') {
xlsSelect.style.display = 'none';
manualInput.style.display = 'block';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none';
} else {
xlsSelect.style.display = 'none';
manualInput.style.display = 'none';
manualInput.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none';
}
saveMapping(mappingId, event.target.value, manualInput.value, xlsSelect.value);
updateXlsDropdowns();
} else if (event.target.classList.contains('main-field-checkbox')) {
const checkbox = event.target;
const mappingId = checkbox.dataset.mappingId;
const value = checkbox.checked ? 1 : 0;
// Se checked, deseleziona tutti gli altri visivamente
if (checkbox.checked) {
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
if (cb !== checkbox) cb.checked = false;
});
}
// Salva l'aggiornamento
fetch('update_main_field.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?php echo $id; ?>,
mapping_id: mappingId,
value: value
})
}).then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error updating main_field:", data.message);
checkbox.checked = !checkbox.checked;
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
cb.checked = cb.dataset.originalChecked === 'true';
});
} else {
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
cb.dataset.originalChecked = cb.checked;
});
}
})
.catch(error => {
console.error("❌ Fetch error:", error);
checkbox.checked = !checkbox.checked;
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
cb.checked = cb.dataset.originalChecked === 'true';
});
});
} else if (event.target.classList.contains('visible-import-checkbox')) {
const checkbox = event.target;
const mappingId = checkbox.dataset.mappingId;
const value = checkbox.checked ? 1 : 0;
fetch('update_visible_import.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?php echo $id; ?>,
mapping_id: mappingId,
value: value
})
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.error("❌ Error updating is_visible_import:", data.message);
checkbox.checked = !checkbox.checked;
}
})
.catch(error => {
console.error("❌ Fetch error:", error);
checkbox.checked = !checkbox.checked;
});
}
});
// Salva lo stato originale dei checkbox al caricamento
document.querySelectorAll('.main-field-checkbox').forEach(cb => {
cb.dataset.originalChecked = cb.checked;
});
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('xls-columns')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let manualInput = tr.querySelector('.manual-default');
let mappedColumn = tr.querySelector('.mapped-column');
let removeBtn = tr.querySelector('.remove-xls');
if (!mappedColumn) {
mappedColumn = document.createElement('span');
mappedColumn.className = 'mapped-column';
mappedColumn.style.marginLeft = '5px';
tr.querySelector('td:nth-child(5)').appendChild(mappedColumn);
}
if (!removeBtn) {
removeBtn = document.createElement('button');
removeBtn.className = 'btn btn-danger btn-sm remove-xls';
removeBtn.textContent = 'X';
removeBtn.style.marginLeft = '5px';
removeBtn.setAttribute('data-id', mappingId);
tr.querySelector('td:nth-child(5)').appendChild(removeBtn);
removeBtn.addEventListener('click', function(e) {
let tr = e.target.closest('tr');
let xlsSelect = tr.querySelector('.xls-columns');
let mappingSelect = tr.querySelector('.mapping-select');
xlsSelect.value = '';
xlsSelect.style.display = 'none';
mappingSelect.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
e.target.style.display = 'none';
saveMapping(mappingId, '', '', null);
updateXlsDropdowns();
});
}
console.log("XLS Column changed:", {
id: mappingId,
value: event.target.value
});
if (mappedColumn) {
mappedColumn.textContent = event.target.value ? `(${event.target.value})` : '';
mappedColumn.style.display = event.target.value ? 'inline' : 'none';
}
if (removeBtn) removeBtn.style.display = event.target.value ? 'inline-block' : 'none';
saveMapping(mappingId, 'xls', manualInput.value, event.target.value);
updateXlsDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('manual-default') && event.target.tagName === 'SELECT') {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
console.log("Manual default dropdown changed:", {
id: mappingId,
value: event.target.value
});
saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
}
});
document.getElementById('schemaFieldsBody').addEventListener('input', function(event) {
if (event.target.classList.contains('manual-default') && event.target.tagName === 'INPUT') {
let tr = event.target.closest('tr');
let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
console.log("Manual default input changed:", {
id: mappingId,
value: event.target.value
});
saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
}
});
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
if (event.target.classList.contains('remove-xls')) {
let mappingId = event.target.getAttribute('data-id');
let tr = event.target.closest('tr');
let xlsSelect = tr.querySelector('.xls-columns');
let mappingSelect = tr.querySelector('.mapping-select');
let mappedColumn = tr.querySelector('.mapped-column');
xlsSelect.value = '';
xlsSelect.style.display = 'none';
mappingSelect.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
event.target.style.display = 'none';
console.log("Removing XLS mapping:", {
id: mappingId
});
saveMapping(mappingId, '', '', null);
updateXlsDropdowns();
}
});
function saveMapping(mappingId, mappingType, defaultValue, excelColumn) {
console.log("Saving mapping:", {
id: mappingId,
mapping_type: mappingType,
excel_column: excelColumn,
manual_default: defaultValue
});
fetch('save_mapping_json.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: mappingId,
mapping_type: mappingType,
excel_column: mappingType === 'xls' ? excelColumn : null,
manual_default: mappingType === 'manual' ? defaultValue : null,
tablename: "<?php echo $template['target_table']; ?>"
})
}).then(response => response.json())
.then(data => {
console.log("Save response:", data);
if (!data.success) console.error("❌ Error saving mapping:", data.message);
if (data.success && excelColumn) {
usedColumnsFromDB = usedColumnsFromDB.filter(col => col !== excelColumn);
usedColumnsFromDB.push(excelColumn);
updateXlsDropdowns();
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
if (availableXlsColumns.length) updateXlsDropdowns();
});
</script>
</body>
</html>

View File

@ -1,464 +0,0 @@
<?php include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$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->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die("Template not found");
}
$clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
$isSchemajsonEmpty = empty(trim($template['schemajson']));
// Recupera i campi dalla tabella template_mapping
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label FROM template_mapping WHERE template_id = ?");
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Recupera le colonne già associate nel database
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
// Decodifica l'header XLS salvato, se presente
$xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : [];
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Configure Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></span></h6>
<p>
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
</p>
</div>
</div>
</div>
<div class="card-body">
<div class="mb-4">
<label class="form-label">Upload XLS Example:</label>
<input type="file" id="xlsUpload" class="form-control">
<small id="uploadStatus">
<?php if (!empty($template['sample_xlsx'])): ?>
Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
<?php else: ?>
No file uploaded yet.
<?php endif; ?>
</small>
</div>
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center mb-2">
<h5>Schema Fields Configuration</h5>
<button id="updateSchemaButton" class="btn btn-primary ms-2" data-empty="<?php echo $isSchemajsonEmpty ? 'true' : 'false'; ?>">
<?php echo $isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details'; ?>
</button>
</div>
<table id="schemaFieldsTable" class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>ID</th>
<th>Type</th>
<th>Mapping</th>
<th>Default Value</th>
</tr>
</thead>
<tbody id="schemaFieldsBody">
<?php foreach ($mappings as $mapping): ?>
<tr>
<td><?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($mapping['field_id'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td>
<select class="form-select mapping-select" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
<option value="">Select Option</option>
<option value="xls" <?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'selected' : ''; ?>>Map to XLS Column</option>
<option value="manual" <?php echo $mapping['is_manual'] ? 'selected' : ' '; ?>>Manual Entry</option>
</select>
<select class="form-select xls-columns" style="display:<?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'block' : 'none'; ?>" data-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>></select>
<?php if ($mapping['excel_column']): ?>
<span class="mapped-column" style="margin-left: 5px;"><?php echo htmlspecialchars($mapping['excel_column']); ?></span>
<button class="btn btn-danger btn-sm remove-xls" data-id="<?php echo $mapping['id']; ?>" style="margin-left: 5px;">X</button>
<?php endif; ?>
</td>
<td>
<input type="text" class="form-control manual-default" placeholder="Default value" value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:<?php echo $mapping['is_manual'] ? 'block' : 'none'; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || [];
let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || [];
document.getElementById('xlsUpload').addEventListener('change', function(event) {
let file = event.target.files[0];
if (!file) return;
let formData = new FormData();
formData.append("xls_file", file);
formData.append("template_id", <?php echo $id; ?>);
let statusText = document.getElementById('uploadStatus');
statusText.innerText = "Uploading...";
fetch('upload_xls_example.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
statusText.innerText = "❌ Upload failed: " + data.message;
return;
}
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
processXLSX(file);
})
.catch(error => {
statusText.innerText = "❌ Upload failed. Check console.";
console.error(error);
});
});
function processXLSX(file) {
let reader = new FileReader();
reader.onload = function(e) {
let data = new Uint8Array(e.target.result);
let workbook = XLSX.read(data, {
type: 'array'
});
let sheet = workbook.Sheets[workbook.SheetNames[0]];
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1; // Usa header_row dal template
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1; // Usa start_column come numero
let sheetData = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: "",
raw: false,
range: 0
});
console.log("Dati della riga " + rowIndex + ":", sheetData[rowIndex - 1]); // Debug: stampa la riga delle intestazioni
if (!sheetData[rowIndex - 1]) {
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
});
return;
}
// Estrai le intestazioni a partire dalla colonna specificata, includendo le vuote
let headers = sheetData[rowIndex - 1].slice(startColumn - 1).map(header => header === undefined ? "" : header);
console.log("Intestazioni estratte:", headers); // Debug: stampa le intestazioni estratte
availableXlsColumns = [...headers];
usedColumnsFromDB = []; // Resetta le colonne usate dopo un nuovo caricamento
saveXlsHeaders(headers);
updateXlsDropdowns();
};
reader.readAsArrayBuffer(file);
}
function saveXlsHeaders(headers) {
fetch('update_xls_headers.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: <?php echo $id; ?>,
xls_headers: JSON.stringify(headers)
})
}).then(response => response.json())
.then(data => {
if (!data.success) console.error("❌ Error saving XLS headers:", data.message);
})
.catch(error => console.error("❌ Fetch error:", error));
}
function updateXlsDropdowns() {
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
.filter(select => select.style.display === 'block' && select.value)
.map(select => select.value)
.concat(usedColumnsFromDB); // Aggiunge le colonne già salvate nel DB
document.querySelectorAll('select.xls-columns').forEach(select => {
let currentValue = select.value || select.dataset.currentXls || '';
let options = availableXlsColumns
.filter(col => !usedColumns.includes(col) || col === currentValue) // Esclude colonne già usate, tranne la corrente
.map(col => `<option value="${col}" ${col === currentValue ? 'selected' : ''}>${col}</option>`)
.join('');
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
select.dataset.currentXls = currentValue;
if (currentValue && !options.includes(currentValue)) {
select.value = ''; // Reset se il valore non è più valido
}
});
}
document.addEventListener("DOMContentLoaded", function() {
let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
let isSchemajsonEmpty = <?php echo json_encode($isSchemajsonEmpty); ?>;
async function loadClientAndSchemaNames() {
if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) {
let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('clientName').textContent = data.value[0].Nominativo || 'N/A';
}
if (schemaId > 0) {
let response = await fetch(`get_schemi.php?id=${schemaId}`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('schemaName').textContent = data.value[0].Nome || 'N/A';
}
}
loadClientAndSchemaNames();
async function updateSchemaDetails() {
if (!schemaId) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="5" class="text-warning">No schema associated.</td></tr>';
return;
}
let updateSchemaButton = document.getElementById('updateSchemaButton');
updateSchemaButton.disabled = true;
updateSchemaButton.textContent = 'Loading...';
try {
let response = await fetch(`get_schema_details.php?id=${schemaId}`);
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
let data = JSON.parse(await response.text());
if (!data.SchemiCustomFieldsDettagli) throw new Error('Missing "SchemiCustomFieldsDettagli"');
await saveSchemaJson(templateId, JSON.stringify(data));
alert('Schema updated successfully. Refresh the page to see changes.');
} catch (error) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="5" class="text-danger">Error: ' + error.message + '</td></tr>';
} finally {
updateSchemaButton.disabled = false;
updateSchemaButton.textContent = 'Update Schema Details';
}
}
async function saveSchemaJson(templateId, schemaJson) {
let response = await fetch('update_schemajson.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
schemajson: schemaJson
})
});
let data = await response.json();
if (!data.success) throw new Error(data.message || 'Failed to save schema JSON');
}
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('mapping-select')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
let manualInput = tr.querySelector('.manual-default');
let mappedColumn = tr.querySelector('.mapped-column');
let removeBtn = tr.querySelector('.remove-xls');
if (event.target.value === 'xls') {
xlsSelect.style.display = 'block';
manualInput.style.display = 'none';
manualInput.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
} else if (event.target.value === 'manual') {
xlsSelect.style.display = 'none';
manualInput.style.display = 'block';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none';
} else {
xlsSelect.style.display = 'none';
manualInput.style.display = 'none';
manualInput.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
if (removeBtn) removeBtn.style.display = 'none';
}
saveMapping(mappingId, event.target.value, manualInput.value, xlsSelect.value);
updateXlsDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('xls-columns')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let manualInput = tr.querySelector('.manual-default');
let mappedColumn = tr.querySelector('.mapped-column');
let removeBtn = tr.querySelector('.remove-xls');
// Aggiungi dinamicamente mappedColumn e removeBtn se non esistono
if (!mappedColumn) {
mappedColumn = document.createElement('span');
mappedColumn.className = 'mapped-column';
mappedColumn.style.marginLeft = '5px';
tr.querySelector('td:nth-child(4)').appendChild(mappedColumn);
}
if (!removeBtn) {
removeBtn = document.createElement('button');
removeBtn.className = 'btn btn-danger btn-sm remove-xls';
removeBtn.textContent = 'X';
removeBtn.style.marginLeft = '5px';
removeBtn.setAttribute('data-id', mappingId);
tr.querySelector('td:nth-child(4)').appendChild(removeBtn);
// Aggiungi l'event listener per il nuovo pulsante
removeBtn.addEventListener('click', function(e) {
let tr = e.target.closest('tr');
let xlsSelect = tr.querySelector('.xls-columns');
let mappingSelect = tr.querySelector('.mapping-select');
xlsSelect.value = '';
xlsSelect.style.display = 'none';
mappingSelect.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
e.target.style.display = 'none';
saveMapping(mappingId, '', '', null);
updateXlsDropdowns();
});
}
console.log("XLS Column changed:", {
id: mappingId,
value: event.target.value
});
if (mappedColumn) {
mappedColumn.textContent = event.target.value ? `(${event.target.value})` : '';
mappedColumn.style.display = event.target.value ? 'inline' : 'none';
}
if (removeBtn) removeBtn.style.display = event.target.value ? 'inline-block' : 'none';
saveMapping(mappingId, 'xls', manualInput.value, event.target.value);
updateXlsDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('input', function(event) {
if (event.target.classList.contains('manual-default')) {
let tr = event.target.closest('tr');
let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
console.log("Manual default changed:", {
id: mappingId,
value: event.target.value
});
saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
}
});
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
if (event.target.classList.contains('remove-xls')) {
let mappingId = event.target.getAttribute('data-id');
let tr = event.target.closest('tr');
let xlsSelect = tr.querySelector('.xls-columns');
let mappingSelect = tr.querySelector('.mapping-select');
let mappedColumn = tr.querySelector('.mapped-column');
xlsSelect.value = '';
xlsSelect.style.display = 'none';
mappingSelect.value = '';
if (mappedColumn) mappedColumn.style.display = 'none';
event.target.style.display = 'none';
console.log("Removing XLS mapping:", {
id: mappingId
});
saveMapping(mappingId, '', '', null);
updateXlsDropdowns();
}
});
function saveMapping(mappingId, mappingType, defaultValue, excelColumn) {
console.log("Saving mapping:", {
id: mappingId,
mapping_type: mappingType,
excel_column: excelColumn,
manual_default: defaultValue
});
fetch('save_mapping_json.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: mappingId,
mapping_type: mappingType,
excel_column: mappingType === 'xls' ? excelColumn : null,
manual_default: mappingType === 'manual' ? defaultValue : null,
tablename: "<?php echo $template['target_table']; ?>"
})
}).then(response => response.json())
.then(data => {
console.log("Save response:", data);
if (!data.success) console.error("❌ Error saving mapping:", data.message);
if (data.success && excelColumn) {
usedColumnsFromDB = usedColumnsFromDB.filter(col => col !== excelColumn); // Rimuovi dalla lista se salvata
usedColumnsFromDB.push(excelColumn); // Aggiungi la nuova colonna usata
updateXlsDropdowns();
}
})
.catch(error => console.error("❌ Fetch error:", error));
}
if (availableXlsColumns.length) updateXlsDropdowns();
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,274 +0,0 @@
<!-- Modal per la gestione delle annotazioni -->
<div class="modal fade" id="annotationsModal" tabindex="-1" aria-labelledby="annotationsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 90% !important; width: 90% !important;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="annotationsModalLabel">Annotazioni per TRF: <span id="trfHeaderAnnotations"></span></h5>
<!-- SLIDER PER DIMENSIONE MARKER -->
<div style="display: flex; align-items: center; gap: 10px; margin-left: 20px;">
<label for="markerSizeSlider" style="margin: 0; font-size: 0.9rem; white-space: nowrap;">Dimensione marker:</label>
<input type="range" id="markerSizeSlider" min="16" max="48" value="16" step="2" style="width: 120px;">
<span id="markerSizeValue" style="font-weight: bold; min-width: 30px;">24px</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- COLONNA SINISTRA RIDOTTA -->
<div class="col-md-6">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin: 0;">Elenco Parti</h6>
<div style="display: flex; align-items: center;">
<input type="checkbox" id="showMixPartsAnnotations" name="showMixPartsAnnotations" style="margin-right: 5px;">
<label for="showMixPartsAnnotations" style="font-size: 0.9rem;">Mix</label>
</div>
</div>
<ul id="partsListAnnotations" class="list-group" style="max-height: 500px; overflow-y: auto;"></ul>
</div>
<!-- COLONNA DESTRA PIÙ GRANDE -->
<div class="col-md-6">
<h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainerAnnotations" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 500px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden;">
<img id="samplePhotoAnnotations" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<canvas id="overlayCanvasAnnotations" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas>
<div id="descriptionListAnnotations" class="draggable-description" style="display: none;"></div>
<div id="markerContainerAnnotations"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" id="addDescriptionsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Aggiungi Lista Descrizioni</button>
<button type="button" class="btn btn-danger btn-sm" id="removeAnnotationsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rimuovi Descrizioni</button>
<button type="button" class="btn btn-warning btn-sm" id="undoMarkerBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Undo Marker</button>
<button type="button" class="btn btn-success btn-sm" id="savePhotoBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Salva Foto con Nome</button>
<button type="button" class="btn btn-primary btn-sm" id="backToPartsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Torna alle Parti</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Chiudi</button>
</div>
</div>
</div>
</div>
<style>
#annotationsModal {
z-index: 1070 !important;
}
#annotationsModal .modal-backdrop {
z-index: 1065 !important;
}
#annotationsModal .modal-content {
width: 100% !important;
max-width: 100% !important;
}
#partsListAnnotations .list-group-item {
cursor: pointer;
transition: background-color 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
}
#partsListAnnotations .list-group-item:hover {
background-color: #f5f5f5;
}
#partsListAnnotations .list-group-item.active {
background-color: #e9ecef;
border-color: #dee2e6;
}
.draggable-description {
position: absolute;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
font-family: Arial, sans-serif;
color: #000000;
cursor: move;
user-select: none;
z-index: 1000;
min-width: 100px;
min-height: 50px;
overflow: visible;
border: 1px solid #ccc;
}
.draggable-description.active-interaction {
border: 2px dashed #000;
}
.draggable-description div {
white-space: nowrap;
}
.resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background: #888;
cursor: se-resize;
}
.draggable-marker {
position: absolute;
width: 24px;
height: 24px;
border-radius: 50%;
color: #ffffff;
text-align: center;
line-height: 24px;
font-size: 12px;
cursor: move;
user-select: none;
z-index: 1000;
}
#markerContainerAnnotations {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#savePhotoBtnAnnotations {
transition: all 0.3s ease-in-out;
}
#savePhotoBtnAnnotations.unsaved {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: white !important;
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7);
}
70% {
box-shadow: 0 0 10px 15px rgba(220, 53, 69, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
}
}
.color-picker-container {
position: relative;
display: inline-block;
overflow: visible;
z-index: 2000;
}
.color-picker {
display: none;
position: absolute;
right: 0;
top: 25px;
background: #fff;
border: 1px solid #ccc;
padding: 5px;
z-index: 2000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
flex-wrap: wrap;
width: 120px;
}
.color-option {
width: 24px;
height: 24px;
margin: 3px;
border: 1px solid #000;
cursor: pointer;
display: inline-block;
pointer-events: auto;
}
.color-option:hover {
border: 2px solid #000;
margin: 2px;
}
.selected-color {
width: 24px;
height: 24px;
margin-left: 5px;
border: 1px solid #000;
cursor: pointer;
display: inline-block;
pointer-events: auto;
}
.selected-color:hover {
border: 2px solid #000;
margin-left: 4px;
}
.temp-alert {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 3000;
}
/* Stile per lo slider */
#markerSizeSlider {
-webkit-appearance: none;
height: 6px;
border-radius: 3px;
background: #ddd;
outline: none;
}
#markerSizeSlider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
}
#markerSizeSlider::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: none;
}
#partsListAnnotations .list-group-item.dragging {
opacity: 0.6;
background-color: #d1e7dd !important;
transform: rotate(3deg);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
#partsListAnnotations .placeholder {
background-color: #f8d7da !important;
border: 2px dashed #dc3545;
height: 40px;
margin: 2px 0;
border-radius: 4px;
}
</style>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>

View File

@ -1,310 +0,0 @@
<!-- Modal modificato con pulsante per riconoscimento vocale, download e selezione matrici -->
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important; width: 80% !important;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; min-width: 0;">
<h6 style="margin: 0; white-space: nowrap;">Elenco Parti</h6>
<div style="display: flex; align-items: center; min-width: 0;">
<select id="global-matrice" class="ms-2" style="width: 250px !important; min-width: 250px !important;">
</select>
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px; margin-left: 10px;">
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera 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;"><i class="fas fa-microphone"></i> Voce</button>
</div>
</div>
<ul id="partsList" class="list-group"></ul>
<table class="table table-striped table-sm mt-3" id="partsTable">
<thead>
<tr>
<th>Num. Parte</th>
<th>Descrizione Parte</th>
<th>Azioni</th>
</tr>
</thead>
<tbody id="partsTableBody">
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-primary btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M</button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainer" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<img id="samplePhoto" src="" alt="Foto del campione" style="max-width: 100%; max-height: 100%; object-fit: contain; position: absolute; top: 0; left: 0;">
<canvas id="photoCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000;"></canvas>
<div id="descriptionList" class="draggable-description" style="display: none;"></div>
<div id="markerContainer"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" id="addDescriptionsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Aggiungi Lista Descrizioni</button>
<button type="button" class="btn btn-danger btn-sm" id="removeAnnotationsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rimuovi Descrizioni</button>
<button type="button" class="btn btn-warning btn-sm" id="undoMarkerBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Undo Marker</button>
<button type="button" class="btn btn-success btn-sm" id="savePhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Salva Foto con Nome</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Chiudi</button>
</div>
</div>
</div>
</div>
<style>
#partsTable tr {
display: table-row !important;
}
#partsTable tr:hover {
background-color: #f5f5f5;
display: table-row !important;
}
#partsTable td,
#partsTable th {
padding: 0.2rem;
vertical-align: middle;
}
#partsTable input {
height: 24px;
padding: 0.1rem 0.3rem;
}
#partsTable button {
padding: 0.1rem 0.3rem;
margin: 0 2px;
}
#partsTable i {
font-size: 0.6rem !important;
}
#partsModal {
z-index: 1060 !important;
}
#partsModal .modal-backdrop {
z-index: 1055 !important;
}
#partsModal .modal-content {
width: 100% !important;
max-width: 100% !important;
}
#partsList .list-group-item {
cursor: pointer;
transition: background-color 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
}
#partsList .list-group-item:hover {
background-color: #f5f5f5;
}
#partsList input[type="color"] {
width: 30px;
height: 24px;
padding: 0;
margin-left: 5px;
cursor: pointer;
}
.draggable-description {
position: absolute;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
font-family: Arial, sans-serif;
color: #000000;
cursor: move;
user-select: none;
z-index: 1000;
min-width: 100px;
min-height: 50px;
overflow: visible;
border: 1px solid #ccc;
}
.draggable-description.active-interaction {
border: 2px dashed #000;
}
.draggable-description div {
white-space: nowrap;
}
.resize-handle {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background: #888;
cursor: se-resize;
}
.draggable-marker {
position: absolute;
width: 24px;
height: 24px;
border-radius: 50%;
color: #ffffff;
text-align: center;
line-height: 24px;
font-size: 12px;
cursor: move;
user-select: none;
z-index: 1000;
}
#markerContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.add-to-mix-btn {
padding: 0.1rem 0.3rem;
font-size: 0.8rem;
}
#savePhotoBtn {
transition: all 0.3s ease-in-out;
}
#savePhotoBtn.unsaved {
background-color: #dc3545 !important;
border-color: #dc3545 !important;
color: white !important;
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7);
}
70% {
box-shadow: 0 0 10px 15px rgba(220, 53, 69, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
}
}
.color-picker-container {
position: relative;
display: inline-block;
}
.color-picker {
display: none;
position: absolute;
top: 25px;
left: 0;
background: #fff;
border: 1px solid #ccc;
padding: 5px;
z-index: 1002;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
flex-wrap: wrap;
width: 120px;
}
.color-option {
width: 24px;
height: 24px;
margin: 3px;
border: 1px solid #000;
cursor: pointer;
display: inline-block;
}
.color-option:hover {
border: 2px solid #000;
margin: 2px;
}
/* Stili per Select2 in #partsList e #global-matrice */
#partsList .select2-container,
.select2-container--default #global-matrice {
width: 250px !important;
min-width: 250px !important;
margin-left: 10px;
}
#partsList .select2-selection--single,
.select2-container--default #global-matrice .select2-selection--single {
height: 26px !important;
padding: 0.2rem 0.5rem !important;
font-size: 0.9rem !important;
border: 1px solid #ced4da !important;
background-color: #fff !important;
}
#partsList .select2-selection__rendered,
.select2-container--default #global-matrice .select2-selection__rendered {
line-height: 24px !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
#partsList .select2-selection__arrow,
.select2-container--default #global-matrice .select2-selection__arrow {
height: 26px !important;
}
#partsList .save-status,
#partsList .save-loading {
margin-left: 5px;
}
.select2-container--open .select2-dropdown {
z-index: 1051 !important;
border: 1px solid #aaa !important;
border-radius: 4px !important;
background: white !important;
overflow-y: auto !important;
max-height: 200px !important;
}
.select2-container--default .select2-search--dropdown .select2-search__field {
width: 100% !important;
padding: 0.2rem !important;
}
.select2-container--default .part-matrice,
.select2-container--default #global-matrice {
width: 250px !important;
min-width: 250px !important;
}
</style>

View File

@ -1,496 +0,0 @@
<div class="modal fade" id="partsModal" tabindex="-1" aria-labelledby="partsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" style="max-width: 80% !important;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row parts-row">
<div class="col-md-9">
<!-- Prima riga: Elenco Parti, Rinumera, Voce -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h6 style="margin: 0;">Elenco Parti</h6>
<div style="display: flex; align-items: center;">
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rinumera 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;"><i class="fas fa-microphone"></i> Voce</button>
<button type="button" class="btn btn-info btn-sm ms-2 d-none" id="quotationeBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Add Quotation</button>
<button type="button" class="btn btn-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>
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-success btn-sm add-row-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 5px;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-primary btn-sm add-mix-global" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 10px;">M</button>
<select id="macro-matrice-filter" class="form-control form-control-sm ms-2" style="width: 200px !important; min-width: 200px !important; margin-right: 10px;">
<option value="">Tutte le MacroMatrici</option>
</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 fa-xs"></i> Propaga a tutte</button>
</div>
<table class="table table-striped table-sm" id="partsTable">
<thead>
<tr>
<th style="width: 80px;">Numero</th>
<th>Descrizione</th>
<th style="width: 200px;">Matrice</th>
<th style="width: 150px;">
<input type="date" class="form-control form-control-sm propagate-date-input" style="width: 130px; margin-left: 5px; display: inline-block;" title="Propaga data a tutte le parti">
</th>
<th style="width: 200px;">
<button type="button" class="btn btn-light btn-sm propagate-note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem; margin-left: 5px;" title="Propaga nota a tutte le parti">
<i class="fas fa-sticky-note"></i>
</button>
Azioni
</th>
</tr>
</thead>
<tbody id="partsTableBody">
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
<td>
<div style="display: flex; align-items: center;">
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right fa-xs"></i></button>
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
</div>
</td>
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
<td>
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-3">
<h6>Foto del Campione</h6>
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<button type="button" class="btn btn-primary btn-sm" id="downloadPhotoBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem; margin-right: 10px;"><i class="fas fa-download"></i></button>
<div id="photoSelectorContainer" style="display: none;"></div>
</div>
<div style="position: relative; width: 100%; min-height: 400px;">
<img id="samplePhoto" src="" alt="Foto del campione" style="max-width: 100%; max-height: 400px; object-fit: contain;">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" id="openAnnotationsBtn">Apri Annotazioni</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Chiudi</button>
</div>
</div>
</div>
</div>
<!-- Modale per la nota -->
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="noteModalLabel">Nota per Parte</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control part-note" rows="5" placeholder="Inserisci una nota"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm save-note-btn">Salva</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="commonNoteModal" tabindex="-1" aria-labelledby="commonNoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commonNoteModalLabel">Nota comune per tutte le parti</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea class="form-control part-note" rows="4" placeholder="Inserisci nota comune"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="button" class="btn btn-primary save-common-note-btn">Salva</button>
</div>
</div>
</div>
</div>
<!-- Modale di conferma per l'eliminazione -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteModalLabel">Conferma Eliminazione</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Sei sicuro di voler eliminare questa parte?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-danger btn-sm" id="confirmDeleteBtn">Elimina</button>
</div>
</div>
</div>
</div>
<!-- Finestra modale "Aggiungi preventivo" -->
<div class="modal fade" id="addQuotationModal" tabindex="-1" aria-labelledby="addQuotationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addQuotationModalLabel">Choose a quotation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<select id="addQuotationSelect" class="form-control form-control-sm" style="width: 100% !important; min-width: 100% !important"></select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary btn-sm" id="addQuotationBtn">Confirm</button>
</div>
</div>
</div>
</div>
<style>
/* --- Base --- */
#partsModal {
z-index: 1060 !important
}
#partsModal .modal-backdrop {
z-index: 1055 !important
}
#partsModal .modal-content {
width: 100% !important;
max-width: 100% !important
}
#addQuotationModal {
z-index: 1060 !important
}
#addQuotationModal .modal-backdrop {
z-index: 1055 !important
}
/* Tabelle */
#partsTable tr {
display: table-row !important
}
#partsTable tr:hover {
background: #f5f5f5
}
#partsTable td,
#partsTable th {
padding: .2rem;
vertical-align: middle
}
#partsTable input,
#partsTable select {
height: 24px;
padding: .1rem .3rem
}
#partsTable button {
padding: .1rem .3rem;
margin: 0 2px
}
#partsTable i {
font-size: .6rem !important
}
/* --- Larghezze fisse header --- */
/* MacroMatrici = 250px */
#macro-matrice-filter {
width: 250px !important;
min-width: 250px !important;
max-width: 250px !important;
flex: 0 0 250px !important;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#macro-matrice-filter.select2-hidden-accessible+.select2 {
width: 250px !important;
min-width: 250px !important;
max-width: 250px !important;
flex: 0 0 250px !important;
}
#macro-matrice-filter.select2-hidden-accessible+.select2 .select2-selection__rendered {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Matrice globale = 450px */
#global-matrice {
width: 450px !important;
min-width: 450px !important;
max-width: 450px !important;
flex: 0 0 450px !important;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#global-matrice.select2-hidden-accessible+.select2 {
width: 450px !important;
min-width: 450px !important;
max-width: 450px !important;
flex: 0 0 450px !important;
}
#global-matrice.select2-hidden-accessible+.select2 .select2-selection__rendered {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Select delle righe (colonna Matrice) = 150px */
.part-matrice {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
flex: 0 0 300px !important;
}
.part-matrice.select2-hidden-accessible+.select2 {
width: 300px !important;
min-width: 300px !important;
max-width: 300px !important;
flex: 0 0 300px !important;
}
/* Colonna Descrizione (2ª colonna) = 420px */
#partsTable th:nth-child(2),
#partsTable td:nth-child(2) {
width: 350 !important;
min-width: 350px !important;
max-width: 350px !important;
}
#partsTable td:nth-child(2) .part-description {
width: 100% !important;
max-width: 100% !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Aspetto Select2 */
.select2-container--default .select2-selection--single {
height: 24px !important;
padding: .1rem .3rem !important;
font-size: .8rem !important;
border: 1px solid #ced4da !important;
}
.select2-container--default .select2-selection__arrow {
height: 24px !important
}
.select2-container--open .select2-dropdown {
z-index: 1061 !important;
border: 1px solid #aaa !important;
border-radius: 4px !important;
background: #fff !important;
max-height: 200px !important;
overflow-y: auto !important;
}
/* Evita stretching del flex nella riga dei filtri */
#partsModal .modal-body>.row .col-md-9>div[style*="display: flex"]>* {
flex: 0 0 auto;
}
/* Altri modali e pulsanti */
.propagate-matrice-btn,
.propagate-all-btn {
padding: .1rem .3rem !important;
font-size: .8rem !important
}
.save-status,
.save-loading {
margin-left: 5px
}
#confirmDeleteModal {
z-index: 1070 !important
}
#confirmDeleteModal .modal-backdrop {
z-index: 1065 !important
}
.note-btn {
padding: .2rem .4rem !important;
font-size: .9rem !important
}
.note-btn.has-note {
color: #dc3545 !important
}
#noteModal {
z-index: 1090 !important
}
#noteModal .modal-backdrop {
z-index: 1085 !important
}
#noteModal .modal-dialog {
position: relative;
z-index: 1090 !important
}
#noteModal textarea,
#commonNoteModal textarea {
resize: vertical
}
#commonNoteModal {
z-index: 1095 !important
}
#commonNoteModal .modal-backdrop {
z-index: 1090 !important
}
#commonNoteModal .modal-dialog {
position: relative;
z-index: 1095 !important
}
/* Evidenza salvataggio riga nel parts table */
/* Aumenta la specificità per le classi di flash */
table#partsTable tr.row-saving {
background-color: #f0ad4e !important;
/* Arancione per salvataggio in corso */
transition: background-color 0.3s ease;
}
table#partsTable tr.row-success {
background-color: #5cb85c !important;
/* Verde per successo */
transition: background-color 0.3s ease;
}
table#partsTable tr.row-error {
background-color: #d9534f !important;
/* Rosso per errore */
transition: background-color 0.3s ease;
}
/* Stato base: nascosti (verrà sovrascritto dallo style inline di jQuery) */
#partsModal .save-loading,
#partsModal .save-status {
display: none;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 999px;
font-weight: 700;
font-size: 12px;
line-height: 1.2;
margin-left: 5px;
}
/* Quando NON sono nascosti via style inline (jQuery .show()), forzali a inline-flex */
#partsModal .save-loading:not([style*="display: none"]),
#partsModal .save-status:not([style*="display: none"]) {
display: inline-flex;
}
/* Loading (giallo) */
/* Loading (giallo) */
#partsModal .save-loading {
background: #ffd753ff;
border: 1px solid #ffd042ff;
color: #111;
/* testo nero */
}
#partsModal .save-loading i {
color: #111;
}
/* icona nera */
#partsModal .save-loading::after {
content: " Salvataggio…";
color: #111;
}
/* Salvato (verde) */
#partsModal .save-status {
background: #5dff83ff;
border: 1px solid #4effafff;
color: #111;
/* testo nero */
}
#partsModal .save-status i {
color: #111;
}
/* icona nera */
#partsModal .save-status::after {
content: " Salvato";
color: #111;
}
/* Animazioni */
@keyframes pulse {
0%,
100% {
transform: scale(1);
opacity: .9
}
50% {
transform: scale(1.05);
opacity: 1
}
}
@keyframes pop {
0% {
transform: scale(.85);
opacity: 0
}
100% {
transform: scale(1);
opacity: 1
}
}
/* rosso */
</style>

View File

@ -1,572 +0,0 @@
$(document).ready(function () {
console.log("parts.js caricato correttamente");
$(".parts-btn").on("click", function () {
console.log("Pulsante Parts cliccato");
const iddatadb = $(this).data("iddatadb");
const rowIndex = $(this).data("row");
const importRef = $("table tbody tr")
.eq(rowIndex)
.find("td")
.eq(1)
.text();
const description =
$("table tbody tr").eq(rowIndex).find("td").eq(2).text() ||
"Sconosciuto";
$("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`);
$("#partsModal").data("iddatadb", iddatadb);
loadPhoto(iddatadb);
loadExistingParts(iddatadb);
$("#partsModal").modal("show");
});
function loadPhoto(iddatadb) {
$.ajax({
url: "load_photo.php",
method: "GET",
data: { iddatadb: iddatadb },
success: function (response) {
if (response.success && response.file_path) {
const img = $("#samplePhoto");
img.attr("src", response.file_path);
img.on("load", function () {
const container = img.parent();
const canvas = document.getElementById("photoCanvas");
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img[0].naturalWidth;
const scaleY = containerHeight / img[0].naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img[0].naturalWidth * scale;
canvas.height = img[0].naturalHeight * scale;
canvas.style.width = `${containerWidth}px`;
canvas.style.height = `${containerHeight}px`;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
img.get(0),
0,
0,
canvas.width,
canvas.height,
);
updateMarkers();
});
} else {
$("#samplePhoto").attr("src", "");
alert("Nessuna foto trovata per questo TRF.");
}
},
error: function (xhr, status, error) {
alert("Errore nel caricamento della foto: " + error);
},
});
}
function addNewRow(nextPartNumber) {
const newRow = `
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="${nextPartNumber || 1}" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
$("#partsTableBody").append(newRow);
updateRowButtons();
}
function updateRowButtons() {
const rowCount = $("#partsTableBody tr").length;
$("#partsTableBody tr").each(function (index) {
const $removeBtn = $(this).find(".remove-row");
if (rowCount > 1) {
$removeBtn.show();
} else {
$removeBtn.hide();
}
});
}
$(document).on("click", ".add-row", function (e) {
e.preventDefault();
console.log("Pulsante Aggiungi riga cliccato");
const maxPartNumber = Math.max(
...$("#partsTableBody tr")
.map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
})
.get(),
);
addNewRow(maxPartNumber + 1);
updatePartsList();
});
$(document).on("click", ".remove-row", function (e) {
e.preventDefault();
console.log("Pulsante Rimuovi riga cliccato");
const $row = $(this).closest("tr");
const partId = $row.data("part-id");
console.log("ID parte da eliminare:", partId);
if (partId !== "new" && partId !== undefined && partId !== null) {
console.log("Procedo con la cancellazione dal database");
$.ajax({
url: "delete_part.php",
method: "POST",
data: JSON.stringify({ part_id: partId }),
contentType: "application/json",
beforeSend: function () {
console.log(
"Invio richiesta AJAX a delete_part.php con part_id:",
partId,
);
},
success: function (response) {
console.log("Risposta da delete_part.php:", response);
if (response.success) {
$row.remove();
updateRowButtons();
updatePartsList();
clearCanvasMarkers();
} else {
alert("Errore nell'eliminazione: " + response.message);
}
},
error: function (xhr, status, error) {
console.log("Errore AJAX:", status, error);
alert(
"Errore nell'eliminazione: " +
error +
". Stato: " +
xhr.status +
" - " +
xhr.responseText,
);
},
});
} else {
console.log(
'Riga non salvata nel database (partId = "new" o non definito), rimuovo solo visivamente',
);
$row.remove();
updateRowButtons();
updatePartsList();
}
});
$(document).on("blur", ".part-description, .part-number", function () {
const $input = $(this);
const $row = $input.closest("tr");
const partNumber = $row.find(".part-number").val();
const partDescription = $row.find(".part-description").val().trim();
const $saveStatus = $row.find(".save-status");
const $saveLoading = $row.find(".save-loading");
const iddatadb = $("#partsModal").data("iddatadb");
console.log("Evento blur su input:", { partNumber, partDescription });
if (partDescription && iddatadb) {
$saveLoading.show();
$saveStatus.hide();
$.ajax({
url: "save_parts.php",
method: "POST",
data: JSON.stringify({
iddatadb: iddatadb,
parts: [
{
part_number: partNumber,
part_description: partDescription,
},
],
}),
contentType: "application/json",
success: function (response) {
if (response.success) {
if (response.part_id) {
$row.data("part-id", response.part_id);
console.log(
"Aggiornato partId della riga:",
response.part_id,
);
}
$saveLoading.hide();
$saveStatus.show();
updatePartsList();
setTimeout(() => $saveStatus.hide(), 2000);
} else {
alert("Errore nel salvataggio: " + response.message);
$saveLoading.hide();
}
},
error: function (xhr, status, error) {
alert("Errore nel salvataggio delle parti: " + error);
$saveLoading.hide();
},
});
}
});
function loadExistingParts(iddatadb) {
$.ajax({
url: "load_parts.php",
method: "GET",
data: { iddatadb: iddatadb },
success: function (response) {
if (response.success) {
$("#partsTableBody").empty();
if (response.parts.length > 0) {
response.parts.forEach((part) => {
const newRow = `
<tr data-part-id="${part.id}">
<td><input type="number" class="form-control form-control-sm part-number" value="${part.part_number}" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" value="${part.part_description}" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
$("#partsTableBody").append(newRow);
});
} else {
addNewRow(1);
}
updateRowButtons();
updatePartsList();
} else {
alert(
"Errore nel caricamento delle parti: " +
response.message,
);
addNewRow(1);
}
},
error: function (xhr, status, error) {
alert("Errore nel caricamento delle parti: " + error);
addNewRow(1);
},
});
}
function updatePartsList() {
$("#partsList").empty();
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
const listItem = `<li class="list-group-item" data-part-number="${partNumber}">${partNumber} - ${partDescription}</li>`;
$("#partsList").append(listItem);
}
});
}
let selectedPartNumber = null;
let markers = [];
let descriptionPosition = { x: 10, y: 10 };
let hasDescriptions = false;
$("#partsList").on("click", "li", function () {
selectedPartNumber = $(this).data("part-number");
console.log("Part number selezionato:", selectedPartNumber);
$(this).addClass("active").siblings().removeClass("active");
});
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
$("#markerContainer").on("click", function (e) {
console.log("Click sul markerContainer rilevato"); // Debug
if (selectedPartNumber !== null) {
const img = $("#samplePhoto");
const canvas = document.getElementById("photoCanvas");
const rect = canvas.getBoundingClientRect();
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
console.log("Coordinate cliccate (x, y):", x, y); // Debug
const existingMarker = markers.find(
(m) => m.partNumber == selectedPartNumber,
);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
} else {
markers.push({ partNumber: selectedPartNumber, x, y });
}
console.log("Markers aggiornati:", markers); // Debug
updateMarkers();
if (hasDescriptions) {
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
}
selectedPartNumber = null;
$("#partsList li").removeClass("active");
} else {
console.log("Nessun part number selezionato"); // Debug
}
});
function updateMarkers() {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const markerContainer = $("#markerContainer");
markerContainer.empty();
markers.forEach((marker) => {
const scaledX = marker.x * scale;
const scaledY = marker.y * scale;
console.log(
"Aggiungo marker:",
marker.partNumber,
"a posizione (scaledX, scaledY):",
scaledX,
scaledY,
); // Debug
const $marker = $(
`<div class="draggable-marker">${marker.partNumber}</div>`,
).css({
left: scaledX - 8 + "px",
top: scaledY - 8 + "px",
});
markerContainer.append($marker);
makeDraggable($marker, marker, scale);
});
}
function makeDraggable($element, item, scale) {
let isDragging = false;
let currentX = parseFloat($element.css("left")) || 0;
let currentY = parseFloat($element.css("top")) || 0;
let initialX, initialY;
$element.on("mousedown", function (e) {
e.preventDefault();
isDragging = true;
initialX = e.clientX - currentX;
initialY = e.clientY - currentY;
$element.css("z-index", 1001);
});
$(document).on("mousemove", function (e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
const container = $("#photoCanvas").parent();
const containerWidth = container.width();
const containerHeight = container.height();
const maxX = containerWidth - $element.width();
const maxY = containerHeight - $element.height();
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
$element.css({
left: currentX + "px",
top: currentY + "px",
});
if (item.partNumber) {
// È un marker
item.x = (currentX + 8) / scale;
item.y = (currentY + 8) / scale;
} else {
// È la lista
descriptionPosition.x = (currentX + 5) / scale;
descriptionPosition.y = (currentY + 5) / scale;
}
}
});
$(document).on("mouseup", function () {
isDragging = false;
$element.css("z-index", 1000);
});
}
function drawDescriptions(x, y) {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
const descriptionList = $("#descriptionList");
descriptionList.empty();
descriptionList.css({
display: "block",
top: y * scale + "px",
left: x * scale + "px",
width: "200px",
});
partsList.forEach((part) => {
descriptionList.append(`<div>${part}</div>`);
});
updateMarkers();
}
function clearCanvasMarkers() {
markers = [];
hasDescriptions = false;
$("#descriptionList").css("display", "none");
$("#markerContainer").empty();
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img.get(0).naturalWidth * scale;
canvas.height = img.get(0).naturalHeight * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img.get(0), 0, 0, canvas.width, canvas.height);
}
$("#addDescriptionsBtn").on("click", function () {
hasDescriptions = true;
descriptionPosition = { x: 10, y: 10 };
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
makeDraggable(
$("#descriptionList"),
descriptionPosition,
Math.min(
$("#photoCanvas").parent().width() /
$("#samplePhoto").get(0).naturalWidth,
$("#photoCanvas").parent().height() /
$("#samplePhoto").get(0).naturalHeight,
),
);
});
$("#removeAnnotationsBtn").on("click", function () {
clearCanvasMarkers();
});
$("#savePhotoBtn").on("click", function () {
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
canvas.width = img.get(0).naturalWidth;
canvas.height = img.get(0).naturalHeight;
ctx.drawImage(img.get(0), 0, 0);
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
if (hasDescriptions) {
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
ctx.fillRect(
descriptionPosition.x,
descriptionPosition.y,
200,
partsList.length * 12 + 10,
);
ctx.fillStyle = "#000000";
ctx.font = "10px Arial";
partsList.forEach((part, index) => {
ctx.fillText(
part,
descriptionPosition.x + 5,
descriptionPosition.y + 12 + index * 12,
);
});
}
markers.forEach((marker) => {
ctx.beginPath();
ctx.arc(marker.x, marker.y, 8, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fill();
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 8px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(marker.partNumber, marker.x, marker.y);
});
const dataURL = canvas.toDataURL("image/png");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const defaultName = `photo_${$("#partsModal").data("iddatadb")}_${timestamp}.png`;
const newName = prompt(
"Inserisci il nome del file (senza estensione):",
defaultName.split(".png")[0],
);
if (newName) {
const finalName = newName + "_" + timestamp + ".png";
$.ajax({
url: "save_annotated_photo.php",
method: "POST",
data: { dataURL: dataURL, filename: finalName },
success: function (response) {
if (response.success) {
alert(
"Foto salvata con successo: " + response.file_path,
);
} else {
alert("Errore nel salvataggio: " + response.message);
}
},
error: function (xhr, status, error) {
alert("Errore nel salvataggio della foto: " + error);
},
});
}
});
$(document).on("mouseenter", "tr", function () {
console.log("Mouse entrato su riga");
});
$(document).on("mouseleave", "tr", function () {
console.log("Mouse uscito da riga");
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
<?php
// Include il modal delle parti (già esistente)
include 'modal_parts.php';
?>
<script src="parts.js"></script>

View File

@ -1,668 +0,0 @@
$(document).ready(function () {
console.log("parts.js caricato correttamente");
const partsButtons = document.querySelectorAll(".parts-btn");
const partsModal = document.getElementById("partsModal");
const overlay = document.querySelector(".overlay");
partsButtons.forEach((button) => {
button.addEventListener("click", function () {
console.log(
"Pulsante Parts cliccato, iddatadb:",
$(this).data("iddatadb"),
);
const iddatadb = $(this).data("iddatadb");
const rowIndex = $(this).data("row");
console.log("Row index:", rowIndex);
const importRef = $("table tbody tr")
.eq(rowIndex)
.find("td")
.eq(1)
.text();
console.log("ImportRef:", importRef);
const description =
$("table tbody tr").eq(rowIndex).find("td").eq(2).text() ||
"Sconosciuto";
console.log("Description:", description);
$("#trfHeader").text(`${iddatadb} - ${importRef} - ${description}`);
$("#partsModal").data("iddatadb", iddatadb);
if (partsModal) {
console.log(
"Tento di aprire il modal, bootstrap:",
typeof bootstrap !== "undefined"
? "definito"
: "non definito",
);
try {
console.log("Inizio try per creare modal instance");
const modalInstance = new bootstrap.Modal(partsModal, {
focus: true,
}); // Forza la gestione del focus
console.log("Modal instance creata");
console.log("Inizio show del modal");
modalInstance.show();
console.log("Modal mostrato");
if (overlay) overlay.style.display = "none"; // Nascondi overlay solo se esiste
} catch (error) {
console.error("Errore nell'apertura del modal:", error);
}
} else {
console.error("Modal Parts non trovato");
}
console.log("Prima di chiamare loadPhoto e loadExistingParts");
loadPhoto(iddatadb)
.then(() => {
console.log("loadPhoto completata");
loadExistingParts(iddatadb)
.then(() => {
console.log("loadExistingParts completata");
})
.catch((error) => {
console.error(
"Errore in loadExistingParts:",
error,
);
});
})
.catch((error) => {
console.error("Errore in loadPhoto:", error);
});
console.log("Dopo l'avvio delle chiamate AJAX");
});
});
// Gestione della chiusura del modal Parts
if (closeBtn) {
closeBtn.addEventListener("click", function () {
partsModal.style.display = "none";
overlay.style.display = "none"; // Nascondi overlay
document.body.style.pointerEvents = "auto"; // Riattiva la pagina
});
}
if (partsModal) {
window.addEventListener("click", function (event) {
if (event.target === partsModal) {
partsModal.style.display = "none";
overlay.style.display = "none"; // Nascondi overlay
document.body.style.pointerEvents = "auto"; // Riattiva la pagina
}
});
}
function loadPhoto(iddatadb) {
console.log("Inizio loadPhoto per iddatadb:", iddatadb);
return $.ajax({
url: "load_photo.php",
method: "GET",
data: { iddatadb: iddatadb },
})
.then(function (response) {
console.log("Risposta loadPhoto:", response);
if (response.success && response.file_path) {
const img = $("#samplePhoto");
img.attr("src", response.file_path);
return new Promise((resolve) => {
img.on("load", function () {
const container = img.parent();
const canvas =
document.getElementById("photoCanvas");
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img[0].naturalWidth;
const scaleY =
containerHeight / img[0].naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img[0].naturalWidth * scale;
canvas.height = img[0].naturalHeight * scale;
canvas.style.width = `${containerWidth}px`;
canvas.style.height = `${containerHeight}px`;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
img.get(0),
0,
0,
canvas.width,
canvas.height,
);
updateMarkers();
resolve();
});
});
} else {
$("#samplePhoto").attr("src", "");
console.log(
"Nessuna foto trovata:",
response.message || "Nessun messaggio",
);
return Promise.resolve(); // Risolvi comunque la promessa
}
})
.catch(function (xhr, status, error) {
console.error("Errore in loadPhoto:", {
status,
error,
response: xhr.responseText,
});
$("#samplePhoto").attr("src", "");
alert("Errore nel caricamento della foto: " + error);
return Promise.reject(error); // Rifiuta la promessa in caso di errore
});
}
function addNewRow(nextPartNumber) {
const newRow = `
<tr data-part-id="new">
<td><input type="number" class="form-control form-control-sm part-number" value="${nextPartNumber || 1}" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
$("#partsTableBody").append(newRow);
updateRowButtons();
}
function updateRowButtons() {
const rowCount = $("#partsTableBody tr").length;
$("#partsTableBody tr").each(function (index) {
const $removeBtn = $(this).find(".remove-row");
if (rowCount > 1) {
$removeBtn.show();
} else {
$removeBtn.hide();
}
});
}
$(document).on("click", ".add-row", function (e) {
e.preventDefault();
console.log("Pulsante Aggiungi riga cliccato");
const maxPartNumber = Math.max(
...$("#partsTableBody tr")
.map(function () {
return parseInt($(this).find(".part-number").val()) || 0;
})
.get(),
);
addNewRow(maxPartNumber + 1);
updatePartsList();
});
$(document).on("click", ".remove-row", function (e) {
e.preventDefault();
console.log("Pulsante Rimuovi riga cliccato");
const $row = $(this).closest("tr");
const partId = $row.data("part-id");
console.log("ID parte da eliminare:", partId);
if (partId !== "new" && partId !== undefined && partId !== null) {
console.log("Procedo con la cancellazione dal database");
$.ajax({
url: "delete_part.php",
method: "POST",
data: JSON.stringify({ part_id: partId }),
contentType: "application/json",
beforeSend: function () {
console.log(
"Invio richiesta AJAX a delete_part.php con part_id:",
partId,
);
},
success: function (response) {
console.log("Risposta da delete_part.php:", response);
if (response.success) {
$row.remove();
updateRowButtons();
updatePartsList();
clearCanvasMarkers();
} else {
alert("Errore nell'eliminazione: " + response.message);
}
},
error: function (xhr, status, error) {
console.log("Errore AJAX:", status, error);
alert(
"Errore nell'eliminazione: " +
error +
". Stato: " +
xhr.status +
" - " +
xhr.responseText,
);
},
});
} else {
console.log(
'Riga non salvata nel database (partId = "new" o non definito), rimuovo solo visivamente',
);
$row.remove();
updateRowButtons();
updatePartsList();
}
});
$(document).on("blur", ".part-description, .part-number", function () {
const $input = $(this);
const $row = $input.closest("tr");
const partNumber = $row.find(".part-number").val();
const partDescription = $row.find(".part-description").val().trim();
const $saveStatus = $row.find(".save-status");
const $saveLoading = $row.find(".save-loading");
const iddatadb = $("#partsModal").data("iddatadb");
console.log("Evento blur su input:", { partNumber, partDescription });
if (partDescription && iddatadb) {
$saveLoading.show();
$saveStatus.hide();
$.ajax({
url: "save_parts.php",
method: "POST",
data: JSON.stringify({
iddatadb: iddatadb,
parts: [
{
part_number: partNumber,
part_description: partDescription,
},
],
}),
contentType: "application/json",
success: function (response) {
if (response.success) {
if (response.part_id) {
$row.data("part-id", response.part_id);
console.log(
"Aggiornato partId della riga:",
response.part_id,
);
}
$saveLoading.hide();
$saveStatus.show();
updatePartsList();
setTimeout(() => $saveStatus.hide(), 2000);
} else {
alert("Errore nel salvataggio: " + response.message);
$saveLoading.hide();
}
},
error: function (xhr, status, error) {
alert("Errore nel salvataggio delle parti: " + error);
$saveLoading.hide();
},
});
}
});
function loadExistingParts(iddatadb) {
console.log("Inizio loadExistingParts per iddatadb:", iddatadb);
return $.ajax({
url: "load_parts.php",
method: "GET",
data: { iddatadb: iddatadb },
})
.then(function (response) {
console.log("Risposta loadExistingParts:", response);
if (response.success) {
$("#partsTableBody").empty();
if (response.parts.length > 0) {
response.parts.forEach((part) => {
const newRow = `
<tr data-part-id="${part.id}">
<td><input type="number" class="form-control form-control-sm part-number" value="${part.part_number}" style="width: 80px;"></td>
<td><input type="text" class="form-control form-control-sm part-description" value="${part.part_description}" style="width: 100%;"></td>
<td>
<button type="button" class="btn btn-success btn-sm add-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-trash fa-xs"></i></button>
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check fa-xs"></i></span>
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin fa-xs"></i></span>
</td>
</tr>
`;
$("#partsTableBody").append(newRow);
});
} else {
addNewRow(1);
}
updateRowButtons();
updatePartsList();
} else {
console.log(
"Errore nel caricamento delle parti:",
response.message,
);
addNewRow(1);
}
return Promise.resolve(); // Risolvi la promessa
})
.catch(function (xhr, status, error) {
console.error("Errore in loadExistingParts:", {
status,
error,
response: xhr.responseText,
});
alert("Errore nel caricamento delle parti: " + error);
addNewRow(1);
return Promise.reject(error); // Rifiuta la promessa in caso di errore
});
}
function updatePartsList() {
$("#partsList").empty();
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
const listItem = `<li class="list-group-item" data-part-number="${partNumber}">${partNumber} - ${partDescription}</li>`;
$("#partsList").append(listItem);
}
});
}
let selectedPartNumber = null;
let markers = [];
let descriptionPosition = { x: 10, y: 10 };
let hasDescriptions = false;
$("#partsList").on("click", "li", function () {
selectedPartNumber = $(this).data("part-number");
console.log("Part number selezionato:", selectedPartNumber);
$(this).addClass("active").siblings().removeClass("active");
});
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
$("#markerContainer").on("click", function (e) {
console.log("Click sul markerContainer rilevato");
if (selectedPartNumber !== null) {
const img = $("#samplePhoto");
const canvas = document.getElementById("photoCanvas");
const rect = canvas.getBoundingRect();
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const x = (e.clientX - rect.left) / scale;
const y = (e.clientY - rect.top) / scale;
console.log("Coordinate cliccate (x, y):", x, y);
const existingMarker = markers.find(
(m) => m.partNumber == selectedPartNumber,
);
if (existingMarker) {
existingMarker.x = x;
existingMarker.y = y;
} else {
markers.push({ partNumber: selectedPartNumber, x, y });
}
console.log("Markers aggiornati:", markers);
updateMarkers();
if (hasDescriptions) {
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
}
selectedPartNumber = null;
$("#partsList li").removeClass("active");
} else {
console.log("Nessun part number selezionato");
}
});
function updateMarkers() {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const markerContainer = $("#markerContainer");
markerContainer.empty();
markers.forEach((marker) => {
const scaledX = marker.x * scale;
const scaledY = marker.y * scale;
console.log(
"Aggiungo marker:",
marker.partNumber,
"a posizione (scaledX, scaledY):",
scaledX,
scaledY,
);
const $marker = $(
`<div class="draggable-marker">${marker.partNumber}</div>`,
).css({
left: scaledX - 8 + "px",
top: scaledY - 8 + "px",
});
markerContainer.append($marker);
makeDraggable($marker, marker, scale);
});
}
function makeDraggable($element, item, scale) {
let isDragging = false;
let currentX = parseFloat($element.css("left")) || 0;
let currentY = parseFloat($element.css("top")) || 0;
let initialX, initialY;
$element.on("mousedown", function (e) {
e.preventDefault();
isDragging = true;
initialX = e.clientX - currentX;
initialY = e.clientY - currentY;
$element.css("z-index", 1001);
});
$(document).on("mousemove", function (e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
const container = $("#photoCanvas").parent();
const containerWidth = container.width();
const containerHeight = container.height();
const maxX = containerWidth - $element.width();
const maxY = containerHeight - $element.height();
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
$element.css({
left: currentX + "px",
top: currentY + "px",
});
if (item.partNumber) {
item.x = (currentX + 8) / scale;
item.y = (currentY + 8) / scale;
} else {
descriptionPosition.x = (currentX + 5) / scale;
descriptionPosition.y = (currentY + 5) / scale;
}
}
});
$(document).on("mouseup", function () {
isDragging = false;
$element.css("z-index", 1000);
});
}
function drawDescriptions(x, y) {
const img = $("#samplePhoto");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
const descriptionList = $("#descriptionList");
descriptionList.empty();
descriptionList.css({
display: "block",
top: y * scale + "px",
left: x * scale + "px",
width: "200px",
});
partsList.forEach((part) => {
descriptionList.append(`<div>${part}</div>`);
});
updateMarkers();
}
function clearCanvasMarkers() {
markers = [];
hasDescriptions = false;
$("#descriptionList").css("display", "none");
$("#markerContainer").empty();
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
const container = img.parent();
const containerWidth = container.width();
const containerHeight = container.height();
const scaleX = containerWidth / img.get(0).naturalWidth;
const scaleY = containerHeight / img.get(0).naturalHeight;
const scale = Math.min(scaleX, scaleY);
canvas.width = img.get(0).naturalWidth * scale;
canvas.height = img.get(0).naturalHeight * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img.get(0), 0, 0, canvas.width, canvas.height);
}
$("#addDescriptionsBtn").on("click", function () {
hasDescriptions = true;
descriptionPosition = { x: 10, y: 10 };
drawDescriptions(descriptionPosition.x, descriptionPosition.y);
makeDraggable(
$("#descriptionList"),
descriptionPosition,
Math.min(
$("#photoCanvas").parent().width() /
$("#samplePhoto").get(0).naturalWidth,
$("#photoCanvas").parent().height() /
$("#samplePhoto").get(0).naturalHeight,
),
);
});
$("#removeAnnotationsBtn").on("click", function () {
clearCanvasMarkers();
});
$("#savePhotoBtn").on("click", function () {
const canvas = document.getElementById("photoCanvas");
const img = $("#samplePhoto");
const ctx = canvas.getContext("2d");
canvas.width = img.get(0).naturalWidth;
canvas.height = img.get(0).naturalHeight;
ctx.drawImage(img.get(0), 0, 0);
const partsList = [];
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
if (partNumber && partDescription) {
partsList.push(`${partNumber} ${partDescription}`);
}
});
if (hasDescriptions) {
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
ctx.fillRect(
descriptionPosition.x,
descriptionPosition.y,
200,
partsList.length * 12 + 10,
);
ctx.fillStyle = "#000000";
ctx.font = "10px Arial";
partsList.forEach((part, index) => {
ctx.fillText(
part,
descriptionPosition.x + 5,
descriptionPosition.y + 12 + index * 12,
);
});
}
markers.forEach((marker) => {
ctx.beginPath();
ctx.arc(marker.x, marker.y, 8, 0, 2 * Math.PI);
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fill();
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 1;
ctx.stroke();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 8px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(marker.partNumber, marker.x, marker.y);
});
const dataURL = canvas.toDataURL("image/png");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const defaultName = `photo_${$("#partsModal").data("iddatadb")}_${timestamp}.png`;
const newName = prompt(
"Inserisci il nome del file (senza estensione):",
defaultName.split(".png")[0],
);
if (newName) {
const finalName = newName + "_" + timestamp + ".png";
$.ajax({
url: "save_annotated_photo.php",
method: "POST",
data: { dataURL: dataURL, filename: finalName },
success: function (response) {
if (response.success) {
alert(
"Foto salvata con successo: " + response.file_path,
);
} else {
alert("Errore nel salvataggio: " + response.message);
}
},
error: function (xhr, status, error) {
alert("Errore nel salvataggio della foto: " + error);
},
});
}
});
$(document).on("mouseenter", "tr", function () {
console.log("Mouse entrato su riga");
});
$(document).on("mouseleave", "tr", function () {
console.log("Mouse uscito da riga");
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,68 +0,0 @@
<?php
// HTML per il modal delle foto
?>
<div id="photosModal" class="modal">
<div class="modal-content">
<span class="close-btn">×</span>
<div id="popupContent">
<p>Loading...</p>
</div>
</div>
</div>
<div id="imageModal" class="image-modal">
<span class="image-modal-close">&times;</span>
<img class="image-modal-content" id="enlargedImage">
</div>
<style>
.photo-item {
transition: background-color 0.3s;
}
.photo-item:hover {
background-color: #f8f9fa;
}
#dropArea.highlight {
border-color: #28a745;
background-color: #e9ecef;
}
.image-modal {
display: none;
position: fixed;
z-index: 1100;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.8);
}
.image-modal-content {
margin: auto;
display: block;
max-width: 90%;
max-height: 90vh;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.image-modal-close {
position: absolute;
top: 15px;
right: 35px;
color: #fff;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.image-modal-close:hover,
.image-modal-close:focus {
color: #bbb;
text-decoration: none;
}
</style>

View File

@ -1,428 +0,0 @@
<?php
// photos_popup.php
include('include/headscript.php');
// Includi Fabric.js solo per questa pagina
?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<?php
// Includi l'autoloader di Composer
require_once __DIR__ . '/../../vendor/autoload.php';
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\RoundBlockSizeMode;
use Endroid\QrCode\Writer\PngWriter;
// Abilita logging per debug
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/photos_popup_debug.log');
// Log iniziale
error_log("Richiesta a photos_popup.php: " . print_r($_GET, true));
// Carica le variabili d'ambiente
try {
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load();
} catch (Exception $e) {
error_log("Errore nel caricamento del file .env: " . $e->getMessage());
?>
<div class="popup-content">
<p>Errore: Impossibile caricare il file di configurazione.</p>
</div>
<?php
exit;
}
// Verifica che BASE_URL sia definito
if (!isset($_ENV['BASE_URL'])) {
error_log("Errore: la variabile BASE_URL non è definita nel file .env");
?>
<div class="popup-content">
<p>Errore: Variabile BASE_URL non definita.</p>
</div>
<?php
exit;
}
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Verifica che almeno uno degli ID sia passato
$iddatadb = isset($_GET['iddatadb']) && !empty($_GET['iddatadb']) ? intval($_GET['iddatadb']) : null;
$idquotations = isset($_GET['idquotations']) && !empty($_GET['idquotations']) ? intval($_GET['idquotations']) : null;
if (!$iddatadb && !$idquotations) {
error_log("Errore: ID riga o ID quotations non fornito");
?>
<div class="popup-content">
<p>Errore: ID riga o ID quotations non fornito.</p>
</div>
<?php
exit;
}
if ($iddatadb && $idquotations) {
error_log("Errore: Non è possibile specificare sia iddatadb che idquotations");
?>
<div class="popup-content">
<p>Errore: Non è possibile specificare sia iddatadb che idquotations.</p>
</div>
<?php
exit;
}
// Determina quale ID e tabella usare
$paramName = $iddatadb ? 'iddatadb' : 'id'; // Usa 'id' per quotations
$paramValue = $iddatadb ?: $idquotations;
$table = $iddatadb ? 'datadb' : 'quotations';
$field = $iddatadb ? 'sample_code' : 'description'; // Usa 'description' per quotations
$photoTable = 'datadb_photos'; // Usa sempre datadb_photos
$photoParamName = $iddatadb ? 'iddatadb' : 'idquotations'; // Usa 'idquotations' per datadb_photos
// Recupera i dettagli della riga
try {
$stmt = $pdo->prepare("SELECT {$paramName}, {$field} FROM {$table} WHERE {$paramName} = ?");
$stmt->execute([$paramValue]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
error_log("Errore: Riga non trovata per {$paramName} = {$paramValue}");
?>
<div class="popup-content">
<p>Errore: Riga non trovata.</p>
</div>
<?php
exit;
}
} catch (Exception $e) {
error_log("Errore query dettagli riga: " . $e->getMessage());
?>
<div class="popup-content">
<p>Errore: Impossibile recuperare i dettagli della riga.</p>
</div>
<?php
exit;
}
$id = $row[$paramName];
$code = $row[$field] ?? 'Non disponibile';
// Recupera le foto associate alla riga
try {
$stmt = $pdo->prepare("SELECT id, file_path, file_name, description, uploaded_at FROM {$photoTable} WHERE {$photoParamName} = ? ORDER BY uploaded_at DESC");
$stmt->execute([$paramValue]);
$photos = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
error_log("Errore query foto: " . $e->getMessage());
$photos = []; // Imposta array vuoto in caso di errore
}
// Definisci il percorso base per le foto
$photoBasePath = '../photostrf/';
// Usa la variabile d'ambiente BASE_URL
$baseUrl = rtrim($_ENV['BASE_URL'], '/');
$uploadUrl = $iddatadb
? $baseUrl . "/upload_photos_mobile.php?iddatadb=" . $iddatadb
: $baseUrl . "/upload_photos_mobile.php?idquotations=" . $idquotations;
// Genera il QR code con endroid/qr-code 6.0.6
$qrCodeDir = '../photostrf/qrcodes/';
if (!is_dir($qrCodeDir)) {
mkdir($qrCodeDir, 0755, true);
}
$qrCodeFile = $qrCodeDir . "qrcode_{$id}.png";
$writer = new PngWriter();
$qrCode = new QrCode(
data: $uploadUrl,
encoding: new Encoding('UTF-8'),
errorCorrectionLevel: ErrorCorrectionLevel::Low,
size: 150,
margin: 10,
roundBlockSizeMode: RoundBlockSizeMode::Margin,
foregroundColor: new Color(0, 0, 0),
backgroundColor: new Color(255, 255, 255)
);
$result = $writer->write($qrCode);
$result->saveToFile($qrCodeFile);
?>
<div class="popup-content">
<div id="loader" class="loader" style="display: none;">
<div style="text-align: center; color: white;">
<i class="fas fa-spinner fa-spin" style="font-size: 40px;"></i>
<p>Caricamento in corso...</p>
</div>
</div>
<h3>Manage Photos</h3>
<p><strong>ID:</strong> <?= htmlspecialchars($id) ?></p>
<p><strong>Code:</strong> <?= htmlspecialchars($code) ?></p>
<!-- QR Code per il caricamento da mobile -->
<div style="text-align: center; margin-bottom: 20px;">
<p>Scan the QR Code with the mobile to take photo with camera:</p>
<img src="../photostrf/qrcodes/qrcode_<?= htmlspecialchars($id) ?>.png" alt="QR Code" style="max-width: 150px;">
<p style="margin-top: 10px;">
<a href="<?= htmlspecialchars($uploadUrl) ?>" target="_blank"><?= htmlspecialchars($uploadUrl) ?></a>
</p>
</div>
<!-- Area drag-and-drop per il caricamento delle foto -->
<div id="dropArea" style="border: 2px dashed #ccc; padding: 20px; text-align: center; margin-bottom: 20px;">
<p>Drag the photo here or click to select</p>
<input type="file" id="photoInput" multiple accept="image/*" style="display: none;">
</div>
<!-- Area per la webcam -->
<div id="webcamArea" style="display: none; text-align: center; margin-bottom: 20px;">
<p>Webcam Preview</p>
<select id="webcamSelect" style="margin-bottom: 10px; padding: 5px;">
<option value="">Select a webcam</option>
</select>
<video id="webcamVideo" autoplay playsinline style="max-width: 100%; max-height: 300px;"></video>
<div style="margin-top: 10px;">
<button id="captureBtn" style="padding: 10px 20px; background: #28a745; color: white; border: none; cursor: pointer;">Capture Photo</button>
<button id="closeWebcamBtn" style="padding: 10px 20px; background: #dc3545; color: white; border: none; cursor: pointer;">Close Webcam</button>
</div>
</div>
<button id="openWebcamBtn" style="padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; margin-bottom: 20px;">Take Photo with Webcam</button>
<!-- Elenco delle foto -->
<div id="photosList">
<?php if (empty($photos)): ?>
<p>Nessuna foto presente.</p>
<?php else: ?>
<?php foreach ($photos as $photo): ?>
<?php
$filePath = $photoBasePath . $photo['file_path'];
$fileExists = file_exists($filePath);
?>
<div class="photo-item" data-photo-id="<?= $photo['id'] ?>" style="display: flex; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px;">
<div style="flex: 1;">
<?php if ($fileExists): ?>
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>" alt="<?= htmlspecialchars($photo['file_name']) ?>" class="thumbnail" style="max-width: 100px; max-height: 100px; margin-right: 10px; cursor: pointer;">
<?php else: ?>
<p style="color: red;">[File non trovato]</p>
<?php endif; ?>
<p style="margin: 0;">
<strong>Nome:</strong> <?= htmlspecialchars($photo['file_name']) ?><br>
<strong>Caricata il:</strong> <?= htmlspecialchars($photo['uploaded_at']) ?><br>
<strong>Descrizione:</strong> <?= htmlspecialchars($photo['description'] ?? 'Nessuna descrizione') ?>
</p>
</div>
<button class="delete-photo-btn" data-photo-id="<?= $photo['id'] ?>" style="background: none; border: none; color: #dc3545; cursor: pointer;">
<i class="fas fa-trash"></i>
</button>
</div>
<?php endforeach; ?>
<!-- Bottone Crea Collage -->
<button id="createCollageBtn" style="padding: 10px 20px; background: #ffc107; color: white; border: none; cursor: pointer; margin-top: 20px;">Crea Collage</button>
<?php endif; ?>
</div>
<!-- Modale per l'immagine ingrandita -->
<div id="imageModal" class="image-modal" style="display: none;">
<span class="image-modal-close">&times;</span>
<img id="enlargedImage" class="image-modal-content" src="" alt="Immagine ingrandita">
</div>
<!-- Modale per collage -->
<div id="collageModal" class="modal" style="display: none; position: fixed; z-index: 1002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.8);">
<div class="modal-content" style="background: white; margin: 5% auto; padding: 20px; width: 80%; max-width: 1200px; position: relative;">
<span class="close-collage" style="position: absolute; top: 10px; right: 20px; font-size: 30px; font-weight: bold; color: #333; cursor: pointer; z-index: 1003; background: #fff; padding: 5px 10px; border-radius: 50%;">&times;</span>
<h3>Crea Collage</h3>
<!-- Lista foto selezionabili -->
<div id="collagePhotoList" style="max-height: 200px; overflow-y: auto; margin-bottom: 20px;">
<?php foreach ($photos as $photo): ?>
<div style="display: inline-block; margin: 10px;">
<input type="checkbox" class="photo-checkbox" data-path="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>">
<img src="../photostrf/<?= htmlspecialchars($photo['file_path']) ?>" alt="" style="width: 80px; height: 80px;">
</div>
<?php endforeach; ?>
</div>
<!-- Bottone per aggiungere selezionate al canvas -->
<button id="addToCanvasBtn">Aggiungi Selezionate al Canvas</button>
<!-- Canvas per editing -->
<canvas id="collageCanvas" width="960" height="720" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
<!-- Pannello dei livelli -->
<div id="layersPanel" style="width: 120px; max-height: 600px; overflow-y: auto; background: #f8f9fa; padding: 10px; position: absolute; right: 0; top: 60px;">
<h4 style="margin: 0 0 10px 0; font-size: 16px;">Livelli</h4>
<ul id="layersList" style="list-style: none; padding: 0;"></ul>
</div>
<!-- Bottoni azioni -->
<div style="margin-top: 20px; display: flex; flex-wrap: wrap; gap: 5px;">
<button id="saveCollageBtn" title="Salva il collage"><i class="fas fa-save"></i></button>
<button id="bringToFrontBtn" title="Porta in primo piano"><i class="fas fa-arrow-up"></i></button>
<button id="sendToBackBtn" title="Manda in fondo"><i class="fas fa-arrow-down"></i></button>
<button id="bringForwardBtn" title="Sposta avanti di un livello"><i class="fas fa-arrow-circle-up"></i></button>
<button id="sendBackwardBtn" title="Sposta indietro di un livello"><i class="fas fa-arrow-circle-down"></i></button>
<button id="cropImageBtn" title="Ritaglia immagine selezionata" disabled><i class="fas fa-crop"></i></button>
<button id="applyCropBtn" title="Applica ritaglio" disabled><i class="fas fa-crop"></i> Applica</button>
<button id="cancelCropBtn" title="Annulla ritaglio" disabled><i class="fas fa-crop"></i> Annulla</button>
<button id="removeBackgroundBtn" title="Rimuovi sfondo immagine selezionata" disabled><i class="fas fa-eraser"></i> Rimuovi Sfondo</button>
<button id="removeImageBtn" title="Rimuovi immagine selezionata" disabled><i class="fas fa-trash-alt"></i> Rimuovi</button>
<button id="undoBtn" title="Annulla ultima azione" disabled><i class="fas fa-undo"></i></button>
<p id="backgroundRemovalInstruction" style="display: none; color: #007bff;">Clicca sull'immagine per selezionare il colore dello sfondo da rimuovere</p>
</div>
</div>
</div>
</div>
<style>
.photo-item {
transition: background-color 0.3s;
}
.photo-item:hover {
background-color: #f8f9fa;
}
#dropArea.highlight {
border-color: #28a745;
background-color: #e9ecef;
}
.image-modal {
display: none;
position: fixed;
z-index: 1001;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.8);
}
.image-modal-content {
margin: auto;
display: block;
max-width: 90%;
max-height: 90vh;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.image-modal-close {
position: absolute;
top: 15px;
right: 35px;
color: #fff;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.image-modal-close:hover,
.image-modal-close:focus {
color: #bbb;
text-decoration: none;
}
.loader {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1002;
display: flex;
align-items: center;
justify-content: center;
}
.loader .fa-spinner {
font-size: 40px;
color: white;
}
.loader p {
margin-top: 10px;
font-size: 16px;
color: white;
}
/* Stile per i pulsanti del modale collage */
#collageModal button {
padding: 8px 12px;
margin: 5px;
border: none;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
gap: 5px;
font-size: 14px;
}
#saveCollageBtn {
background: #28a745;
color: white;
}
#bringToFrontBtn,
#sendToBackBtn,
#bringForwardBtn,
#sendBackwardBtn {
background: #007bff;
color: white;
padding: 8px;
}
#cropImageBtn,
#applyCropBtn,
#cancelCropBtn,
#removeBackgroundBtn,
#removeImageBtn,
#undoBtn {
background: #ffc107;
color: white;
}
#collageModal button:disabled {
background: #ccc;
cursor: not-allowed;
}
#collageModal button i {
font-size: 16px;
}
/* Stile per il pannello dei livelli */
#layersPanel {
z-index: 1002;
}
#layersPanel li {
margin-bottom: 5px;
}
#layersPanel img {
width: 50px;
height: 50px;
border: 2px solid #ccc;
cursor: pointer;
object-fit: cover;
}
#layersPanel img:hover {
border-color: #007bff;
}
</style>

View File

@ -0,0 +1,30 @@
<?php
require_once(__DIR__ . '/class/db-functions.php');
header('Content-Type: application/json; charset=utf-8');
try {
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$sql = "
SELECT
pm.id,
c.client_name,
pm.price_ref,
pm.price,
pm.source_file,
pm.created_at
FROM price_mapping pm
LEFT JOIN clients c ON pm.idclient = c.idclient
ORDER BY pm.id DESC
";
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($data, JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
echo json_encode(['error' => $e->getMessage()]);
}

View File

@ -1,76 +0,0 @@
<?php
header('Content-Type: application/json');
require_once 'class/db-functions.php';
$response = ["success" => false, "message" => ""];
try {
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
throw new Exception("Invalid request method.");
}
// Recupera e sanifica i dati
$id = intval($_POST['id']);
$name = trim($_POST['name']);
$header_row = intval($_POST['header_row']);
$start_column = trim($_POST['start_column']);
$description = trim($_POST['description'] ?? '');
$target_table = trim($_POST['target_table']);
$idclient = intval($_POST['client_id'] ?? 0); // Usa client_id dal form
$clientname = trim($_POST['client_name'] ?? ''); // Usa client_name dal form
$idschema = intval($_POST['idschema'] ?? 0); // Nuovo campo
$schemaname = trim($_POST['schemaname'] ?? ''); // Corretto da schemamaname
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null; // Aggiunto idroutine
$button_size = trim($_POST['button_size'] ?? 'medium'); // Nuovo campo
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff'); // Nuovo campo
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff'); // Nuovo campo
$button_label = trim($_POST['button_label'] ?? 'Click Me'); // Nuovo campo
// Controllo sui campi obbligatori
if (empty($id) || empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idschema <= 0) {
throw new Exception("All fields marked with * are required, including schema.");
}
// Validazione del idclient
if ($idclient <= 0) {
throw new Exception("Please select a valid client.");
}
// Connessione al database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Aggiorna il database, includendo i nuovi campi
$stmt = $pdo->prepare("UPDATE excel_templates
SET name = ?, header_row = ?, start_column = ?, description = ?, target_table = ?,
idclient = ?, clientname = ?, schemaname = ?, idschema = ?, idroutine = ?,
button_size = ?, button_bg_color = ?, button_text_color = ?, button_label = ?,
updated_at = NOW()
WHERE id = ?");
$stmt->execute([
$name,
$header_row,
$start_column,
$description,
$target_table,
$idclient,
$clientname,
$schemaname,
$idschema,
$idroutine,
$button_size,
$button_bg_color,
$button_text_color,
$button_label,
$id
]);
// rowCount potrebbe essere 0 se non ci sono modifiche, quindi consideriamo comunque un successo
$response["success"] = true;
$response["message"] = "Template updated successfully!";
} catch (Exception $e) {
$response["message"] = $e->getMessage();
}
// Restituisce un JSON per il fetch
echo json_encode($response);

View File

@ -1,117 +0,0 @@
<?php
// Sopprime eventuali output di errori (li logghiamo invece di mostrarli)
ob_start();
ini_set('display_errors', 1);
error_reporting(E_ALL);
// Inizia la sessione
session_start();
// Includi PHPSpreadsheet
require_once '../../vendor/autoload.php';
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => ''];
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel_file'])) {
$template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0;
$header_row = isset($_POST['header_row']) ? intval($_POST['header_row']) : 1;
$start_column = isset($_POST['start_column']) ? intval($_POST['start_column']) : 1;
$file = $_FILES['excel_file'];
$fileError = $file['error'];
if ($fileError === UPLOAD_ERR_OK) {
// Recupera l'ID dell'utente loggato (assumiamo sia disponibile in $iduserlogin)
if (!isset($iduserlogin)) {
$iduserlogin = 1; // Valore di default
error_log("Warning: iduserlogin non definito, usando 1 come default");
}
// Genera il nome del file rinominato
$timestamp = date('YmdHis');
$originalFilename = basename($file['name']);
$newFilename = "{$iduserlogin}-{$timestamp}-{$originalFilename}";
$importFolder = __DIR__ . '/imported_trf/';
if (!file_exists($importFolder)) {
mkdir($importFolder, 0777, true);
}
$destination = $importFolder . $newFilename;
// Sposta il file
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new Exception("Errore durante lo spostamento del file in $destination");
}
error_log("File spostato con successo in: $destination");
// Carica il file rinominato con PHPSpreadsheet
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($destination);
$worksheet = $spreadsheet->getActiveSheet();
$highestRow = $worksheet->getHighestRow();
$highestColumn = $worksheet->getHighestColumn();
$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);
$startRow = max(1, $header_row);
$startColumn = max(1, $start_column);
// Debug dei parametri
error_log("Processing - startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
// Validazione degli indici
if ($startRow > $highestRow) {
$response['error'] = "La riga di partenza ($startRow) supera il numero totale di righe ($highestRow).";
} elseif ($startColumn > $highestColumnIndex) {
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex).";
} else {
$excelData = [];
// Estrai la riga degli header
$headerRowData = [];
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$cell = $worksheet->getCell($columnLetter . $header_row);
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
$headerRowData[] = htmlspecialchars($cellValue ?: '');
}
// Estrai i dati a partire dalla riga successiva
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
$rowData = [];
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$cell = $worksheet->getCell($columnLetter . $row);
$cellValue = $cell ? $cell->getCalculatedValue() : ''; // Usa getCalculatedValue per le formule
$rowData[] = htmlspecialchars($cellValue ?: '');
}
if (!empty(array_filter($rowData))) {
$excelData[] = $rowData;
}
}
// Salva i dati in sessione
$_SESSION['excel_data'] = $excelData;
$_SESSION['template_id'] = $template_id;
$_SESSION['headers'] = $headerRowData; // Salva gli header in sessione
$response['rows'] = $excelData;
$response['columns'] = $headerRowData; // Usa gli header reali
$response['template_id'] = $template_id;
$response['filename'] = $newFilename; // Aggiungi il nome del file rinominato
}
} else {
$response['error'] = "Errore nell'upload del file: Codice errore $fileError.";
}
} else {
$response['error'] = "Richiesta non valida.";
}
} catch (Exception $e) {
$response['error'] = "Errore durante il caricamento del file: " . $e->getMessage();
error_log("Exception in process_import_xls.php: " . $e->getMessage());
}
// Pulisce qualsiasi output indesiderato
ob_end_clean();
// Invia la risposta JSON
header('Content-Type: application/json');
echo json_encode($response);
exit;

View File

@ -1,170 +0,0 @@
<?php
// Sopprime eventuali output di errori (li logghiamo invece di mostrarli)
ob_start();
ini_set('display_errors', 0);
error_reporting(E_ALL);
// Inizia la sessione
session_start();
// Includi PHPSpreadsheet e la classe DBHandler
require_once '../../vendor/autoload.php';
require_once __DIR__ . '/class/db-functions.php';
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'apply_routine' => false];
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel_file'])) {
$template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0;
$header_row = isset($_POST['header_row']) ? intval($_POST['header_row']) : 1;
$start_column = isset($_POST['start_column']) ? intval($_POST['start_column']) : 1;
// Debug del template_id ricevuto
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
error_log("Converted template_id: $template_id");
$file = $_FILES['excel_file'];
$fileError = $file['error'];
if ($fileError === UPLOAD_ERR_OK) {
// Recupera l'ID dell'utente loggato
if (!isset($iduserlogin)) {
$iduserlogin = 1;
error_log("Warning: iduserlogin non definito, usando 1 come default");
}
// Genera il nome del file rinominato
$timestamp = date('YmdHis');
$originalFilename = basename($file['name']);
$newFilename = "{$iduserlogin}-{$timestamp}-{$originalFilename}";
$importFolder = __DIR__ . '/imported_trf/';
if (!file_exists($importFolder)) {
mkdir($importFolder, 0777, true);
}
$destination = $importFolder . $newFilename;
// Sposta il file
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new Exception("Errore durante lo spostamento del file in $destination");
}
error_log("File spostato con successo in: $destination");
// Connessione al database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Recupera il mapping da template_mapping
$stmt = $pdo->prepare("SELECT field_id AS excel_column, field_id AS mysql_column, data_type, is_required, default_value, is_manual FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Debug dei mapping
error_log("Mappings found for template_id $template_id: " . print_r($mappings, true));
if (empty($mappings)) {
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
} else {
// Carica il file rinominato con PHPSpreadsheet
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($destination);
$worksheet = $spreadsheet->getActiveSheet();
$highestRow = $worksheet->getHighestRow();
$highestColumn = $worksheet->getHighestColumn();
$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);
$startRow = max(1, $header_row);
$startColumn = max(1, $start_column);
// Debug dei parametri
error_log("Processing - template_id: $template_id, startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
// Validazione degli indici
if ($startRow > $highestRow) {
$response['error'] = "La riga di partenza ($startRow) supera il numero totale di righe ($highestRow).";
} elseif ($startColumn > $highestColumnIndex) {
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex).";
} else {
$excelData = [];
// Estrai la riga degli header
$headerRowData = [];
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$cell = $worksheet->getCell($columnLetter . $header_row);
$cellValue = $cell ? $cell->getCalculatedValue() : '';
$headerRowData[] = htmlspecialchars($cellValue ?: '');
}
// Estrai i dati a partire dalla riga successiva, includendo excelrow
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
$rowData = [];
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col);
$cell = $worksheet->getCell($columnLetter . $row);
$cellValue = $cell ? $cell->getCalculatedValue() : '';
$rowData[] = htmlspecialchars($cellValue ?: '');
}
if (!empty(array_filter($rowData))) {
$excelData[] = ['data' => $rowData, 'excelrow' => $row];
}
}
// Recupera routine dal template
$stmt = $pdo->prepare("SELECT idroutine, idclient FROM excel_templates WHERE id = ?");
$stmt->execute([$template_id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if ($template && $template['idroutine']) {
$stmtRoutine = $pdo->prepare("SELECT idroutine, name, filename, headerrow, instruction FROM routine WHERE idroutine = ?");
$stmtRoutine->execute([$template['idroutine']]);
$routineData = $stmtRoutine->fetch(PDO::FETCH_ASSOC);
if ($routineData) {
$response['apply_routine'] = true;
$response['routine_data'] = [
'name' => $routineData['name'] ?? 'Routine Sconosciuta',
'instruction' => $routineData['instruction'] ?? 'Nessuna descrizione disponibile',
'filename' => $routineData['filename'] ?? '',
'headerrow' => $routineData['headerrow'] ?? $header_row
];
error_log("Routine rilevata per template {$template_id}: " . print_r($routineData, true));
} else {
error_log("Errore: Nessuna routine trovata per idroutine {$template['idroutine']}");
}
} else {
error_log("Nessuna routine associata al template {$template_id}");
}
// Aggiungi idclient alla risposta
$response['idclient'] = $template['idclient'] ?? null;
// Salva i dati in sessione
$_SESSION['excel_data'] = $excelData;
$_SESSION['template_id'] = $template_id;
$_SESSION['headers'] = $headerRowData;
$_SESSION['mappings'] = $mappings;
// Includi excel_data nella risposta JSON in ogni caso
$response['excel_data'] = $excelData;
$response['rows'] = array_column($excelData, 'data');
$response['columns'] = $headerRowData;
$response['template_id'] = $template_id;
$response['filename'] = $newFilename;
}
}
} else {
$response['error'] = "Errore nell'upload del file: Codice errore $fileError.";
}
} else {
$response['error'] = "Richiesta non valida.";
}
} catch (Exception $e) {
$response['error'] = "Errore durante il caricamento del file: " . $e->getMessage();
error_log("Exception in process_import_xls2.php: " . $e->getMessage());
}
// Pulisce qualsiasi output indesiderato
ob_end_clean();
// Invia la risposta JSON
header('Content-Type: application/json');
echo json_encode($response);
exit;

View File

@ -1,67 +0,0 @@
<?php
header('Content-Type: application/json');
require_once 'class/db-functions.php';
$response = ["success" => false, "message" => ""];
try {
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
throw new Exception("Invalid request method.");
}
// Recupera e sanifica i dati
$name = trim($_POST['name']);
$header_row = intval($_POST['header_row']);
$start_column = trim($_POST['start_column']);
$description = trim($_POST['description'] ?? '');
$target_table = trim($_POST['target_table']);
$idclient = intval($_POST['client_id'] ?? 0);
$clientname = trim($_POST['client_name'] ?? '');
$idschema = intval($_POST['idschema'] ?? 0);
$schemaname = trim($_POST['schemaname'] ?? '');
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null;
$button_size = trim($_POST['button_size'] ?? 'medium');
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff');
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
$button_label = trim($_POST['button_label'] ?? 'Click Me');
// Controllo sui campi obbligatori
if (empty($name) || empty($header_row) || empty($start_column) || empty($target_table) || $idclient <= 0 || $idschema <= 0) {
throw new Exception("All fields marked with * are required, including client and schema.");
}
// Connessione al database
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Inserisci il nuovo template
$stmt = $pdo->prepare("
INSERT INTO excel_templates
(name, header_row, start_column, description, target_table, idclient, clientname, idschema, schemaname, idroutine,
button_size, button_bg_color, button_text_color, button_label, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
");
$stmt->execute([
$name,
$header_row,
$start_column,
$description,
$target_table,
$idclient,
$clientname,
$idschema,
$schemaname,
$idroutine,
$button_size,
$button_bg_color,
$button_text_color,
$button_label
]);
$response["success"] = true;
$response["message"] = "Template created successfully!";
} catch (Exception $e) {
$response["message"] = $e->getMessage();
}
echo json_encode($response);

View File

@ -1,574 +0,0 @@
<?php
// Abilita errori per debug
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/quotations_debug.log');
if (!file_exists(__DIR__ . '/quotations_debug.log')) {
file_put_contents(__DIR__ . '/quotations_debug.log', "Inizio operazioni alle " . date('Y-m-d H:i:s') . "\n", FILE_APPEND);
}
// Log iniziale
error_log("Inizio operazioni alle " . date('Y-m-d H:i:s'));
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Recupera l'ID dell'utente loggato
$user_id = $iduserlogin ?? 1;
// Gestione creazione nuova quotation (crea record vuoto su conferma)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create') {
$description = '';
$customer = '';
try {
$stmt = $pdo->prepare("INSERT INTO quotations (description, customer, iduser) VALUES (?, ?, ?)");
$success = $stmt->execute([$description, $customer, $user_id]);
if ($success) {
$newId = $pdo->lastInsertId();
error_log("Creata nuova quotation ID: $newId");
header("Location: quotations.php?edit_id=" . $newId . "&status=success&message=" . urlencode("Quotation creata con successo"));
} else {
error_log("Errore: Impossibile creare la quotation, nessun ID generato.");
header("Location: quotations.php?status=error&message=" . urlencode("Errore durante la creazione della quotation"));
}
} catch (PDOException $e) {
error_log("Errore PDO durante la creazione della quotation: " . $e->getMessage());
header("Location: quotations.php?status=error&message=" . urlencode("Errore database: " . $e->getMessage()));
}
exit;
}
// Gestione modifica quotation
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'update' && isset($_POST['id'])) {
$id = intval($_POST['id']);
$description = $_POST['description'] ?? '';
$customer = $_POST['customer'] ?? '';
try {
$stmt = $pdo->prepare("UPDATE quotations SET description = ?, customer = ? WHERE id = ? AND iduser = ?");
$stmt->execute([$description, $customer, $id, $user_id]);
error_log("Modificata quotation ID: $id");
header("Location: quotations.php?status=success&message=" . urlencode("Quotation modificata con successo"));
} catch (PDOException $e) {
error_log("Errore PDO durante la modifica della quotation: " . $e->getMessage());
header("Location: quotations.php?status=error&message=" . urlencode("Errore database: " . $e->getMessage()));
}
exit;
}
// Gestione cancellazione quotation
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete' && isset($_POST['id'])) {
$id = intval($_POST['id']);
try {
$stmt = $pdo->prepare("DELETE FROM quotations WHERE id = ? AND iduser = ?");
$stmt->execute([$id, $user_id]);
error_log("Cancellata quotation ID: $id");
header("Location: quotations.php?status=success&message=" . urlencode("Quotation cancellata con successo"));
} catch (PDOException $e) {
error_log("Errore PDO durante la cancellazione della quotation: " . $e->getMessage());
header("Location: quotations.php?status=error&message=" . urlencode("Errore database: " . $e->getMessage()));
}
exit;
}
// Recupera tutte le quotations per l'utente
try {
$stmt = $pdo->prepare("SELECT * FROM quotations WHERE iduser = ? ORDER BY creation_date DESC");
$stmt->execute([$user_id]);
$quotations = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Errore PDO durante il recupero delle quotations: " . $e->getMessage());
$quotations = [];
}
// Verifica se è richiesta la modifica di una quotation
$editQuotation = null;
if (isset($_GET['edit_id'])) {
$editId = intval($_GET['edit_id']);
try {
$stmt = $pdo->prepare("SELECT * FROM quotations WHERE id = ? AND iduser = ?");
$stmt->execute([$editId, $user_id]);
$editQuotation = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$editQuotation) {
error_log("Nessuna quotation trovata per id: $editId");
}
} catch (PDOException $e) {
error_log("Errore PDO durante il recupero della quotation per modifica: " . $e->getMessage());
$editQuotation = null;
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2Lw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<style>
.cell-changed {
background-color: #fff3b0 !important;
transition: background-color 0.3s ease;
}
input.manual-input,
select.manual-input {
background-color: #fff3cd;
}
input.required-input,
select.required-input {
background-color: #f8d7da;
}
input,
select,
textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 5px;
font-size: 14px;
}
.action-btn {
padding: 6px 8px;
margin-right: 5px;
border: none;
border-radius: 5px;
cursor: pointer;
width: 35px;
box-sizing: border-box;
}
.save-btn {
background-color: #28a745;
color: white;
}
.delete-btn {
background-color: #dc3545;
color: white;
}
.photos-btn {
background-color: #007bff;
color: white;
}
.parts-btn {
background-color: #ffc107;
color: white;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group textarea {
height: 100px;
resize: vertical;
}
.flash-success {
background-color: #d4edda !important;
transition: background-color 0.3s ease;
}
.quotation-actions {
margin-top: 20px;
}
.modal {
display: none;
position: fixed;
z-index: 1050;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 600px;
border-radius: 8px;
position: relative;
}
.close-btn {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close-btn:hover,
.close-btn:focus {
color: #000;
text-decoration: none;
}
.modal.fade {
z-index: 1060 !important;
}
.modal-backdrop {
z-index: 1055 !important;
}
.overlay.toggle-icon {
z-index: 1000 !important;
}
.alert {
margin-bottom: 15px;
padding: 10px;
border-radius: 4px;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
<title>Gestione Quotations - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Gestione Quotations</h6>
</div>
</div>
</div>
<div class="card-body">
<?php if (isset($_GET['status']) && isset($_GET['message'])): ?>
<div class="alert alert-<?= $_GET['status'] === 'success' ? 'success' : 'danger' ?>">
<?= htmlspecialchars(urldecode($_GET['message'])) ?>
</div>
<?php endif; ?>
<?php if ($editQuotation): ?>
<!-- Modifica Quotation -->
<h6 class="mb-3">Modifica Quotation ID: <?= $editQuotation['id'] ?></h6>
<form id="editForm" method="post">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="<?= $editQuotation['id'] ?>">
<div class="form-group">
<label for="description">Descrizione</label>
<textarea id="description" name="description" class="manual-input required-input" required><?= htmlspecialchars($editQuotation['description']) ?></textarea>
</div>
<div class="form-group">
<label for="customer">Cliente</label>
<input type="text" id="customer" name="customer" class="manual-input required-input" value="<?= htmlspecialchars($editQuotation['customer']) ?>" required>
</div>
<button type="submit" class="btn btn-primary">Salva Modifiche</button>
<a href="quotations.php" class="btn btn-secondary">Torna alla Lista</a>
</form>
<div class="quotation-actions">
<h6 class="mb-3">Azioni</h6>
<button type="button" class="photos-btn action-btn" data-row="0" data-idquotations="<?= $editQuotation['id'] ?>" style="background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; flex: 1;"><i class="fas fa-camera"></i></button>
<button type="button" class="parts-btn action-btn" data-iddatadb="" data-idquotations="<?= $editQuotation['id'] ?>" data-row="0">Parti</button>
</div>
<?php else: ?>
<!-- Lista Quotations -->
<div class="mb-3">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">Crea Nuova Quotation</button>
</div>
<h6 class="mb-3">Quotations Esistenti</h6>
<table id="quotationsTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Data Creazione</th>
<th>Descrizione</th>
<th>Cliente</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($quotations as $index => $row): ?>
<tr data-id="<?= $row['id'] ?>">
<td><?= htmlspecialchars($row['id']) ?></td>
<td><?= htmlspecialchars($row['creation_date']) ?></td>
<td>
<textarea name="description" class="cell-input manual-input form-control"><?= htmlspecialchars($row['description']) ?></textarea>
</td>
<td>
<input type="text" name="customer" class="cell-input manual-input form-control" value="<?= htmlspecialchars($row['customer']) ?>">
</td>
<td>
<button type="button" class="save-btn action-btn edit-btn" data-id="<?= $row['id'] ?>" title="Salva Modifiche"><i class="fas fa-save"></i></button>
<button type="button" class="delete-btn action-btn" data-id="<?= $row['id'] ?>" title="Cancella" data-bs-toggle="modal" data-bs-target="#deleteModal"><i class="fas fa-trash"></i></button>
<button type="button" class="photos-btn action-btn" data-entity-type="quotation" data-idquotations="<?= $row['id'] ?>" data-row="<?= $index ?>" title="Photos"><i class="fas fa-camera"></i></button>
<button type="button" class="parts-btn action-btn" data-entity-type="quotation" data-idquotations="<?= $row['id'] ?>" data-row="<?= $index ?>" title="Parts"><i class="fas fa-puzzle-piece"></i></button>
<a href="quotations.php?edit_id=<?= $row['id'] ?>" class="btn btn-secondary action-btn" title="Modifica Dettagliata"><i class="fas fa-edit"></i></a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Modal per conferma creazione nuova quotation -->
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createModalLabel">Conferma Creazione Nuova Quotation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Vuoi creare una nuova quotation?</p>
<form id="createModalForm" method="post">
<input type="hidden" name="action" value="create">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary" id="confirmCreate">Conferma</button>
</div>
</div>
</div>
</div>
<!-- Modal per conferma cancellazione -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Conferma Cancellazione</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Sicuro di voler cancellare questa quotation?</p>
<form id="deleteForm" method="post">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="deleteQuotationId">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-danger" id="confirmDelete">Conferma</button>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<div id="partsModalContainer"></div>
<div id="annotationsModalContainer"></div>
<?php include 'photos_functions.php'; ?>
<?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="photos.js"></script>
<script src="annotationsModal.js"></script>
<script src="partsTable.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Mostra messaggi di stato se presenti
const urlParams = new URLSearchParams(window.location.search);
const status = urlParams.get('status');
const message = urlParams.get('message');
if (status && message) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${status === 'success' ? 'success' : 'danger'} temp-alert`;
alertDiv.textContent = decodeURIComponent(message);
document.querySelector('.card-body').prepend(alertDiv);
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
$(document).on('click', '.parts-btn', function() {
const idquotations = $(this).data('idquotations');
$.ajax({
url: 'modal_partsTable.php',
method: 'GET',
data: {
idquotations: idquotations
},
success: function(response) {
$('#partsModalContainer').html(response);
const modalElement = document.getElementById('partsModal');
if (!modalElement) return;
$("#trfHeader").text(`Quotation #${idquotations}`);
$("#partsModal").data("idquotations", idquotations);
let modal = bootstrap.Modal.getInstance(modalElement) || new bootstrap.Modal(modalElement, {
backdrop: true
});
modal.show();
if (typeof window.loadParts === 'function') window.loadParts(null, idquotations);
},
error: function(xhr, status, error) {
alert('Errore nel caricamento del modale: ' + error);
}
});
});
$(document).on('hidden.bs.modal', '#partsModal', function() {
$('#partsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
$(document).on('hidden.bs.modal', '#annotationsModal', function() {
$('#annotationsModalContainer').empty();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
// Inizializza DataTables se non siamo in modalità modifica
if (!document.querySelector('#editForm')) {
$('#quotationsTable').DataTable({
paging: true,
searching: true,
ordering: true,
info: true,
autoWidth: false,
responsive: true
});
}
// Quando il modale di creazione si apre, nascondi l'overlay
$('#createModal').on('show.bs.modal', function() {
$('.overlay.toggle-icon').css('display', 'none');
});
// Quando il modale si chiude, ripristina l'overlay
$('#createModal').on('hide.bs.modal', function() {
$('.overlay.toggle-icon').css('display', '');
});
// Gestione conferma creazione nel modal
document.getElementById('confirmCreate').addEventListener('click', function() {
const createModal = bootstrap.Modal.getInstance(document.getElementById('createModal'));
createModal.hide();
const form = document.getElementById('createModalForm');
const formData = new FormData(form);
fetch('quotations.php', {
method: 'POST',
body: formData
}).then(response => {
if (!response.ok) {
throw new Error('Errore HTTP: ' + response.status);
}
return response.text();
}).then(() => {
window.location.href = 'quotations.php?status=success&message=' + encodeURIComponent('Quotation creata con successo');
}).catch(error => {
console.error('Errore durante la creazione della quotation:', error);
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger temp-alert';
alertDiv.textContent = 'Errore durante la creazione della quotation: ' + error.message;
document.querySelector('.card-body').prepend(alertDiv);
setTimeout(() => {
alertDiv.remove();
}, 5000);
});
});
// Gestione modifica inline e save nella lista
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function() {
const row = this.closest('tr');
const id = row.dataset.id;
const description = row.querySelector('textarea[name="description"]').value;
const customer = row.querySelector('input[name="customer"]').value;
const formData = new FormData();
formData.append('action', 'update');
formData.append('id', id);
formData.append('description', description);
formData.append('customer', customer);
fetch('quotations.php', {
method: 'POST',
body: formData
}).then(response => {
if (response.ok) {
row.classList.add('flash-success');
setTimeout(() => row.classList.remove('flash-success'), 500);
alert('Quotation modificata con successo!');
} else {
alert('Errore durante la modifica.');
}
}).catch(error => {
console.error('Errore durante la modifica della quotation:', error);
alert('Errore durante la modifica: ' + error.message);
});
});
});
// Gestione apertura modal di cancellazione
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
document.getElementById('deleteQuotationId').value = id;
});
});
// Gestione conferma cancellazione nel modal
document.getElementById('confirmDelete').addEventListener('click', function() {
document.getElementById('deleteForm').submit();
});
// I bottoni photos e parts usano gli script esistenti (photos.js, parts.js), passando data-idquotations
});
</script>
<!-- Modale per le foto in quotations.php -->
</body>
</html>

View File

@ -1,11 +0,0 @@
{
"@odata.context": "https:\/\/93.43.5.102\/limsapi\/api\/odata\/$metadata#Rapporto\/$entity",
"IdRapporto": 515081,
"DataUltimaModifica": "2025-05-14T18:11:21.02+02:00",
"DaLeggere": false,
"CodiceRapporto": "2523026",
"Data": "2025-05-14T14:05:30+02:00",
"Versione": 0,
"DataStampa": "2025-05-14T14:11:00+02:00",
"Firmato": true
}

File diff suppressed because it is too large Load Diff

View File

@ -1,62 +0,0 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$data = json_decode(file_get_contents("php://input"), true);
if (!isset($data['template_id'], $data['excel_column'], $data['mysql_column'], $data['tablename'])) {
echo json_encode(["success" => false, "message" => "Missing required fields"]);
exit;
}
// Rimuove l'associazione
$stmtDelete = $pdo->prepare("
DELETE FROM excel_column_mappings
WHERE template_id = ? AND excel_column = ? AND mysql_column = ? AND tablename = ?
");
$result = $stmtDelete->execute([
$data['template_id'],
$data['excel_column'],
$data['mysql_column'],
$data['tablename']
]);
if (!$result) {
echo json_encode(["success" => false, "message" => "Failed to delete mapping"]);
exit;
}
// Dopo la rimozione, aggiorna la lista delle colonne disponibili
$stmtColumns = $pdo->prepare("SHOW COLUMNS FROM " . $data['tablename']);
$stmtColumns->execute();
$all_mysql_columns = array_column($stmtColumns->fetchAll(PDO::FETCH_ASSOC), 'Field');
$stmtHeader = $pdo->prepare("SELECT headerexcel FROM excel_column_mappings WHERE template_id = ? LIMIT 1");
$stmtHeader->execute([$data['template_id']]);
$headerRow = $stmtHeader->fetch(PDO::FETCH_ASSOC);
$xls_headers = isset($headerRow['headerexcel']) ? explode(",", $headerRow['headerexcel']) : [];
// Ricalcola le colonne non associate
$stmtMappings = $pdo->prepare("SELECT excel_column, mysql_column FROM excel_column_mappings WHERE template_id = ?");
$stmtMappings->execute([$data['template_id']]);
$existingMappings = $stmtMappings->fetchAll(PDO::FETCH_ASSOC);
$mapped_xls_columns = array_column($existingMappings, 'excel_column');
$mapped_mysql_columns = array_column($existingMappings, 'mysql_column');
$remaining_xls_columns = array_diff($xls_headers, $mapped_xls_columns);
$remaining_mysql_columns = array_diff($all_mysql_columns, $mapped_mysql_columns);
echo json_encode([
"success" => true,
"remaining_xls_columns" => array_values($remaining_xls_columns),
"remaining_mysql_columns" => array_values($remaining_mysql_columns)
]);
exit;

View File

@ -1,63 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$iddatadb = $data['iddatadb'] ?? null;
$parts = $data['parts'] ?? [];
if (!$iddatadb || empty($parts)) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
exit;
}
try {
$pdo->beginTransaction();
// Elimina tutte le parti esistenti per l'iddatadb per evitare conflitti di unicità
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE iddatadb = :iddatadb");
$stmt->execute([':iddatadb' => $iddatadb]);
// Prepara l'inserimento delle nuove parti
$stmt = $pdo->prepare("
INSERT INTO identification_parts
(iddatadb, part_number, part_description, mix, created_at, updated_at)
VALUES (:iddatadb, :part_number, :part_description, :mix, NOW(), NOW())
");
$part_ids = [];
foreach ($parts as $part) {
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
if (!$partNumber || !$partDescription) {
throw new PDOException("Numero parte o descrizione mancante per parte: " . json_encode($part));
}
$stmt->execute([
':iddatadb' => $iddatadb,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
]);
$part_ids[] = $pdo->lastInsertId();
}
$pdo->commit();
echo json_encode([
'success' => true,
'part_ids' => $part_ids,
'message' => 'Parti rinumerate con successo'
]);
} catch (PDOException $e) {
$pdo->rollBack();
echo json_encode([
'success' => false,
'message' => 'Errore nel salvataggio: ' . $e->getMessage()
]);
}

View File

@ -1,63 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$data = json_decode(file_get_contents('php://input'), true);
$idquotations = $data['idquotations'] ?? null;
$parts = $data['parts'] ?? [];
if (!$idquotations || empty($parts)) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
exit;
}
try {
$pdo->beginTransaction();
// Elimina tutte le parti esistenti per idquotations
$stmt = $pdo->prepare("DELETE FROM identification_parts WHERE idquotations = :idquotations");
$stmt->execute([':idquotations' => $idquotations]);
// Prepara l'inserimento delle nuove parti
$stmt = $pdo->prepare("
INSERT INTO identification_parts
(idquotations, part_number, part_description, mix, created_at, updated_at)
VALUES (:idquotations, :part_number, :part_description, :mix, NOW(), NOW())
");
$part_ids = [];
foreach ($parts as $part) {
$partNumber = $part['part_number'] ?? null;
$partDescription = $part['part_description'] ?? '';
$mix = $part['mix'] ?? 'N';
if (!$partNumber || !$partDescription) {
throw new PDOException("Numero parte o descrizione mancante per parte: " . json_encode($part));
}
$stmt->execute([
':idquotations' => $idquotations,
':part_number' => $partNumber,
':part_description' => $partDescription,
':mix' => $mix
]);
$part_ids[] = $pdo->lastInsertId();
}
$pdo->commit();
echo json_encode([
'success' => true,
'part_ids' => $part_ids,
'message' => 'Parti rinumerate con successo'
]);
} catch (PDOException $e) {
$pdo->rollBack();
echo json_encode([
'success' => false,
'message' => 'Errore nel salvataggio: ' . $e->getMessage()
]);
}

View File

@ -1,76 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
error_reporting(E_ALL);
ini_set('display_errors', 1);
$file = $_FILES['file'] ?? null;
$filename = $_POST['filename'] ?? null;
$iddatadb = $_POST['iddatadb'] ?? null;
if (!$file || !$filename || !$iddatadb) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti']);
exit;
}
if (!preg_match('/^[a-zA-Z0-9_-]+\.(png|jpg|jpeg)$/', $filename)) {
echo json_encode(['success' => false, 'message' => 'Nome file non valido']);
exit;
}
if (!is_numeric($iddatadb)) {
echo json_encode(['success' => false, 'message' => 'ID non valido']);
exit;
}
$allowedTypes = ['image/png', 'image/jpeg'];
if (!in_array($file['type'], $allowedTypes)) {
echo json_encode(['success' => false, 'message' => 'Formato file non supportato']);
exit;
}
try {
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$stmt = $pdo->prepare("SELECT iddatadb FROM datadb WHERE iddatadb = :iddatadb");
$stmt->execute([':iddatadb' => $iddatadb]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'iddatadb non valido']);
exit;
}
$dirPath = '../photostrf/annotated';
if (!file_exists($dirPath)) {
mkdir($dirPath, 0755, true);
}
$filePath = $dirPath . '/' . $filename;
if (file_exists($filePath)) {
echo json_encode(['success' => false, 'message' => 'File già esistente']);
exit;
}
if (!move_uploaded_file($file['tmp_name'], $filePath)) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio del file']);
exit;
}
$stmt = $pdo->prepare("
INSERT INTO datadb_photos (iddatadb, file_path, file_name, uploaded_at, uploaded_by)
VALUES (:iddatadb, :file_path, :file_name, NOW(), :uploaded_by)
");
$stmt->execute([
':iddatadb' => $iddatadb,
':file_path' => $filePath,
':file_name' => $filename,
':uploaded_by' => $iduserlogin
]);
echo json_encode([
'success' => true,
'file_path' => $filePath,
'message' => 'Foto salvata con successo e registrata nel DB'
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}

View File

@ -1,76 +0,0 @@
<?php
header('Content-Type: application/json');
include('include/headscript.php');
error_reporting(E_ALL);
ini_set('display_errors', 1);
$file = $_FILES['file'] ?? null;
$filename = $_POST['filename'] ?? null;
$idquotations = $_POST['idquotations'] ?? null;
if (!$file || !$filename || !$idquotations || !isset($iduserlogin)) {
echo json_encode(['success' => false, 'message' => 'Dati mancanti o utente non autenticato']);
exit;
}
if (!preg_match('/^[a-zA-Z0-9_-]+\.(png|jpg|jpeg)$/', $filename)) {
echo json_encode(['success' => false, 'message' => 'Nome file non valido']);
exit;
}
if (!is_numeric($idquotations)) {
echo json_encode(['success' => false, 'message' => 'ID non valido']);
exit;
}
$allowedTypes = ['image/png', 'image/jpeg'];
if (!in_array($file['type'], $allowedTypes)) {
echo json_encode(['success' => false, 'message' => 'Formato file non supportato']);
exit;
}
try {
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$stmt = $pdo->prepare("SELECT id FROM quotations WHERE id = :idquotations");
$stmt->execute([':idquotations' => $idquotations]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'idquotations non valido']);
exit;
}
$dirPath = '../photostrf/annotated';
if (!file_exists($dirPath)) {
mkdir($dirPath, 0755, true);
}
$filePath = $dirPath . '/' . $filename;
if (file_exists($filePath)) {
echo json_encode(['success' => false, 'message' => 'File già esistente']);
exit;
}
if (!move_uploaded_file($file['tmp_name'], $filePath)) {
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio del file']);
exit;
}
$stmt = $pdo->prepare("
INSERT INTO datadb_photos (idquotations, file_path, file_name, uploaded_at, uploaded_by)
VALUES (:idquotations, :file_path, :file_name, NOW(), :uploaded_by)
");
$stmt->execute([
':idquotations' => $idquotations,
':file_path' => $filePath,
':file_name' => $filename,
':uploaded_by' => $iduserlogin
]);
echo json_encode([
'success' => true,
'file_path' => $filePath,
'message' => 'Foto salvata con successo e registrata nel DB'
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]);
}

View File

@ -1,412 +0,0 @@
<?php include('include/headscript.php');
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
die("Invalid template ID");
}
$id = intval($_GET['id']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx, idclient, clientname, idschema, schemaname, schemajson FROM excel_templates WHERE id = ?");
$stmt->execute([$id]);
$template = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$template) {
die("Template not found");
}
$clientName = $template['clientname'] ?: '';
$schemaName = $template['schemaname'] ?: '';
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
$isSchemajsonEmpty = empty(trim($template['schemajson']));
// Recupera i campi dalla tabella template_mapping
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label FROM template_mapping WHERE template_id = ?");
$stmt->execute([$id]);
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<?php include('top_stat_widget.php'); ?>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center">
<div>
<h6 class="mb-0">Configure Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></span></h6>
<p>
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
</p>
</div>
</div>
</div>
<div class="card-body">
<div class="mb-4">
<label class="form-label">Upload XLS Example:</label>
<input type="file" id="xlsUpload" class="form-control">
<small id="uploadStatus">
<?php if (!empty($template['sample_xlsx'])): ?>
Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
<?php else: ?>
No file uploaded yet.
<?php endif; ?>
</small>
</div>
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center mb-2">
<h5>Schema Fields Configuration</h5>
<button id="updateSchemaButton" class="btn btn-primary ms-2" data-empty="<?php echo $isSchemajsonEmpty ? 'true' : 'false'; ?>">
<?php echo $isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details'; ?>
</button>
</div>
<table id="schemaFieldsTable" class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>ID</th>
<th>Type</th>
<th>Mapping</th>
<th>Default Value</th>
<th>Action</th>
</tr>
</thead>
<tbody id="schemaFieldsBody">
<?php foreach ($mappings as $mapping): ?>
<tr>
<td><?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($mapping['field_id'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
<td>
<select class="form-select mapping-select" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
<option value="">Select Option</option>
<option value="xls" <?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'selected' : ''; ?>>Map to XLS Column</option>
<option value="manual" <?php echo $mapping['is_manual'] ? 'selected' : ''; ?>>Manual Entry</option>
</select>
<select class="form-select xls-columns" style="display:<?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'block' : 'none'; ?>" data-id="<?php echo $mapping['id']; ?>"></select>
</td>
<td>
<input type="text" class="form-control manual-default" placeholder="Default value" value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:<?php echo $mapping['is_manual'] ? 'block' : 'none'; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
</td>
<td>
<?php if ($mapping['excel_column']): ?>
<button class="btn btn-danger btn-sm remove-association" data-id="<?php echo $mapping['id']; ?>">Remove</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="mt-4">
<h5>Current Configurations</h5>
<ul id="configurationsList" class="list-group border p-3"></ul>
</div>
<div class="mt-4 text-end">
<a href="templates_dashboard.php" class="btn btn-primary"> Back to Template Dashboard</a>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
let availableXlsColumns = [];
document.getElementById('xlsUpload').addEventListener('change', function(event) {
let file = event.target.files[0];
if (!file) return;
let formData = new FormData();
formData.append("xls_file", file);
formData.append("template_id", <?php echo $id; ?>);
let statusText = document.getElementById('uploadStatus');
statusText.innerText = "Uploading...";
fetch('upload_xls_example.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (!data.success) {
statusText.innerText = "❌ Upload failed: " + data.message;
return;
}
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
processXLSX(file);
})
.catch(error => {
statusText.innerText = "❌ Upload failed. Check console.";
console.error(error);
});
});
function processXLSX(file) {
let reader = new FileReader();
reader.onload = function(e) {
let data = new Uint8Array(e.target.result);
let workbook = XLSX.read(data, {
type: 'array'
});
let sheet = workbook.Sheets[workbook.SheetNames[0]];
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1;
let startColumn = parseInt(document.getElementById('startColumn').textContent.charCodeAt(0) - 64) || 1;
let sheetData = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: "",
raw: false,
range: 0
});
if (!sheetData[rowIndex - 1]) {
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
select.innerHTML = '<option value="">No headers found</option>';
});
return;
}
let headers = sheetData[rowIndex - 1].slice(startColumn - 1).filter(header => header !== undefined && header.trim() !== "");
availableXlsColumns = [...headers]; // Memorizza tutte le colonne disponibili
updateXlsDropdowns();
};
reader.readAsArrayBuffer(file);
}
function updateXlsDropdowns() {
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
.filter(select => select.style.display === 'block' && select.value)
.map(select => select.value);
document.querySelectorAll('select.xls-columns').forEach(select => {
let currentValue = select.value || select.dataset.currentXls || '';
let options = availableXlsColumns
.filter(col => !usedColumns.includes(col) || col === currentValue)
.map(col => `<option value="${col}" ${col === currentValue ? 'selected' : ''}>${col}</option>`)
.join('');
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
select.dataset.currentXls = currentValue;
});
}
document.addEventListener("DOMContentLoaded", function() {
let templateId = <?php echo $id; ?>;
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
let isSchemajsonEmpty = <?php echo json_encode($isSchemajsonEmpty); ?>;
async function loadClientAndSchemaNames() {
if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) {
let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('clientName').textContent = data.value[0].Nominativo || 'N/A';
}
if (schemaId > 0) {
let response = await fetch(`get_schemi.php?id=${schemaId}`);
let data = await response.json();
if (response.ok && data.value?.length) document.getElementById('schemaName').textContent = data.value[0].Nome || 'N/A';
}
}
loadClientAndSchemaNames();
async function updateSchemaDetails() {
if (!schemaId) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-warning">No schema associated.</td></tr>';
return;
}
let updateSchemaButton = document.getElementById('updateSchemaButton');
updateSchemaButton.disabled = true;
updateSchemaButton.textContent = 'Loading...';
try {
let response = await fetch(`get_schema_details.php?id=${schemaId}`);
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
let data = JSON.parse(await response.text());
if (!data.SchemiCustomFieldsDettagli) throw new Error('Missing "SchemiCustomFieldsDettagli"');
await saveSchemaJson(templateId, JSON.stringify(data));
alert('Schema updated successfully. Refresh the page to see changes.');
} catch (error) {
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="6" class="text-danger">Error: ' + error.message + '</td></tr>';
} finally {
updateSchemaButton.disabled = false;
updateSchemaButton.textContent = 'Update Schema Details';
}
}
async function saveSchemaJson(templateId, schemaJson) {
let response = await fetch('update_schemajson.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
template_id: templateId,
schemajson: schemaJson
})
});
let data = await response.json();
if (!data.success) throw new Error(data.message || 'Failed to save schema JSON');
}
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
function loadConfigurations() {
fetch('load_mappings.php?template_id=' + templateId)
.then(response => response.json())
.then(data => {
if (!data.success) return;
let configurationsList = document.getElementById('configurationsList');
configurationsList.innerHTML = '';
data.mappings.forEach(mapping => {
let li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center';
li.innerHTML = `${mapping.field_label} (${mapping.field_id}) → ${mapping.excel_column || 'Manual: ' + (mapping.manual_default || 'N/A')} <button class="btn btn-danger btn-sm removeMapping" data-id="${mapping.id}">X</button>`;
configurationsList.appendChild(li);
});
})
.catch(error => console.error("❌ Error fetching mappings:", error));
}
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('mapping-select')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
let manualInput = tr.querySelector('.manual-default');
let removeBtn = tr.querySelector('.remove-association');
if (event.target.value === 'xls') {
xlsSelect.style.display = 'block';
manualInput.style.display = 'none';
manualInput.value = '';
removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
} else if (event.target.value === 'manual') {
xlsSelect.style.display = 'none';
manualInput.style.display = 'block';
removeBtn.style.display = 'none';
} else {
xlsSelect.style.display = 'none';
manualInput.style.display = 'none';
manualInput.value = '';
removeBtn.style.display = 'none';
}
saveMapping(mappingId, event.target.value, manualInput.value, xlsSelect.value);
updateXlsDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
if (event.target.classList.contains('xls-columns')) {
let tr = event.target.closest('tr');
let mappingId = event.target.getAttribute('data-id');
let manualInput = tr.querySelector('.manual-default');
let removeBtn = tr.querySelector('.remove-association');
removeBtn.style.display = event.target.value ? 'inline-block' : 'none';
saveMapping(mappingId, 'xls', manualInput.value, event.target.value);
updateXlsDropdowns();
}
});
document.getElementById('schemaFieldsBody').addEventListener('input', function(event) {
if (event.target.classList.contains('manual-default')) {
let tr = event.target.closest('tr');
let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id');
let xlsSelect = tr.querySelector('.xls-columns');
saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
}
});
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
if (event.target.classList.contains('remove-association')) {
let mappingId = event.target.getAttribute('data-id');
let tr = event.target.closest('tr');
let xlsSelect = tr.querySelector('.xls-columns');
xlsSelect.value = '';
xlsSelect.style.display = 'none';
tr.querySelector('.mapping-select').value = '';
event.target.style.display = 'none';
saveMapping(mappingId, '', '', null);
updateXlsDropdowns();
}
});
function saveMapping(mappingId, mappingType, defaultValue, excelColumn) {
fetch('save_mapping_json.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: mappingId,
mapping_type: mappingType,
excel_column: mappingType === 'xls' ? excelColumn : null,
manual_default: mappingType === 'manual' ? defaultValue : null,
tablename: "<?php echo $template['target_table']; ?>"
})
}).then(response => response.json())
.then(data => {
if (data.success) loadConfigurations();
else console.error("❌ Error saving mapping:", data.message);
})
.catch(error => console.error("❌ Fetch error:", error));
}
document.getElementById('configurationsList').addEventListener('click', function(event) {
if (event.target.classList.contains('removeMapping')) {
let mappingId = event.target.getAttribute('data-id');
fetch('remove_mapping.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: mappingId
})
}).then(response => response.json())
.then(data => {
if (data.success) {
loadConfigurations();
updateXlsDropdowns(); // Aggiorna la tendina dopo la rimozione
} else console.error("❌ Error removing mapping:", data.message);
})
.catch(error => console.error("❌ Fetch error:", error));
}
});
loadConfigurations();
});
</script>
</body>
</html>

View File

@ -1,47 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
$response = ['success' => false, 'message' => ''];
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['iddatadb'])) {
throw new Exception('Richiesta non valida');
}
$iddatadb = intval($_POST['iddatadb']);
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// Campi non modificabili
$excludeFields = ['importdate', 'status', 'user_id', 'filename_import', 'importreferencecode', 'limscode'];
// Prepara i dati da aggiornare
$updates = [];
$values = [];
foreach ($_POST as $key => $value) {
if ($key !== 'iddatadb' && !in_array($key, $excludeFields)) {
$updates[] = "$key = ?";
$values[] = htmlspecialchars($value);
}
}
$values[] = $iddatadb;
if (empty($updates)) {
throw new Exception('Nessun dato da aggiornare');
}
$sql = "UPDATE datadb SET " . implode(', ', $updates) . " WHERE iddatadb = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute($values);
$response['success'] = true;
$response['message'] = 'Riga aggiornata con successo';
} catch (Exception $e) {
$response['message'] = $e->getMessage();
error_log("Errore in save_edited_row.php: " . $e->getMessage());
}
echo json_encode($response);
exit;

View File

@ -1,92 +0,0 @@
<?php
include('include/headscript.php');
header('Content-Type: application/json');
$response = ['success' => false, 'message' => ''];
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['iddatadb'])) {
throw new Exception('Richiesta non valida');
}
$iddatadb = intval($_POST['iddatadb']);
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$data = $_POST;
$details = [];
// 1. Estrarre i dettagli da POST
foreach ($data as $key => $value) {
if (preg_match('/^details(\d+)field_value$/', $key, $matches)) {
$id = $matches[1];
$details[$id] = $value;
}
}
// 2. Recupera i valori esistenti da import_data_details
$stmt = $pdo->prepare("SELECT mapping_id, field_value FROM import_data_details WHERE id = ?");
$stmt->execute([$iddatadb]);
$currentValues = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$currentValues[$row['mapping_id']] = $row['field_value'];
}
// 3. Confronta i valori nuovi con quelli esistenti
$changed = [];
foreach ($details as $id => $newValue) {
$oldValue = $currentValues[$id] ?? null;
if ($oldValue !== $newValue) {
$changed[$id] = [
'old' => $oldValue,
'new' => $newValue
];
}
}
// 4. Aggiorna i dettagli se ci sono modifiche
if (!empty($changed)) {
$updateStmt = $pdo->prepare("
UPDATE import_data_details
SET field_value = :newValue
WHERE id = :iddatadb AND mapping_id = :mappingId
");
foreach ($changed as $mappingId => $values) {
$updateStmt->execute([
':newValue' => $values['new'],
':iddatadb' => $iddatadb,
':mappingId' => $mappingId
]);
}
}
// 5. Aggiorna idclient in datadb
if (isset($idclient)) {
$updateStmt = $pdo->prepare("
UPDATE datadb
SET idclient = :idclient
WHERE iddatadb = :iddatadb
");
$updateStmt->execute([
':idclient' => $idclient,
':iddatadb' => $iddatadb
]);
$response['message'] = !empty($changed) ? "Updated details and idclient successfully" : "Updated idclient successfully";
} else {
$response['message'] = !empty($changed) ? "Updated details successfully" : "No changes found";
}
$response['success'] = true;
$response['changed'] = $changed; // Debug / optional
} catch (Exception $e) {
$response['success'] = false;
$response['message'] = $e->getMessage();
error_log("Errore in save_edited_row.php: " . $e->getMessage());
}
echo json_encode($response);
exit;

View File

@ -1,39 +0,0 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once(__DIR__ . '/class/db-functions.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
$data = json_decode(file_get_contents("php://input"), true);
if (!$data || !isset($data['id'])) {
echo json_encode(["success" => false, "message" => "Invalid or missing ID"]);
exit;
}
$mappingId = $data['id'];
$mappingType = $data['mapping_type'] ?? '';
$excelColumn = $data['excel_column'] ?? null;
$manualDefault = $data['manual_default'] ?? null;
$tablename = $data['tablename'] ?? '';
try {
$stmt = $pdo->prepare("UPDATE template_mapping SET is_manual = ?, excel_column = ?, manual_default = ? WHERE id = ?");
$isManual = ($mappingType === 'manual') ? 1 : 0;
$result = $stmt->execute([$isManual, $excelColumn, $manualDefault, $mappingId]);
if (!$result) {
echo json_encode(["success" => false, "message" => "Database update failed"]);
exit;
}
echo json_encode(["success" => true, "message" => "Mapping updated successfully", "data" => $data]); // Aggiunto debug
} catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
}
exit;

Some files were not shown because too many files have changed in this diff Show More