Compare commits
10 Commits
4eae855e23
...
29e4b41874
| Author | SHA1 | Date | |
|---|---|---|---|
| 29e4b41874 | |||
| eef9ae8d36 | |||
| 68c867a3f4 | |||
| a9827e4e81 | |||
| b51936f784 | |||
| 15b6f38e8b | |||
| 12c6cc5f95 | |||
| a0b12463c0 | |||
| 07ddcafd3f | |||
| 7843d4b1fc |
1473
public/userarea/annotationsModal.js
Normal file
1473
public/userarea/annotationsModal.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,7 @@ class VisualLimsApiClient
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function authenticate()
|
||||
private function authenticate($retryCount = 0, $maxRetries = 3)
|
||||
{
|
||||
$ch = curl_init("{$this->baseUrl}/api/authentication/authenticate");
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@ -45,16 +45,22 @@ class VisualLimsApiClient
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'w') ?: fopen('php://stderr', 'w');
|
||||
$log = fopen(__DIR__ . '/curl_auth_debug.log', 'a') ?: fopen('php://stderr', 'w');
|
||||
curl_setopt($ch, CURLOPT_STDERR, $log);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curl_error = curl_error($ch);
|
||||
$log_message = date('Y-m-d H:i:s') . " - Auth attempt {$retryCount}: HTTP {$http_code}, Error: {$curl_error}, Response: " . substr($response, 0, 1000) . "\n";
|
||||
fwrite($log, $log_message);
|
||||
fclose($log);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $http_code != 200) {
|
||||
if ($http_code === 400 && strpos($response, 'Cannot persist the object') !== false && $retryCount < $maxRetries) {
|
||||
usleep(500000); // Ritardo di 500ms
|
||||
return $this->authenticate($retryCount + 1, $maxRetries); // Riprova
|
||||
}
|
||||
throw new Exception("Autenticazione fallita: HTTP {$http_code}, Errore cURL: {$curl_error}, Risposta: " . substr($response, 0, 1000));
|
||||
}
|
||||
|
||||
@ -191,5 +197,4 @@ class VisualLimsApiClient
|
||||
{
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
103
public/userarea/cronfiles/get_matrici_cron.php
Normal file
103
public/userarea/cronfiles/get_matrici_cron.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 3) . '/vendor/autoload.php'; // Risale a root/vendor/
|
||||
require_once dirname(__FILE__) . '/../class/VisualLimsApiClient.class.php'; // In root/public/userarea/class/
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
// Debug: Log the path where we expect the .env file
|
||||
$envPath = dirname(__DIR__, 3);
|
||||
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);
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
// Endpoint per recuperare le Matrici
|
||||
$endpoint = 'Matrice';
|
||||
|
||||
// (Opzionale) aggiungi parametri
|
||||
$options = []; // es. ['$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);
|
||||
|
||||
// Debug: Log the API response size
|
||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - API response received, value count: ' . (isset($data['value']) ? count($data['value']) : 0) . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Salva il JSON in locale (opzionale, per debug)
|
||||
file_put_contents(__DIR__ . '/matrici_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||
|
||||
// Svuota la tabella (dump rapido)
|
||||
$pdo->exec("TRUNCATE TABLE {$dbPrefix}matrici");
|
||||
|
||||
// Debug: Log after truncate
|
||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Table truncated: ' . $dbPrefix . 'matrici' . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Prepara l'insert
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO {$dbPrefix}matrici (
|
||||
IdMatrice, NomeMatriceTraduzione, DescrizioneTraduzione, MacroMatrice, NomeMatrice, Descrizione
|
||||
) VALUES (
|
||||
:IdMatrice, :NomeMatriceTraduzione, :DescrizioneTraduzione, :MacroMatrice, :NomeMatrice, :Descrizione
|
||||
)
|
||||
");
|
||||
|
||||
// Inserisci i dati
|
||||
$insertedRows = 0;
|
||||
if (isset($data['value']) && is_array($data['value'])) {
|
||||
foreach ($data['value'] as $item) {
|
||||
$stmt->execute([
|
||||
':IdMatrice' => $item['IdMatrice'],
|
||||
':NomeMatriceTraduzione' => $item['NomeMatriceTraduzione'],
|
||||
':DescrizioneTraduzione' => $item['DescrizioneTraduzione'] ?? null,
|
||||
':MacroMatrice' => $item['MacroMatrice'] ?? null,
|
||||
':NomeMatrice' => $item['NomeMatrice'],
|
||||
':Descrizione' => $item['Descrizione'] ?? null,
|
||||
]);
|
||||
$insertedRows++;
|
||||
// Debug: Log each inserted row
|
||||
file_put_contents(__DIR__ . '/debug_log.txt', date('Y-m-d H:i:s') . ' - Inserted row with IdMatrice: ' . $item['IdMatrice'] . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
// Log successo
|
||||
file_put_contents(__DIR__ . '/success_log.txt', date('Y-m-d H:i:s') . ' - Aggiornamento completato: ' . $insertedRows . ' record inseriti.' . PHP_EOL, FILE_APPEND);
|
||||
} catch (Exception $e) {
|
||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
exit(0); // Esci con successo per cron
|
||||
File diff suppressed because one or more lines are too long
@ -33,14 +33,14 @@ try {
|
||||
throw new Exception("Missing iddatadb");
|
||||
}
|
||||
|
||||
// 🔹 STEP 1+2: Fetch Cliente ID + Schema ID
|
||||
// 🔹 STEP 1+2: Fetch Cliente ID from datadb and Schema ID from excel_templates
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT et.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
|
||||
");
|
||||
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);
|
||||
|
||||
|
||||
@ -23,16 +23,52 @@ try {
|
||||
// Componi endpoint finale
|
||||
$endpoint = "Cliente?$queryString";
|
||||
|
||||
// Richiama API
|
||||
$data = $api->get($endpoint);
|
||||
// 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");
|
||||
}
|
||||
|
||||
// Salva risposta per debug
|
||||
file_put_contents(__DIR__ . '/clienti_response.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||
// Esegui la chiamata con retry
|
||||
$data = makeApiRequest($api, $endpoint);
|
||||
|
||||
echo json_encode($data);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
$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);
|
||||
}
|
||||
|
||||
59
public/userarea/get_macro_matrici.php
Normal file
59
public/userarea/get_macro_matrici.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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);
|
||||
}
|
||||
59
public/userarea/get_matrici_db.php
Normal file
59
public/userarea/get_matrici_db.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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);
|
||||
}
|
||||
@ -40,6 +40,12 @@ foreach ($allMappings as $mapping) {
|
||||
}
|
||||
}
|
||||
|
||||
// Recupera l'idclient di default dal template (se presente)
|
||||
$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;
|
||||
|
||||
$insertedIds = $_POST['inserted_ids'] ?? $_SESSION['inserted_ids'];
|
||||
|
||||
// Recupera i dati appena inseriti con i nomi degli utenti
|
||||
@ -80,6 +86,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<?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.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.cell-changed {
|
||||
background-color: #fff3b0 !important;
|
||||
@ -409,6 +416,46 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
.flatpickr-input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.client-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
.client-input:focus {
|
||||
outline: none;
|
||||
border-color: #80bdff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.select2-dropdown-smaller {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
height: 31px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||
height: 31px;
|
||||
}
|
||||
</style>
|
||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
</head>
|
||||
@ -470,6 +517,13 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="grid-cell" style="flex: 0 0 300px;">
|
||||
<select class="custom-field dropdown-select client-select searchable-client" data-column="idclient" id="clientSelect" name="idclient">
|
||||
<option value="">Select a client...</option>
|
||||
</select>
|
||||
<button type="button" class="propagate-btn" data-column="idclient"><i class="fas fa-arrow-down"></i></button>
|
||||
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
|
||||
</div>
|
||||
<div class="grid-cell" style="flex: 0 0 150px;"></div>
|
||||
<?php
|
||||
$autoIndex = 0;
|
||||
@ -534,10 +588,12 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<div class="resizer"></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="grid-header" data-index="<?= $mainFieldMapping ? 2 : 1 ?>" style="flex: 0 0 150px; position: relative;">Status<div class="resizer"></div>
|
||||
<div class="grid-header" data-index="<?= $mainFieldMapping ? 2 : 1 ?>" style="flex: 0 0 300px; position: relative;">Client<div class="resizer"></div>
|
||||
</div>
|
||||
<div class="grid-header" data-index="<?= $mainFieldMapping ? 3 : 2 ?>" style="flex: 0 0 150px; position: relative;">Status<div class="resizer"></div>
|
||||
</div>
|
||||
<?php
|
||||
$headerIndex = $mainFieldMapping ? 3 : 2;
|
||||
$headerIndex = $mainFieldMapping ? 4 : 3;
|
||||
foreach ($allMappings as $mapping) {
|
||||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . htmlspecialchars($mapping['field_label']) . "<div class='resizer'></div></div>";
|
||||
@ -603,14 +659,19 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="grid-cell editable-cell" data-col="status" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 2 : 1 ?>" style="flex: 0 0 150px;">
|
||||
<div class="grid-cell editable-cell" data-col="idclient" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 2 : 1 ?>" style="flex: 0 0 300px;">
|
||||
<select name="rows[<?= $index ?>][idclient]" class="cell-input dropdown-select client-select searchable-client" data-current-value="<?= htmlspecialchars($row['idclient'] ?? $default_idclient) ?>">
|
||||
<option value="">Select a client...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid-cell editable-cell" data-col="status" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 3 : 2 ?>" style="flex: 0 0 150px;">
|
||||
<span class="status-badge status-<?= htmlspecialchars($row['status'] ?? 'i') ?>">
|
||||
<?= htmlspecialchars($row['status'] === 'i' ? 'Imported' : ($row['status'] === 'P' ? 'In Progress' : 'To LIMS')) ?>
|
||||
</span>
|
||||
<input type="hidden" name="rows[<?= $index ?>][status]" value="<?= htmlspecialchars($row['status'] ?? 'i') ?>">
|
||||
</div>
|
||||
<?php
|
||||
$cellIndex = $mainFieldMapping ? 3 : 2;
|
||||
$cellIndex = $mainFieldMapping ? 4 : 3;
|
||||
$rowDetails = array_filter($manualDetails, fn($d) => $d['datadb_id'] == $row['iddatadb']);
|
||||
$autoIndex = 0;
|
||||
foreach ($allMappings as $mapping) {
|
||||
@ -711,7 +772,8 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</form>
|
||||
<?php include 'modal_parts.php'; ?>
|
||||
<div id="partsModalContainer"></div>
|
||||
<div id="annotationsModalContainer"></div>
|
||||
<?php include 'photos_functions.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
@ -725,8 +787,10 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script src="photos.js"></script>
|
||||
<script src="parts.js"></script>
|
||||
<script src="annotationsModal.js"></script>
|
||||
<script src="partsTable.js"></script>
|
||||
<script src="tracking.js"></script>
|
||||
<script src="export_to_lims.js"></script>
|
||||
<script>
|
||||
@ -836,11 +900,16 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
if (matches) {
|
||||
const mappingId = matches[1];
|
||||
formData.append(`details${mappingId}field_value`, input.value);
|
||||
if (input.tagName === 'SELECT' && input.classList.contains('dropdown-select')) {
|
||||
if (input.tagName === 'SELECT') {
|
||||
input.setAttribute('data-selected-value', input.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const idclientSelect = row.querySelector(`select[name="rows[${rowIndex}][idclient]"]`);
|
||||
if (idclientSelect) {
|
||||
formData.append('idclient', idclientSelect.value);
|
||||
}
|
||||
formData.append('iddatadb', iddatadb);
|
||||
|
||||
fetch('save_edited_row.php', {
|
||||
@ -908,6 +977,11 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const idclientSelect = row.querySelector(`select[name="rows[${rowIndex}][idclient]"]`);
|
||||
if (idclientSelect) {
|
||||
formData.append('idclient', idclientSelect.value);
|
||||
}
|
||||
formData.append('iddatadb', iddatadb);
|
||||
|
||||
try {
|
||||
@ -980,6 +1054,90 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const inputs = document.querySelectorAll('.cell-input');
|
||||
let clientData = []; // Dichiarazione di clientData qui
|
||||
|
||||
// Funzione per caricare i client
|
||||
async function loadClients(retryCount = 0, maxRetries = 3) {
|
||||
const clientLoadingStatus = document.getElementById("clientLoadingStatus");
|
||||
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) {
|
||||
if (response.status === 500 && data.error.includes('Cannot persist the object') && retryCount < maxRetries) {
|
||||
console.log(`Tentativo ${retryCount + 1}/${maxRetries}: Riprovo a caricare i clienti...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return loadClients(retryCount + 1, maxRetries);
|
||||
}
|
||||
throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
clientData = data.value || [];
|
||||
const select = document.getElementById("clientSelect");
|
||||
select.innerHTML = '<option value="">Select a client...</option>';
|
||||
clientData.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($default_idclient ?? 0); ?>)) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.add(option);
|
||||
});
|
||||
|
||||
populateClientDropdowns();
|
||||
clientLoadingStatus.textContent = "Clienti caricati.";
|
||||
} catch (error) {
|
||||
clientLoadingStatus.textContent = "Errore nel caricamento.";
|
||||
console.error("Errore nel caricamento dei client:", error);
|
||||
Swal.fire({
|
||||
title: "Errore!",
|
||||
text: "Impossibile caricare i clienti: " + error.message,
|
||||
icon: "error",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => clientLoadingStatus.style.display = 'none', 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Funzione per popolare i dropdown dei client
|
||||
function populateClientDropdowns() {
|
||||
const clientDropdowns = document.querySelectorAll('select[name^="rows"][name$="[idclient]"]');
|
||||
clientDropdowns.forEach(dropdown => {
|
||||
const currentValue = dropdown.getAttribute('data-current-value') || '';
|
||||
dropdown.innerHTML = '<option value="">Select a client...</option>';
|
||||
clientData.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 (String(id) === String(currentValue)) {
|
||||
option.selected = true;
|
||||
}
|
||||
dropdown.add(option);
|
||||
});
|
||||
|
||||
// Ripristina il valore corrente
|
||||
if (currentValue) {
|
||||
dropdown.value = currentValue;
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
dropdown.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Carica i client all'avvio
|
||||
loadClients();
|
||||
|
||||
// Gestione degli input
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('focus', function() {
|
||||
this.closest('.grid-cell').classList.add('expanded');
|
||||
@ -989,42 +1147,60 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
});
|
||||
});
|
||||
|
||||
// Gestione della propagazione
|
||||
const propagateButtons = document.querySelectorAll('.propagate-btn');
|
||||
propagateButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
button.addEventListener('click', async function() {
|
||||
const column = this.getAttribute('data-column');
|
||||
const input = this.previousElementSibling;
|
||||
const value = input.value;
|
||||
const value = input.tagName === 'SELECT' ? input.value : input.value;
|
||||
|
||||
console.log('Propagate clicked for column:', column, 'with value:', value); // Debug
|
||||
|
||||
// Assicurati che i dropdown dei client siano popolati
|
||||
if (column === 'idclient' && clientData.length === 0) {
|
||||
await loadClients(); // Carica i client se non ancora caricati
|
||||
}
|
||||
|
||||
const gridTopCells = document.querySelector('.grid-top').querySelectorAll('.grid-cell');
|
||||
const targetTopIndex = Array.from(gridTopCells).findIndex(cell =>
|
||||
cell.querySelector('.propagate-btn[data-column="' + column + '"]')
|
||||
);
|
||||
|
||||
console.log('Target index found:', targetTopIndex); // Debug
|
||||
|
||||
if (targetTopIndex !== -1) {
|
||||
const rows = document.querySelectorAll('.grid-row');
|
||||
rows.forEach(row => {
|
||||
const cells = row.querySelectorAll('.grid-cell');
|
||||
if (cells.length > targetTopIndex) {
|
||||
const targetInput = cells[targetTopIndex].querySelector('input, select');
|
||||
const targetInput = cells[targetTopIndex].querySelector('select, input');
|
||||
if (targetInput) {
|
||||
targetInput.value = value;
|
||||
console.log('Setting value on target input:', targetInput, 'with value:', value); // Debug
|
||||
if (targetInput.tagName === 'SELECT') {
|
||||
targetInput.setAttribute('data-selected-value', value);
|
||||
const event = new Event('change');
|
||||
targetInput.value = value;
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
targetInput.dispatchEvent(event);
|
||||
} else if (targetInput.classList.contains('date-picker')) {
|
||||
// Update Flatpickr instance
|
||||
const flatpickrInstance = targetInput._flatpickr;
|
||||
if (flatpickrInstance && value) {
|
||||
flatpickrInstance.setDate(value, true);
|
||||
}
|
||||
const event = new Event('change');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
targetInput.dispatchEvent(event);
|
||||
} else {
|
||||
const event = new Event('change');
|
||||
targetInput.value = value;
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
targetInput.dispatchEvent(event);
|
||||
}
|
||||
} else {
|
||||
console.warn('No target input found in cell'); // Debug
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1032,6 +1208,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
});
|
||||
});
|
||||
|
||||
// Gestione del ridimensionamento delle colonne
|
||||
const resizers = document.querySelectorAll('.resizer');
|
||||
let currentResizer = null;
|
||||
let startX = 0;
|
||||
@ -1074,7 +1251,7 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
const dropdownData = {};
|
||||
|
||||
async function populateDropdowns() {
|
||||
const dropdowns = document.querySelectorAll('.dropdown-select');
|
||||
const dropdowns = document.querySelectorAll('.dropdown-select:not(.client-select)');
|
||||
if (dropdowns.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -1098,13 +1275,15 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
dropdownData[fieldId] = data[fieldId] || [];
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.error('Errore nel caricamento dei valori per dropdown:', error);
|
||||
}
|
||||
}
|
||||
|
||||
dropdowns.forEach(dropdown => {
|
||||
const fieldId = dropdown.getAttribute('data-field-id');
|
||||
const selectedValue = dropdown.getAttribute('data-selected-value') || '';
|
||||
const currentValue = dropdown.value;
|
||||
const currentValue = dropdown.value || '';
|
||||
|
||||
if (!fieldId || !dropdownData[fieldId]) {
|
||||
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
|
||||
@ -1124,61 +1303,263 @@ foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
dropdown.appendChild(option);
|
||||
});
|
||||
|
||||
if ((currentValue || selectedValue) && dropdown.value !== (currentValue || selectedValue)) {
|
||||
dropdown.value = '';
|
||||
// Ripristina il valore corrente
|
||||
if (currentValue || selectedValue) {
|
||||
dropdown.value = currentValue || selectedValue;
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
dropdown.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
populateDropdowns();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
$(document).on('click', '.add-part-btn', function() {
|
||||
const rowIndex = $(this).data('row');
|
||||
const row = $(this).closest('.grid-row');
|
||||
const iddatadb = row.data('id');
|
||||
const input = row.find(`input[name="rows[${rowIndex}][tested_component]"]`);
|
||||
const description = input.val().trim();
|
||||
|
||||
if (!description) {
|
||||
alert('Inserisci un valore per Tested Component');
|
||||
return;
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let clientData = [];
|
||||
|
||||
async function loadClients(retryCount = 0, maxRetries = 3) {
|
||||
const clientLoadingStatus = document.getElementById("clientLoadingStatus");
|
||||
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) {
|
||||
if (response.status === 500 && data.error.includes('Cannot persist the object') && retryCount < maxRetries) {
|
||||
console.log(`Tentativo ${retryCount + 1}/${maxRetries}: Riprovo a caricare i clienti...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return loadClients(retryCount + 1, maxRetries);
|
||||
}
|
||||
throw new Error(data.error || `Errore HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
clientData = data.value || [];
|
||||
const select = document.getElementById("clientSelect");
|
||||
select.innerHTML = '<option value="">Select a client...</option>';
|
||||
clientData.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($default_idclient ?? 0); ?>)) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.add(option);
|
||||
});
|
||||
|
||||
populateClientDropdowns();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
iddatadb: iddatadb,
|
||||
parts: [{
|
||||
part_number: '1', // Imposta part_number a '1'
|
||||
part_description: description,
|
||||
mix: 'N'
|
||||
}]
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: 'save_parts.php',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(data),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
alert('Parte aggiunta con successo!');
|
||||
input.val(''); // Pulisci l'input dopo l'aggiunta
|
||||
// Opzionale: aggiorna la tabella delle parti se il modal è aperto
|
||||
const partsModal = $('#partsModal');
|
||||
if (partsModal.hasClass('show')) {
|
||||
loadParts(iddatadb);
|
||||
function populateClientDropdowns() {
|
||||
const clientDropdowns = document.querySelectorAll('select[name^="rows"][name$="[idclient]"]');
|
||||
clientDropdowns.forEach(dropdown => {
|
||||
const currentValue = dropdown.getAttribute('data-current-value') || '';
|
||||
dropdown.innerHTML = '<option value="">Select a client...</option>';
|
||||
clientData.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 (String(id) === String(currentValue)) {
|
||||
option.selected = true;
|
||||
}
|
||||
} else {
|
||||
alert('Errore: ' + response.message);
|
||||
dropdown.add(option);
|
||||
});
|
||||
|
||||
// Ripristina il valore corrente
|
||||
if (currentValue) {
|
||||
dropdown.value = currentValue;
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
dropdown.dispatchEvent(event);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('Errore durante la richiesta AJAX');
|
||||
});
|
||||
}
|
||||
|
||||
loadClients();
|
||||
|
||||
document.getElementById('clientSelect').addEventListener('change', function() {
|
||||
const gridCell = this.closest('.grid-cell');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
gridCell.dispatchEvent(event);
|
||||
});
|
||||
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.matches('select[name^="rows"][name$="[idclient]"]')) {
|
||||
const gridCell = e.target.closest('.grid-cell');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
gridCell.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Gestione del cambio valore per il dropdown principale dei client
|
||||
document.getElementById('clientSelect').addEventListener('change', function() {
|
||||
const gridCell = this.closest('.grid-cell');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
gridCell.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Gestione del cambio valore per i dropdown dei client nelle righe
|
||||
document.addEventListener('change', function(e) {
|
||||
if (e.target.matches('select[name^="rows"][name$="[idclient]"]')) {
|
||||
const gridCell = e.target.closest('.grid-cell');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
gridCell.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Initialize Select2 for searchable client dropdowns
|
||||
$('.searchable-client').select2({
|
||||
placeholder: "Select a client...",
|
||||
allowClear: true,
|
||||
width: '100%',
|
||||
dropdownCssClass: 'select2-dropdown-smaller',
|
||||
minimumInputLength: 1
|
||||
});
|
||||
|
||||
// Ensure Select2 dropdowns trigger change events for unsaved changes tracking
|
||||
$('.searchable-client').on('select2:select select2:clear', function(e) {
|
||||
const gridCell = this.closest('.grid-cell');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
gridCell.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Update propagate functionality for client dropdown
|
||||
$('.propagate-btn[data-column="idclient"]').on('click', async function() {
|
||||
const value = $('#clientSelect').val();
|
||||
const rows = document.querySelectorAll('.grid-row');
|
||||
rows.forEach(row => {
|
||||
const clientSelect = row.querySelector('select[name$="[idclient]"]');
|
||||
if (clientSelect) {
|
||||
$(clientSelect).val(value).trigger('change.select2');
|
||||
const event = new Event('change', {
|
||||
bubbles: true
|
||||
});
|
||||
clientSelect.closest('.grid-cell').dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.parts-btn', function() {
|
||||
const iddatadb = $(this).data('iddatadb') || null;
|
||||
const idquotations = $(this).data('idquotations') || null;
|
||||
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";
|
||||
|
||||
$.ajax({
|
||||
url: 'modal_partsTable.php',
|
||||
method: 'GET',
|
||||
data: {
|
||||
iddatadb: iddatadb
|
||||
},
|
||||
success: function(response) {
|
||||
$('#partsModalContainer').html(response);
|
||||
const modalElement = document.getElementById('partsModal');
|
||||
if (!modalElement) {
|
||||
console.error('Elemento modale non trovato: #partsModal');
|
||||
const errorMsg = $('<div class="alert alert-danger temp-alert" role="alert">Errore: Modale non trovato.</div>');
|
||||
$("body").prepend(errorMsg);
|
||||
setTimeout(() => errorMsg.fadeOut(500, function() {
|
||||
$(this).remove();
|
||||
}), 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
$("#trfHeader").text(`${iddatadb || idquotations} - ${importRef} - ${description}`);
|
||||
$("#partsModal").data("iddatadb", iddatadb).data("idquotations", idquotations);
|
||||
|
||||
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (!modal) {
|
||||
modal = new bootstrap.Modal(modalElement, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
focus: true
|
||||
});
|
||||
}
|
||||
modal.show();
|
||||
|
||||
if (typeof window.loadParts === 'function') {
|
||||
window.loadParts(iddatadb, idquotations);
|
||||
} else {
|
||||
console.error('Funzione loadParts non definita. Verifica partsTable.js.');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Errore nel caricamento del modale:', error);
|
||||
const errorMsg = $('<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento del modale: ' + error + '</div>');
|
||||
$("body").prepend(errorMsg);
|
||||
setTimeout(() => errorMsg.fadeOut(500, function() {
|
||||
$(this).remove();
|
||||
}), 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('hidden.bs.modal', '#partsModal', function() {
|
||||
const modalElement = document.getElementById('partsModal');
|
||||
if (modalElement) {
|
||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modal) {
|
||||
modal.dispose();
|
||||
}
|
||||
}
|
||||
$('#partsModalContainer').empty();
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open').css('padding-right', '');
|
||||
$('.overlay.toggle-icon').css('display', 'none');
|
||||
});
|
||||
|
||||
$(document).on('hidden.bs.modal', '#annotationsModal', function() {
|
||||
const modalElement = document.getElementById('annotationsModal');
|
||||
if (modalElement) {
|
||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modal) {
|
||||
modal.dispose();
|
||||
}
|
||||
}
|
||||
$('#annotationsModalContainer').empty();
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open').css('padding-right', '');
|
||||
$('.overlay.toggle-icon').css('display', 'none');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- Modale di conferma per l'esportazione -->
|
||||
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
|
||||
@ -75,6 +75,12 @@ foreach ($selected_rows as $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,
|
||||
@ -83,9 +89,10 @@ foreach ($selected_rows as $rowIndex) {
|
||||
$user_id,
|
||||
null,
|
||||
date('Y-m-d'),
|
||||
$excelrow // Aggiunto excelrow per la colonna excelrow
|
||||
$excelrow,
|
||||
$default_idclient // Aggiungi idclient
|
||||
];
|
||||
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$sql = "INSERT INTO datadb (templateid, importreferencecode, filename_import, status, user_id, limscode, importdate, excelrow, idclient) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($values);
|
||||
|
||||
|
||||
@ -14,9 +14,9 @@ if (!$iddatadb) {
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT id, iddatadb, part_number, part_description FROM identification_parts WHERE iddatadb = :iddatadb ORDER BY part_number ASC");
|
||||
$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();
|
||||
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'parts' => $parts]);
|
||||
} catch (PDOException $e) {
|
||||
|
||||
219
public/userarea/modal_annotations.php
Normal file
219
public/userarea/modal_annotations.php
Normal file
@ -0,0 +1,219 @@
|
||||
<!-- 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: 80% !important; width: 80% !important;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="annotationsModalLabel">Annotazioni per TRF: <span id="trfHeaderAnnotations"></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;">
|
||||
<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"></ul>
|
||||
</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="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: 400px;">
|
||||
<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;
|
||||
}
|
||||
</style>
|
||||
@ -1,4 +1,4 @@
|
||||
<!-- Modal modificato con pulsante per riconoscimento vocale e download -->
|
||||
<!-- 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">
|
||||
@ -9,10 +9,12 @@
|
||||
<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;">
|
||||
<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;">
|
||||
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px;">
|
||||
<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>
|
||||
@ -45,13 +47,14 @@
|
||||
<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>
|
||||
<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> <!-- Nuovo canvas per Fabric.js -->
|
||||
<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>
|
||||
@ -249,4 +252,59 @@
|
||||
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>
|
||||
314
public/userarea/modal_partsTable.php
Normal file
314
public/userarea/modal_partsTable.php
Normal file
@ -0,0 +1,314 @@
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<style>
|
||||
#partsModal {
|
||||
z-index: 1060 !important;
|
||||
}
|
||||
|
||||
#partsModal .modal-backdrop {
|
||||
z-index: 1055 !important;
|
||||
}
|
||||
|
||||
#partsModal .modal-content {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#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,
|
||||
#partsTable select {
|
||||
height: 24px;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
|
||||
#partsTable button {
|
||||
padding: 0.1rem 0.3rem;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
#partsTable i {
|
||||
font-size: 0.6rem !important;
|
||||
}
|
||||
|
||||
#global-matrice,
|
||||
.part-matrice,
|
||||
#macro-matrice-filter {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
.select2-container--default #global-matrice {
|
||||
width: 350px !important;
|
||||
min-width: 350px !important;
|
||||
}
|
||||
|
||||
.select2-container--default #macro-matrice-filter {
|
||||
width: 200px !important;
|
||||
min-width: 200px !important;
|
||||
}
|
||||
|
||||
.select2-container--default .part-matrice {
|
||||
width: 150px !important;
|
||||
min-width: 150px !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--single {
|
||||
height: 24px !important;
|
||||
padding: 0.1rem 0.3rem !important;
|
||||
font-size: 0.8rem !important;
|
||||
border: 1px solid #ced4da !important;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection__rendered {
|
||||
line-height: 22px !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !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: white !important;
|
||||
overflow-y: auto !important;
|
||||
max-height: 200px !important;
|
||||
}
|
||||
|
||||
.propagate-matrice-btn,
|
||||
.propagate-all-btn {
|
||||
padding: 0.1rem 0.3rem !important;
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
|
||||
.save-status,
|
||||
.save-loading {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#confirmDeleteModal {
|
||||
z-index: 1070 !important;
|
||||
}
|
||||
|
||||
#confirmDeleteModal .modal-backdrop {
|
||||
z-index: 1065 !important;
|
||||
}
|
||||
|
||||
.note-btn {
|
||||
padding: 0.2rem 0.4rem !important;
|
||||
/* Aumentato leggermente il padding per un pulsante più grande */
|
||||
font-size: 0.9rem !important;
|
||||
/* Aumentato il font-size per un'icona più grande */
|
||||
}
|
||||
|
||||
.note-btn.has-note {
|
||||
color: #dc3545 !important;
|
||||
/* Rosso quando la nota è presente */
|
||||
}
|
||||
|
||||
#noteModal {
|
||||
z-index: 1090 !important;
|
||||
}
|
||||
|
||||
#noteModal .modal-backdrop {
|
||||
z-index: 1085 !important;
|
||||
}
|
||||
|
||||
#noteModal .modal-dialog {
|
||||
position: relative;
|
||||
z-index: 1090 !important;
|
||||
}
|
||||
|
||||
#noteModal textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.propagate-date-btn {
|
||||
padding: 0.2rem 0.4rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.propagate-note-btn {
|
||||
padding: 0.2rem 0.4rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
#commonNoteModal {
|
||||
z-index: 1095 !important;
|
||||
/* Sopra #noteModal (1090) */
|
||||
}
|
||||
|
||||
#commonNoteModal .modal-backdrop {
|
||||
z-index: 1090 !important;
|
||||
/* Sopra il backdrop di #noteModal (1085) */
|
||||
}
|
||||
|
||||
#commonNoteModal .modal-dialog {
|
||||
position: relative;
|
||||
z-index: 1095 !important;
|
||||
}
|
||||
|
||||
#commonNoteModal textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
@ -12,11 +12,13 @@ $(document).ready(function () {
|
||||
|
||||
let photoAnnotations = {};
|
||||
let partColors = {};
|
||||
let partMatrice = {};
|
||||
let selectedPartNumber = null;
|
||||
let unsavedChanges = false;
|
||||
let fabricCanvas = null;
|
||||
let descriptionTextbox = null;
|
||||
let markerObjects = {};
|
||||
let matrici = [];
|
||||
|
||||
// ===================
|
||||
// VOICE RECOGNITION SETUP
|
||||
@ -137,21 +139,82 @@ $(document).ready(function () {
|
||||
.data("iddatadb", iddatadb)
|
||||
.data("idquotations", idquotations);
|
||||
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations);
|
||||
// Precarica le matrici se iddatadb (assumendo matrici solo per iddatadb)
|
||||
if (iddatadb) {
|
||||
if (matrici.length === 0) {
|
||||
$.ajax({
|
||||
url: "get_matrici_db.php",
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
matrici = data.value || [];
|
||||
initializeGlobalSelect2();
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations);
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
matrici = [];
|
||||
initializeGlobalSelect2();
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations);
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle matrici: ' +
|
||||
error +
|
||||
" (" +
|
||||
xhr.status +
|
||||
")</div>",
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(function () {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
initializeGlobalSelect2();
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations);
|
||||
}
|
||||
} else {
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations);
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(
|
||||
document.getElementById("partsModal"),
|
||||
);
|
||||
modal.show();
|
||||
const modalElement = document.getElementById("partsModal");
|
||||
if (modalElement) {
|
||||
// Verifica se il modale è già stato inizializzato
|
||||
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (!modal) {
|
||||
modal = new bootstrap.Modal(modalElement, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
focus: true,
|
||||
});
|
||||
}
|
||||
modal.show();
|
||||
} else {
|
||||
console.error("Elemento modale non trovato: #partsModal");
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Errore: Modale non trovato.</div>',
|
||||
);
|
||||
$("body").prepend(errorMsg);
|
||||
setTimeout(function () {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$("#partsModal .close-btn, #partsModal").on("click", function (event) {
|
||||
if (event.target === this) {
|
||||
const modal = bootstrap.Modal.getInstance(
|
||||
document.getElementById("partsModal"),
|
||||
);
|
||||
modal.hide();
|
||||
if (event.target === this || $(event.target).hasClass("close-btn")) {
|
||||
const modalElement = document.getElementById("partsModal");
|
||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -165,7 +228,7 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
$("#partsModal").on("hidden.bs.modal", function () {
|
||||
// Reset global state to initial values
|
||||
// Resetta lo stato
|
||||
photoData = {
|
||||
naturalWidth: 0,
|
||||
naturalHeight: 0,
|
||||
@ -175,21 +238,33 @@ $(document).ready(function () {
|
||||
};
|
||||
photoAnnotations = {};
|
||||
partColors = {};
|
||||
partMatrice = {};
|
||||
selectedPartNumber = null;
|
||||
unsavedChanges = false;
|
||||
if (fabricCanvas) {
|
||||
fabricCanvas.off(); // Rimuove tutti gli eventi
|
||||
fabricCanvas.off();
|
||||
fabricCanvas.dispose();
|
||||
fabricCanvas = null;
|
||||
}
|
||||
descriptionTextbox = null;
|
||||
markerObjects = {};
|
||||
// Clear UI elements
|
||||
matrici = [];
|
||||
$("#photoSelectorContainer").empty().hide();
|
||||
$("#samplePhoto").attr("src", "");
|
||||
$("#partsTableBody").empty();
|
||||
// Remove any temporary messages
|
||||
$("#global-matrice").empty();
|
||||
$(".temp-alert").remove();
|
||||
|
||||
// Rimuovi manualmente il backdrop e ripristina il body
|
||||
const modalElement = document.getElementById("partsModal");
|
||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modal) {
|
||||
modal.dispose(); // Distrugge l'istanza del modale
|
||||
}
|
||||
$(".modal-backdrop").remove();
|
||||
$("body").removeClass("modal-open");
|
||||
$("body").css("padding-right", "");
|
||||
$(":focus").blur();
|
||||
});
|
||||
|
||||
// ===================
|
||||
@ -535,6 +610,7 @@ $(document).ready(function () {
|
||||
if (response.success) {
|
||||
$row.remove();
|
||||
delete partColors[partNumber];
|
||||
delete partMatrice[partNumber];
|
||||
if (markerObjects[partNumber]) {
|
||||
fabricCanvas.remove(markerObjects[partNumber]);
|
||||
delete markerObjects[partNumber];
|
||||
@ -576,6 +652,7 @@ $(document).ready(function () {
|
||||
} else {
|
||||
$row.remove();
|
||||
delete partColors[partNumber];
|
||||
delete partMatrice[partNumber];
|
||||
if (markerObjects[partNumber]) {
|
||||
fabricCanvas.remove(markerObjects[partNumber]);
|
||||
delete markerObjects[partNumber];
|
||||
@ -717,6 +794,9 @@ $(document).ready(function () {
|
||||
</tr>`;
|
||||
$("#partsTableBody").append(newRow);
|
||||
partColors[part.part_number] = defaultColor;
|
||||
if (part.idmatrice) {
|
||||
partMatrice[part.part_number] = part.idmatrice;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addNewRow(1);
|
||||
@ -743,6 +823,163 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
|
||||
// Funzione per inizializzare Select2 sulle tendine delle matrici
|
||||
function initializeSelect2($select, partNumber, partId, idmatrice) {
|
||||
if (typeof $.fn.select2 === "undefined") {
|
||||
$select.replaceWith(
|
||||
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const options = matrici.map(function (matrice) {
|
||||
return {
|
||||
id: matrice.IdMatrice,
|
||||
text: matrice.NomeMatrice, // Updated to use NomeMatrice
|
||||
};
|
||||
});
|
||||
|
||||
$select.select2({
|
||||
placeholder: "Seleziona matrice",
|
||||
allowClear: true,
|
||||
data: options,
|
||||
dropdownParent: $("#partsModal"),
|
||||
matcher: function (params, data) {
|
||||
if (!params.term || params.term.length < 3) {
|
||||
return data;
|
||||
}
|
||||
const term = params.term.toUpperCase();
|
||||
if (data.text.toUpperCase().indexOf(term) >= 0) {
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
if (partId && partId !== "new" && idmatrice) {
|
||||
const matrice = matrici.find((m) => m.IdMatrice == idmatrice);
|
||||
if (matrice) {
|
||||
const option = new Option(
|
||||
matrice.NomeMatrice, // Updated to use NomeMatrice
|
||||
matrice.IdMatrice,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
$select.append(option).trigger("change");
|
||||
partMatrice[partNumber] = matrice.IdMatrice;
|
||||
}
|
||||
}
|
||||
|
||||
$select.on("change", function () {
|
||||
const idmatrice = $(this).val();
|
||||
const $listItem = $(this).closest("li");
|
||||
const $saveStatus = $listItem.find(".save-status");
|
||||
const $saveLoading = $listItem.find(".save-loading");
|
||||
|
||||
partMatrice[partNumber] = idmatrice || null;
|
||||
|
||||
if (partId && partId !== "new") {
|
||||
$saveLoading.show();
|
||||
$saveStatus.hide();
|
||||
|
||||
const iddatadb = $("#partsModal").data("iddatadb");
|
||||
const idquotations = $("#partsModal").data("idquotations");
|
||||
const endpoint = idquotations
|
||||
? "save_matrice_quotation.php"
|
||||
: "save_matrice.php";
|
||||
const data = idquotations
|
||||
? { idquotations: idquotations }
|
||||
: { iddatadb: iddatadb };
|
||||
|
||||
$.ajax({
|
||||
url: endpoint,
|
||||
method: "POST",
|
||||
data: JSON.stringify({
|
||||
...data,
|
||||
parts: [
|
||||
{
|
||||
id: partId,
|
||||
idmatrice: idmatrice || null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
contentType: "application/json",
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
$saveLoading.hide();
|
||||
$saveStatus.show();
|
||||
setTimeout(() => $saveStatus.hide(), 2000);
|
||||
} else {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della matrice: ' +
|
||||
response.message +
|
||||
"</div>",
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(function () {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
$saveLoading.hide();
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della matrice: ' +
|
||||
error +
|
||||
" (" +
|
||||
xhr.status +
|
||||
")</div>",
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(function () {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
$saveLoading.hide();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Funzione per inizializzare Select2 sul dropdown globale
|
||||
function initializeGlobalSelect2() {
|
||||
const $select = $("#global-matrice");
|
||||
if (typeof $.fn.select2 === "undefined") {
|
||||
$select.replaceWith(
|
||||
'<input type="text" class="form-control form-control-sm" placeholder="Select2 non disponibile" disabled>',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const options = matrici.map(function (matrice) {
|
||||
return {
|
||||
id: matrice.IdMatrice,
|
||||
text: matrice.NomeMatrice, // Updated to use NomeMatrice
|
||||
};
|
||||
});
|
||||
|
||||
$select.select2({
|
||||
placeholder: "Seleziona matrice globale",
|
||||
allowClear: true,
|
||||
data: options,
|
||||
dropdownParent: $("#partsModal"),
|
||||
matcher: function (params, data) {
|
||||
if (!params.term || params.term.length < 3) {
|
||||
return data;
|
||||
}
|
||||
const term = params.term.toUpperCase();
|
||||
if (data.text.toUpperCase().indexOf(term) >= 0) {
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ===================
|
||||
// PARTS LIST
|
||||
// ===================
|
||||
@ -763,6 +1000,7 @@ $(document).ready(function () {
|
||||
$("#partsTableBody tr").each(function () {
|
||||
const partNumber = $(this).find(".part-number").val();
|
||||
const partDescription = $(this).find(".part-description").val();
|
||||
const partId = $(this).data("part-id");
|
||||
const partColor =
|
||||
partColors[partNumber] ||
|
||||
(partDescription.startsWith("Mix") ? "#0000ff" : "#ff0000");
|
||||
@ -778,17 +1016,30 @@ $(document).ready(function () {
|
||||
)
|
||||
.join("");
|
||||
const listItem = `
|
||||
<li class="list-group-item" data-part-number="${partNumber}">
|
||||
<li class="list-group-item" data-part-number="${partNumber}" data-part-id="${partId}">
|
||||
${partNumber} - ${partDescription}
|
||||
<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" style="width: 250px !important; margin-right: 10px;"></select>
|
||||
<button type="button" class="btn btn-success btn-sm add-to-mix-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;"><i class="fas fa-plus fa-xs"></i></button>
|
||||
<div class="color-picker-container">
|
||||
<div class="color-option selected-color" style="background-color: ${partColor}; margin-left: 5px;"></div>
|
||||
<div class="color-picker">${colorOptions}</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</li>`;
|
||||
$("#partsList").append(listItem);
|
||||
const $select = $("#partsList").find(
|
||||
`li[data-part-number="${partNumber}"] .part-matrice`,
|
||||
);
|
||||
initializeSelect2(
|
||||
$select,
|
||||
partNumber,
|
||||
partId,
|
||||
partMatrice[partNumber],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -862,11 +1113,32 @@ $(document).ready(function () {
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", ".propagate-matrice-btn", function () {
|
||||
const $listItem = $(this).closest("li");
|
||||
const globalVal = $("#global-matrice").val();
|
||||
if (globalVal) {
|
||||
$listItem.find(".part-matrice").val(globalVal).trigger("change");
|
||||
} else {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Seleziona una matrice globale prima di propagare.</div>',
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(function () {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
|
||||
$("#partsList").on("click", "li", function (e) {
|
||||
if (
|
||||
$(e.target).hasClass("add-to-mix-btn") ||
|
||||
$(e.target).hasClass("color-option") ||
|
||||
$(e.target).closest(".color-picker-container").length
|
||||
$(e.target).closest(".color-picker-container").length ||
|
||||
$(e.target).hasClass("part-matrice") ||
|
||||
$(e.target).closest(".select2-container").length ||
|
||||
$(e.target).hasClass("propagate-matrice-btn")
|
||||
)
|
||||
return;
|
||||
selectedPartNumber = $(this).data("part-number");
|
||||
@ -892,6 +1164,7 @@ $(document).ready(function () {
|
||||
? { idquotations: idquotations }
|
||||
: { iddatadb: iddatadb };
|
||||
let newPartColors = {};
|
||||
let newPartMatrice = {};
|
||||
let newMarkerObjects = {};
|
||||
|
||||
let partsData = $rows
|
||||
@ -908,6 +1181,7 @@ $(document).ready(function () {
|
||||
partsData.forEach((part, index) => {
|
||||
const newNumber = index + 1;
|
||||
newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
|
||||
newPartMatrice[newNumber] = partMatrice[part.partNumber] || null;
|
||||
if (markerObjects[part.partNumber]) {
|
||||
newMarkerObjects[newNumber] = markerObjects[part.partNumber];
|
||||
}
|
||||
@ -934,6 +1208,7 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
partColors = newPartColors;
|
||||
partMatrice = newPartMatrice;
|
||||
markerObjects = newMarkerObjects;
|
||||
|
||||
const partsToSave = partsData.map((part) => ({
|
||||
@ -941,6 +1216,7 @@ $(document).ready(function () {
|
||||
part_number: part.partNumber,
|
||||
part_description: part.partDescription,
|
||||
mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
|
||||
idmatrice: partMatrice[part.partNumber] || null,
|
||||
}));
|
||||
|
||||
$.ajax({
|
||||
|
||||
1884
public/userarea/partsTable.js
Normal file
1884
public/userarea/partsTable.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -108,7 +108,7 @@ try {
|
||||
}
|
||||
|
||||
// Recupera routine dal template
|
||||
$stmt = $pdo->prepare("SELECT idroutine FROM excel_templates WHERE id = ?");
|
||||
$stmt = $pdo->prepare("SELECT idroutine, idclient FROM excel_templates WHERE id = ?");
|
||||
$stmt->execute([$template_id]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -133,6 +133,9 @@ try {
|
||||
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;
|
||||
|
||||
@ -11,13 +11,14 @@ try {
|
||||
}
|
||||
|
||||
$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. 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];
|
||||
@ -25,16 +26,15 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. DB-დან წამოვიღოთ არსებული მნიშვნელობები
|
||||
// 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. შევადაროთ POST-ს და DB-ს
|
||||
// 3. Confronta i valori nuovi con quelli esistenti
|
||||
$changed = [];
|
||||
foreach ($details as $id => $newValue) {
|
||||
$oldValue = $currentValues[$id] ?? null;
|
||||
@ -46,7 +46,7 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. თუ არის ცვლილებები → UPDATE
|
||||
// 4. Aggiorna i dettagli se ci sono modifiche
|
||||
if (!empty($changed)) {
|
||||
$updateStmt = $pdo->prepare("
|
||||
UPDATE import_data_details
|
||||
@ -61,15 +61,27 @@ try {
|
||||
':mappingId' => $mappingId
|
||||
]);
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['message'] = "Updated successfully";
|
||||
$response['changed'] = $changed; // Debug / optional
|
||||
} else {
|
||||
$response['success'] = true;
|
||||
$response['message'] = "No changes found";
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
39
public/userarea/save_matrice.php
Normal file
39
public/userarea/save_matrice.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
$part = $parts[0];
|
||||
$partId = $part['id'] ?? null;
|
||||
$idmatrice = $part['idmatrice'] ?? null;
|
||||
|
||||
if (!$partId) {
|
||||
echo json_encode(['success' => false, 'message' => 'ID parte mancante']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE identification_parts
|
||||
SET idmatrice = :idmatrice,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id");
|
||||
$stmt->execute([
|
||||
':id' => $partId,
|
||||
':idmatrice' => $idmatrice // Può essere NULL
|
||||
]);
|
||||
echo json_encode(['success' => true, 'message' => 'Matrice aggiornata con successo']);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio della matrice: ' . $e->getMessage()]);
|
||||
}
|
||||
@ -15,46 +15,64 @@ if (!$iddatadb || empty($parts)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$part = $parts[0];
|
||||
$partId = $part['id'] ?? null; // part_id თუ არსებობს
|
||||
$partNumber = $part['part_number'] ?? null;
|
||||
$partDescription = $part['part_description'] ?? '';
|
||||
$mix = $part['mix'] ?? 'N';
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
$results = [];
|
||||
|
||||
if ($partDescription) {
|
||||
try {
|
||||
if ($partId) {
|
||||
// UPDATE თუ უკვე არსებობს part
|
||||
$stmt = $pdo->prepare("UPDATE identification_parts
|
||||
SET part_number = :part_number,
|
||||
part_description = :part_description,
|
||||
mix = :mix,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id");
|
||||
$stmt->execute([
|
||||
':id' => $partId,
|
||||
':part_number' => $partNumber,
|
||||
':part_description' => $partDescription,
|
||||
':mix' => $mix
|
||||
]);
|
||||
echo json_encode(['success' => true, 'part_id' => $partId, 'part_number'=>$partNumber, 'message' => 'Parte aggiornata con successo']);
|
||||
} else {
|
||||
// INSERT თუ ახალია
|
||||
$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())");
|
||||
$stmt->execute([
|
||||
':iddatadb' => $iddatadb,
|
||||
':part_number' => $partNumber,
|
||||
':part_description' => $partDescription,
|
||||
':mix' => $mix
|
||||
]);
|
||||
$newId = $pdo->lastInsertId();
|
||||
echo json_encode(['success' => true, 'part_id' => $newId, 'part_number'=>$partNumber, 'message' => 'Parte salvata con successo']);
|
||||
foreach ($parts as $part) {
|
||||
$partId = $part['id'] ?? null;
|
||||
$partNumber = $part['part_number'] ?? null;
|
||||
$partDescription = $part['part_description'] ?? '';
|
||||
$mix = $part['mix'] ?? 'N';
|
||||
$idmatrice = $part['idmatrice'] ?? null;
|
||||
$note = $part['note'] ?? null;
|
||||
$dateexpiry = $part['dateexpiry'] ?? null;
|
||||
|
||||
if ($partDescription || $note || $dateexpiry) {
|
||||
if ($partId) {
|
||||
// UPDATE se la parte esiste
|
||||
$stmt = $pdo->prepare("UPDATE identification_parts
|
||||
SET part_number = :part_number,
|
||||
part_description = :part_description,
|
||||
mix = :mix,
|
||||
idmatrice = :idmatrice,
|
||||
note = :note,
|
||||
dateexpiry = :dateexpiry,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id");
|
||||
$stmt->execute([
|
||||
':id' => $partId,
|
||||
':part_number' => $partNumber,
|
||||
':part_description' => $partDescription,
|
||||
':mix' => $mix,
|
||||
':idmatrice' => $idmatrice,
|
||||
':note' => $note,
|
||||
':dateexpiry' => $dateexpiry,
|
||||
]);
|
||||
$results[] = ['part_id' => $partId, 'part_number' => $partNumber, 'message' => 'Parte aggiornata con successo'];
|
||||
} else {
|
||||
// INSERT per nuova parte
|
||||
$stmt = $pdo->prepare("INSERT INTO identification_parts
|
||||
(iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at)
|
||||
VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW())");
|
||||
$stmt->execute([
|
||||
':iddatadb' => $iddatadb,
|
||||
':part_number' => $partNumber,
|
||||
':part_description' => $partDescription,
|
||||
':mix' => $mix,
|
||||
':idmatrice' => $idmatrice,
|
||||
':note' => $note,
|
||||
':dateexpiry' => $dateexpiry,
|
||||
]);
|
||||
$newId = $pdo->lastInsertId();
|
||||
$results[] = ['part_id' => $newId, 'part_number' => $partNumber, 'message' => 'Parte salvata con successo'];
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Descrizione mancante']);
|
||||
|
||||
$pdo->commit();
|
||||
echo json_encode(['success' => true, 'results' => $results]);
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => 'Errore nel salvataggio: ' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
// upload_photos_mobile.php
|
||||
include('include/headscriptnologin.php');
|
||||
include('include/headscript.php');
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user