first commit theloftstore
This commit is contained in:
parent
e75be99e43
commit
6b65b31932
@ -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
76
composer.lock
generated
@ -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"
|
||||
}
|
||||
|
||||
BIN
public/uploads/client_files/1762868941_client1_provapagina.pdf
Normal file
BIN
public/uploads/client_files/1762868941_client1_provapagina.pdf
Normal file
Binary file not shown.
BIN
public/uploads/client_files/1762868959_client1_provapagina.pdf
Normal file
BIN
public/uploads/client_files/1762868959_client1_provapagina.pdf
Normal file
Binary file not shown.
BIN
public/uploads/client_files/1762869314_client1_provapagina.pdf
Normal file
BIN
public/uploads/client_files/1762869314_client1_provapagina.pdf
Normal file
Binary file not shown.
BIN
public/uploads/client_files/1762869435_provapagina.pdf
Normal file
BIN
public/uploads/client_files/1762869435_provapagina.pdf
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
88
public/userarea/class/chatpdf_helper.php
Normal file
88
public/userarea/class/chatpdf_helper.php
Normal 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;
|
||||
}
|
||||
}
|
||||
249
public/userarea/client_files.php
Normal file
249
public/userarea/client_files.php
Normal 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>
|
||||
33
public/userarea/client_files_delete.php
Normal file
33
public/userarea/client_files_delete.php
Normal 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()]);
|
||||
}
|
||||
36
public/userarea/client_files_list.php
Normal file
36
public/userarea/client_files_list.php
Normal 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()]);
|
||||
}
|
||||
98
public/userarea/client_files_upload.php
Normal file
98
public/userarea/client_files_upload.php
Normal 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()]);
|
||||
}
|
||||
@ -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
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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']);
|
||||
@ -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());
|
||||
}
|
||||
@ -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;
|
||||
@ -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>
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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
|
||||
]
|
||||
]);
|
||||
}
|
||||
@ -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
|
||||
]);
|
||||
@ -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']);
|
||||
}
|
||||
@ -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
|
||||
]);
|
||||
@ -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);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()
|
||||
]);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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>';
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()
|
||||
]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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
@ -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
@ -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;
|
||||
?>
|
||||
412
public/userarea/import_list.php
Normal file
412
public/userarea/import_list.php
Normal 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>
|
||||
21
public/userarea/import_list_data.php
Normal file
21
public/userarea/import_list_data.php
Normal 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()]);
|
||||
}
|
||||
20
public/userarea/import_list_delete.php
Normal file
20
public/userarea/import_list_delete.php
Normal 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()]);
|
||||
}
|
||||
47
public/userarea/import_list_save.php
Normal file
47
public/userarea/import_list_save.php
Normal 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()]);
|
||||
}
|
||||
200
public/userarea/import_prices_pdf.php
Normal file
200
public/userarea/import_prices_pdf.php
Normal 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>
|
||||
76
public/userarea/import_save.php
Normal file
76
public/userarea/import_save.php
Normal 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()]);
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
63
public/userarea/import_xls_process.php
Normal file
63
public/userarea/import_xls_process.php
Normal 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()]);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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
@ -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);
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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);
|
||||
@ -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'));
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
// Include il modal delle parti (già esistente)
|
||||
include 'modal_parts.php';
|
||||
?>
|
||||
<script src="parts.js"></script>
|
||||
@ -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
@ -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">×</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>
|
||||
@ -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">×</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%;">×</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>
|
||||
30
public/userarea/price_mapping_list.php
Normal file
30
public/userarea/price_mapping_list.php
Normal 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()]);
|
||||
}
|
||||
@ -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);
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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);
|
||||
@ -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>
|
||||
@ -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
@ -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;
|
||||
@ -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()
|
||||
]);
|
||||
}
|
||||
@ -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()
|
||||
]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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()]);
|
||||
}
|
||||
@ -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>
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user