2720 lines
135 KiB
PHP
2720 lines
135 KiB
PHP
<?php
|
||
include('include/headscript.php');
|
||
|
||
// ✅ FIX timezone (Rome)
|
||
ini_set('date.timezone', 'Europe/Rome');
|
||
date_default_timezone_set('Europe/Rome');
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
|
||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
|
||
exit;
|
||
}
|
||
|
||
$template_id = intval($_POST['template_id']) ?? $_SESSION['template_id'];
|
||
$selected_rows = $_POST['selected_rows'] ?? $_SESSION['selected_rows'];
|
||
$columns = json_decode($_POST['columns'], true) ?? $_SESSION['columns'];
|
||
$rows = json_decode($_POST['rows'], true) ?? $_SESSION['rows'];
|
||
$newFilename = htmlspecialchars($_POST['filename']) ?? $_SESSION['filename'];
|
||
|
||
// Recupera l'ID dell'utente loggato
|
||
$user_id = $iduserlogin ?? 1;
|
||
|
||
$db = DBHandlerSelect::getInstance();
|
||
$pdo = $db->getConnection();
|
||
|
||
// Genera un UUID univoco per importreferencecode
|
||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||
|
||
// Recupera tutti i mapping dal template, includendo is_visible_import
|
||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, is_visible_import, auto_value
|
||
FROM template_mapping
|
||
WHERE template_id = ?");
|
||
$stmt->execute([$template_id]);
|
||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
$timeLabels = [
|
||
'Sample Arrival Time:',
|
||
'Sample Unlock Time:',
|
||
'Login Time:',
|
||
'Presa in carico, Orario:',
|
||
];
|
||
|
||
// Returns the auto value for current session (import) based on mapping.auto_value
|
||
function getImportAutoValue(array $mapping): string
|
||
{
|
||
$auto = $mapping['auto_value'] ?? 'none';
|
||
|
||
if ($auto === 'import_date') {
|
||
return date('Y-m-d');
|
||
}
|
||
|
||
if ($auto === 'import_time') {
|
||
// HTML <input type="time"> expects HH:MM (seconds optional)
|
||
return date('H:i');
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
if (empty($allMappings)) {
|
||
header("Location: import_xls.php?id=$template_id&status=error&message=" . urlencode("Nessun mapping trovato per il template"));
|
||
exit;
|
||
}
|
||
|
||
// Trova il campo main_field
|
||
$mainFieldMapping = null;
|
||
foreach ($allMappings as $mapping) {
|
||
if ($mapping['main_field'] == 1 && $mapping['is_visible_import'] == 1) {
|
||
$mainFieldMapping = $mapping;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 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'];
|
||
// ------------------------------------------------------------
|
||
// AUTO SET: schema field_id = 244 with auth_users.lims_user_id (if present)
|
||
// ------------------------------------------------------------
|
||
try {
|
||
// 1) Read logged user lims_user_id
|
||
$stmtUser = $pdo->prepare("SELECT lims_user_id FROM auth_users WHERE id = ? LIMIT 1");
|
||
$stmtUser->execute([(int)$user_id]);
|
||
$limsUserId = $stmtUser->fetchColumn();
|
||
|
||
// normalize
|
||
$limsUserId = ($limsUserId !== false && $limsUserId !== null && $limsUserId !== '') ? (string)$limsUserId : '';
|
||
|
||
if ($limsUserId !== '' && !empty($insertedIds)) {
|
||
|
||
// 2) Find mapping_id for field_id = 244 in this template
|
||
$stmtMap = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ? AND field_id = 244 LIMIT 1");
|
||
$stmtMap->execute([(int)$template_id]);
|
||
$mappingId244 = (int)$stmtMap->fetchColumn();
|
||
|
||
if ($mappingId244 > 0) {
|
||
|
||
// 3) Upsert value into import_data_details for every inserted datadb row
|
||
$pdo->beginTransaction();
|
||
|
||
$updStmt = $pdo->prepare("UPDATE import_data_details SET field_value = ? WHERE id = ? AND mapping_id = ?");
|
||
$insStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||
|
||
foreach ($insertedIds as $iddatadb) {
|
||
$iddatadb = (int)$iddatadb;
|
||
if ($iddatadb <= 0) continue;
|
||
|
||
$updStmt->execute([$limsUserId, $iddatadb, $mappingId244]);
|
||
|
||
if ($updStmt->rowCount() === 0) {
|
||
$insStmt->execute([$iddatadb, $mappingId244, $limsUserId]);
|
||
}
|
||
}
|
||
|
||
$pdo->commit();
|
||
}
|
||
}
|
||
} catch (Exception $e) {
|
||
// Do not block import page if this fails; just log
|
||
error_log("[AUTO FIELD 244] " . $e->getMessage());
|
||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||
}
|
||
// Recupera i dati appena inseriti con i nomi degli utenti
|
||
$stmt = $pdo->prepare("
|
||
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
|
||
FROM datadb d
|
||
LEFT JOIN auth_users u ON d.user_id = u.id
|
||
WHERE d.iddatadb IN (" . implode(',', array_fill(0, count($insertedIds), '?')) . ")
|
||
");
|
||
$stmt->execute($insertedIds);
|
||
$importedData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// Recupera i dettagli manuali da import_data_details
|
||
$stmt = $pdo->prepare("
|
||
SELECT d.id AS detail_id, d.id AS datadb_id, d.mapping_id, d.field_value,
|
||
m.field_id, m.field_label, m.data_type, m.is_required, m.manual_default
|
||
FROM import_data_details d
|
||
JOIN template_mapping m ON d.mapping_id = m.id
|
||
WHERE d.id IN (" . implode(',', array_fill(0, count($insertedIds), '?')) . ")
|
||
");
|
||
$stmt->execute($insertedIds);
|
||
$manualDetails = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// Recupera il mapping globale per mostrare gli slug leggibili
|
||
$stmt = $pdo->query("SELECT mysql_column_name, user_friendly_slug FROM column_mapping");
|
||
$slugMapping = [];
|
||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||
$slugMapping[$row['mysql_column_name']] = $row['user_friendly_slug'];
|
||
}
|
||
// --- FIX LABELS ONLY (do not change keys) ---
|
||
$slugMapping['AnagraficaCertestObject'] = 'Object';
|
||
$slugMapping['AnagraficaCertestService'] = 'Service';
|
||
|
||
// ---------------- FIXED FIELDS (from template_fixed_mapping) ----------------
|
||
$fixedStmt = $pdo->prepare("
|
||
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
||
FROM template_fixed_mapping
|
||
WHERE template_id = ? AND is_visible_import = 1
|
||
ORDER BY id
|
||
");
|
||
$fixedStmt->execute([$template_id]);
|
||
$fixedFieldsRaw = $fixedStmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// Ordine desiderato: Cliente Responsabile prima, poi gli altri, ConsegnaRichiesta prima delle 3 speciali
|
||
$desiredOrder = [
|
||
'ClienteResponsabile',
|
||
'AnagraficaCertestObject',
|
||
'AnagraficaCertestService',
|
||
'MoltiplicatorePrezzo',
|
||
'ConsegnaRichiesta',
|
||
// se ci sono altri campi fixed li mettiamo dopo
|
||
];
|
||
|
||
$fixedFields = [];
|
||
$tempMap = [];
|
||
foreach ($fixedFieldsRaw as $f) {
|
||
$tempMap[$f['fixed_field_key']] = $f;
|
||
}
|
||
|
||
foreach ($desiredOrder as $key) {
|
||
if (isset($tempMap[$key])) {
|
||
$fixedFields[] = $tempMap[$key];
|
||
unset($tempMap[$key]);
|
||
}
|
||
}
|
||
|
||
// Aggiunge eventuali campi fixed non elencati sopra (alla fine)
|
||
foreach ($tempMap as $f) {
|
||
$fixedFields[] = $f;
|
||
}
|
||
|
||
// helper default (DATE can use 'today' if you already use it elsewhere)
|
||
function fixedDefaultValue(array $f): string
|
||
{
|
||
$v = $f['default_value'] ?? '';
|
||
if ($f['data_type'] === 'DATE' && $v === 'today') return date('Y-m-d');
|
||
return (string)$v;
|
||
}
|
||
|
||
?>
|
||
|
||
<!doctype html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||
<?php include('cssinclude.php'); ?>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2Lw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||
<link rel="stylesheet" href="https://cdn.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;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
|
||
input.auto-input,
|
||
select.auto-input {
|
||
background-color: #d4edda;
|
||
}
|
||
|
||
input.manual-input,
|
||
select.manual-input {
|
||
background-color: #fff3cd;
|
||
}
|
||
|
||
input.required-input,
|
||
select.required-input {
|
||
background-color: #f8d7da;
|
||
}
|
||
|
||
input.required-input,
|
||
select.required-input {
|
||
background-color: #f8d7da;
|
||
border-color: #ced4da !important;
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
input,
|
||
select,
|
||
textarea {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 4px;
|
||
padding: 5px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
input,
|
||
select,
|
||
textarea {
|
||
color: #333;
|
||
}
|
||
|
||
textarea {
|
||
resize: vertical;
|
||
min-height: 60px;
|
||
border: 1px solid #ced4da !important;
|
||
}
|
||
|
||
textarea:focus,
|
||
textarea:active,
|
||
textarea:hover {
|
||
border: 1px solid #ced4da !important;
|
||
outline: none !important;
|
||
}
|
||
|
||
textarea.auto-input {
|
||
background-color: #d4edda;
|
||
}
|
||
|
||
textarea.manual-input {
|
||
background-color: #fff3cd;
|
||
}
|
||
|
||
textarea.required-input {
|
||
background-color: #f8d7da;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
border-radius: 12px;
|
||
text-align: center;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.status-i {
|
||
background-color: #ffc107;
|
||
color: #212529;
|
||
}
|
||
|
||
.status-P {
|
||
background-color: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.status-l {
|
||
background-color: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.grid-container {
|
||
overflow-x: auto;
|
||
width: 100%;
|
||
margin-bottom: 20px;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.25rem;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
position: relative;
|
||
}
|
||
|
||
.grid-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.grid-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.grid-row:nth-child(even) {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.grid-row:hover {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.grid-header,
|
||
.grid-cell {
|
||
flex: 1;
|
||
min-width: 70px;
|
||
padding: 12px 15px;
|
||
border-right: 1px solid #dee2e6;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
transition: max-width 0.3s ease;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.grid-header {
|
||
font-weight: 600;
|
||
background-color: #e9ecef;
|
||
color: #495057;
|
||
border-bottom: 2px solid #dee2e6;
|
||
position: relative;
|
||
}
|
||
|
||
.grid-header:last-child,
|
||
.grid-cell:last-child {
|
||
border-right: none;
|
||
}
|
||
|
||
/* Sticky columns - first column (Actions) */
|
||
.grid-top .grid-cell.save-all-cell,
|
||
.grid-header.button-header,
|
||
.grid-cell.button-cell {
|
||
position: sticky !important;
|
||
left: 0;
|
||
z-index: 9;
|
||
background: white;
|
||
overflow: visible;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.grid-header.button-header {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.grid-row:nth-child(even) .grid-cell.button-cell {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.grid-row:hover .grid-cell.button-cell {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.grid-top .grid-cell:nth-child(2),
|
||
.grid-row .grid-header:nth-child(2),
|
||
.grid-row .grid-cell:nth-child(2) {
|
||
position: sticky !important;
|
||
left: 210px;
|
||
z-index: 8;
|
||
background: white;
|
||
overflow: visible;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.grid-row .grid-header:nth-child(2) {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.grid-row:nth-child(even) .grid-cell:nth-child(2) {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.grid-row:hover .grid-cell:nth-child(2) {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.grid-row {
|
||
position: relative;
|
||
}
|
||
|
||
.grid-cell.expanded,
|
||
.grid-header.expanded {
|
||
max-width: 500px !important;
|
||
white-space: normal !important;
|
||
overflow-wrap: break-word !important;
|
||
background-color: #e0f7fa !important;
|
||
flex: 0 0 500px !important;
|
||
}
|
||
|
||
.grid-header.expanded {
|
||
background-color: #e0f7fa !important;
|
||
}
|
||
|
||
.resizer {
|
||
width: 5px;
|
||
height: 100%;
|
||
background: #ddd;
|
||
cursor: col-resize;
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
}
|
||
|
||
.resizer:hover {
|
||
background: #999;
|
||
}
|
||
|
||
.grid-top {
|
||
display: flex;
|
||
align-items: stretch;
|
||
padding: 10px 0;
|
||
min-height: 0;
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
.grid-top .grid-cell {
|
||
padding: 5px 10px;
|
||
flex: 0 0 150px;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
overflow: visible;
|
||
}
|
||
|
||
.grid-top .save-all-cell {
|
||
flex: 0 0 210px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.grid-top .grid-cell input,
|
||
.grid-top .grid-cell select,
|
||
.grid-top .grid-cell .date-picker,
|
||
.grid-top .grid-cell .fixed-top {
|
||
position: relative;
|
||
max-width: 100%;
|
||
z-index: 1;
|
||
}
|
||
|
||
.propagate-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #666;
|
||
font-size: 14px;
|
||
margin-top: 5px;
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
transition: color 0.3s ease;
|
||
}
|
||
|
||
.propagate-btn:hover {
|
||
color: #28a745;
|
||
}
|
||
|
||
.awb-input {
|
||
width: 40% !important;
|
||
display: inline-block;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.carrier-select {
|
||
width: 40% !important;
|
||
display: inline-block;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 4px;
|
||
padding: 5px;
|
||
font-size: 14px;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.go-btn {
|
||
width: 15% !important;
|
||
display: inline-block;
|
||
}
|
||
|
||
.tracking-info .tracking-result {
|
||
font-size: 12px;
|
||
color: #495057;
|
||
}
|
||
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1050;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: auto;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
#partsModal {
|
||
z-index: 1200 !important;
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: #fefefe;
|
||
margin: 15% auto;
|
||
padding: 20px;
|
||
border: 1px solid #888;
|
||
width: 80%;
|
||
max-width: 600px;
|
||
border-radius: 8px;
|
||
position: relative;
|
||
}
|
||
|
||
.close-btn {
|
||
color: #aaa;
|
||
float: right;
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.close-btn:hover,
|
||
.close-btn:focus {
|
||
color: #000;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 1049;
|
||
}
|
||
|
||
/* Required empty cell: keep light red background BUT NO red border */
|
||
.grid-cell.missing-required {
|
||
background-color: #ffe6e6 !important;
|
||
|
||
/* remove the red border you set */
|
||
border: 0 !important;
|
||
|
||
/* keep grid separators */
|
||
border-right: 1px solid #dee2e6 !important;
|
||
}
|
||
|
||
/* if it is the last column, don't force the right border */
|
||
.grid-cell.missing-required:last-child {
|
||
border-right: none !important;
|
||
}
|
||
|
||
|
||
.dropdown-select {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 4px;
|
||
padding: 5px;
|
||
font-size: 14px;
|
||
appearance: none;
|
||
background: white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="%23333"><path d="M7.293 4.293a1 1 0 011.414 0L10 6.586l1.293-1.293a1 1 0 111.414 1.414l-2 2a1 1 0 01-1.414 0l-2-2a1 1 0 010-1.414z"/></svg>') no-repeat right 5px center;
|
||
}
|
||
|
||
.dropdown-select:focus {
|
||
outline: none;
|
||
border-color: #80bdff;
|
||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||
}
|
||
|
||
.grid-cell.button-cell,
|
||
.grid-header.button-header {
|
||
min-width: 210px !important;
|
||
flex: 0 0 210px !important;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 6px 8px;
|
||
margin-right: 5px;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
width: 35px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.flash-success {
|
||
background-color: #d4edda !important;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
|
||
.save-all-btn {
|
||
background-color: #28a745;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 5px;
|
||
padding: 8px 16px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.save-all-btn:hover {
|
||
background-color: #218838;
|
||
}
|
||
|
||
#exportConfirmModal,
|
||
#exportResponseModal {
|
||
z-index: 1300 !important;
|
||
}
|
||
|
||
#exportConfirmModal .modal-backdrop,
|
||
#exportResponseModal .modal-backdrop {
|
||
z-index: 1299 !important;
|
||
}
|
||
|
||
.add-part-btn {
|
||
padding: 2px 5px;
|
||
font-size: 10px;
|
||
line-height: 1;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.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-results__options {
|
||
max-height: 400px !important;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.api-fixed-select+.select2-container .select2-selection__rendered {
|
||
color: #333;
|
||
}
|
||
|
||
.api-fixed-select option[value=""] {
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
|
||
.api-fixed-select.required-input:invalid,
|
||
.api-fixed-select[required]:not([value]):not([data-select2-id]) {
|
||
background-color: #f8d7da !important;
|
||
border-color: #dc3545 !important;
|
||
}
|
||
</style>
|
||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="wrapper">
|
||
<?php include('include/navbar.php'); ?>
|
||
<?php include('include/topbar.php'); ?>
|
||
<div class="page-wrapper">
|
||
<div class="page-content">
|
||
<div class="mb-3 text">
|
||
<a href="historical_trf.php?id=<?= $template_id ?>&status=i" class="btn btn-warning me-2">Imported (i)</a>
|
||
<a href="historical_trf.php?id=<?= $template_id ?>&status=P" class="btn btn-primary me-2">In Progress (P)</a>
|
||
<a href="historical_trf.php?id=<?= $template_id ?>&status=l" class="btn btn-success">To LIMS (l)</a>
|
||
</div>
|
||
<div class="card radius-10">
|
||
<div class="card-header">
|
||
<div class="d-flex align-items-center">
|
||
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; margin:10px 0;">
|
||
⚠️ Unsaved changes detected! Please save before leaving this page.<br>
|
||
<span id="changedRows" style="font-weight:normal; color:darkred;"></span>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="editForm">
|
||
<div class="grid-container">
|
||
<div class="grid-top">
|
||
<div class="grid-cell save-all-cell">
|
||
<button type="button" class="save-all-btn" title="Save All Rows"><i class="fas fa-save"></i> Save All</button>
|
||
</div>
|
||
<?php
|
||
|
||
$topColIndex = 1;
|
||
|
||
if ($mainFieldMapping): ?>
|
||
<div class="grid-cell grid-top-cell" style="flex: 0 0 150px;" data-index="1">
|
||
<?php
|
||
$fieldValue = $mainFieldMapping['manual_default'] ?? '';
|
||
if ($mainFieldMapping['data_type'] === 'Data' && $mainFieldMapping['manual_default'] === 'today') {
|
||
$fieldValue = date('Y-m-d');
|
||
}
|
||
$inputClass = $mainFieldMapping['is_manual'] ? 'manual-input' : 'auto-input';
|
||
if ($mainFieldMapping['is_required']) $inputClass .= ' required-input';
|
||
if ($mainFieldMapping['data_type'] === 'SceltaMultipla') {
|
||
echo "<select class='custom-field dropdown-select $inputClass' data-column='main_field' data-field-id='{$mainFieldMapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<option value=''>Seleziona...</option>";
|
||
echo "</select>";
|
||
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
|
||
} elseif ($mainFieldMapping['data_type'] === 'Data') {
|
||
echo "<input type='text' class='custom-field date-picker $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
|
||
} elseif ($mainFieldMapping['data_type'] === 'INT') {
|
||
echo "<input type='number' class='custom-field $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
|
||
} else {
|
||
echo "<input type='text' class='custom-field $inputClass' data-column='main_field' value='" . htmlspecialchars($fieldValue) . "' " . ($mainFieldMapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<button type='button' class='propagate-btn' data-column='main_field'><i class='fas fa-arrow-down'></i></button>";
|
||
}
|
||
?>
|
||
</div>
|
||
<?php endif;
|
||
|
||
$topColIndex = $mainFieldMapping ? 2 : 1; ?>
|
||
|
||
<div class="grid-cell grid-top-cell" style="flex: 0 0 300px;" data-index="<?= $mainFieldMapping ? 2 : 1 ?>">
|
||
<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 grid-top-cell" style="flex: 0 0 150px;" data-index="<?= $mainFieldMapping ? 3 : 2 ?>"></div>
|
||
<?php
|
||
|
||
$topColIndex = $mainFieldMapping ? 4 : 3;
|
||
$autoIndex = 0;
|
||
|
||
foreach ($allMappings as $mapping) {
|
||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||
$inputClass = 'auto-input';
|
||
if ($mapping['is_required']) $inputClass .= ' required-input';
|
||
if ($mapping['data_type'] === 'SceltaMultipla') {
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'>";
|
||
echo "<select class='custom-field dropdown-select $inputClass' data-column='auto_$autoIndex' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue ?? '') . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<option value=''>Seleziona...</option>";
|
||
echo "</select>";
|
||
echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>";
|
||
echo "</div>";
|
||
} else {
|
||
// Show auto import date/time in header too (read-only)
|
||
$autoVal = getImportAutoValue($mapping);
|
||
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'>";
|
||
|
||
if (($mapping['auto_value'] ?? 'none') === 'import_date') {
|
||
echo "<input type='text' class='custom-field date-picker auto-input' value='" . htmlspecialchars($autoVal) . "' readonly>";
|
||
echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>";
|
||
} elseif (($mapping['auto_value'] ?? 'none') === 'import_time') {
|
||
echo "<input type='time' class='custom-field auto-input' value='" . htmlspecialchars($autoVal) . "' readonly>";
|
||
echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>";
|
||
} else {
|
||
// keep empty cell for other auto fields
|
||
echo "";
|
||
}
|
||
|
||
echo "</div>";
|
||
}
|
||
|
||
$autoIndex++;
|
||
$topColIndex++;
|
||
}
|
||
}
|
||
|
||
$manualIndex = 0;
|
||
|
||
foreach ($allMappings as $mapping) {
|
||
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||
$fieldValue = $mapping['manual_default'] ?? '';
|
||
if ($mapping['data_type'] === 'Data' && $mapping['manual_default'] === 'today') {
|
||
$fieldValue = date('Y-m-d');
|
||
}
|
||
$inputClass = 'manual-input';
|
||
if ($mapping['is_required']) $inputClass .= ' required-input';
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'>";
|
||
if ($mapping['data_type'] === 'SceltaMultipla') {
|
||
echo "<select class='custom-field dropdown-select $inputClass' data-column='manual_$manualIndex' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<option value=''>Seleziona...</option>";
|
||
echo "</select>";
|
||
} elseif ($mapping['data_type'] === 'Data') {
|
||
echo "<input type='text' class='custom-field date-picker $inputClass' data-column='manual_$manualIndex' value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif ($mapping['data_type'] === 'INT') {
|
||
echo "<input type='number' class='custom-field $inputClass' data-column='manual_$manualIndex' value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif (in_array($mapping['field_label'], $timeLabels)) {
|
||
echo "<input type='time' class='custom-field $inputClass' data-column='manual_$manualIndex' value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} else {
|
||
echo "<input type='text' class='custom-field $inputClass' data-column='manual_$manualIndex' value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
}
|
||
echo "<button type='button' class='propagate-btn' data-column='manual_$manualIndex'><i class='fas fa-arrow-down'></i></button>";
|
||
echo "</div>";
|
||
|
||
$manualIndex++;
|
||
$topColIndex++;
|
||
}
|
||
}
|
||
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 200px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 250px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
// ---------------- FIXED FIELDS TOP (propagate) - same order as header: fixed cols, 3 empties after ConsegnaRichiesta ----------------
|
||
if (!empty($fixedFields)) {
|
||
$insertedAfterConsegnaTop = false;
|
||
foreach ($fixedFields as $fx => $f) {
|
||
|
||
$key = $f['fixed_field_key']; // datadb column
|
||
|
||
$val = fixedDefaultValue($f);
|
||
|
||
$isRequired = ((int)$f['is_required'] === 1);
|
||
$inputClass = 'manual-input' . ($isRequired ? ' required-input' : '');
|
||
$topRequiredClass = ($isRequired && ($val === '' || $val === null)) ? 'missing-required' : '';
|
||
|
||
echo "<div class='grid-cell grid-top-cell {$topRequiredClass}' style='flex: 0 0 180px;' data-index='$topColIndex'>";
|
||
$topColIndex++;
|
||
|
||
// Forza DATE anche se per errore nel DB è diversa
|
||
$isDate = ($f['data_type'] === 'DATE' || $key === 'ConsegnaRichiesta');
|
||
|
||
if ($isDate) {
|
||
|
||
echo "<input type='text'
|
||
class='custom-field date-picker {$inputClass} fixed-top'
|
||
data-column='fixed_{$fx}'
|
||
data-fixed-key='" . htmlspecialchars($key, ENT_QUOTES) . "'
|
||
value='" . htmlspecialchars((string)$val, ENT_QUOTES) . "'
|
||
" . ($isRequired ? "required" : "") . ">";
|
||
} else {
|
||
|
||
$isApiField = in_array($key, [
|
||
'MoltiplicatorePrezzo',
|
||
'ClienteResponsabile',
|
||
'AnagraficaCertestObject',
|
||
'AnagraficaCertestService'
|
||
], true);
|
||
|
||
if ($isApiField) {
|
||
$placeholder = ($key === 'ClienteResponsabile') ? 'Seleziona cliente prima...' : 'Seleziona...';
|
||
|
||
echo "<select
|
||
class='custom-field dropdown-select api-fixed-select {$inputClass} fixed-top'
|
||
data-column='fixed_{$fx}'
|
||
data-fixed-key='" . htmlspecialchars($key, ENT_QUOTES) . "'
|
||
" . ($isRequired ? "required" : "") . ">
|
||
<option value=''>{$placeholder}</option>
|
||
</select>";
|
||
} else {
|
||
|
||
echo "<input type='number'
|
||
class='custom-field {$inputClass} fixed-top'
|
||
data-column='fixed_{$fx}'
|
||
data-fixed-key='" . htmlspecialchars($key, ENT_QUOTES) . "'
|
||
value='" . htmlspecialchars((string)$val, ENT_QUOTES) . "'
|
||
" . ($isRequired ? "required" : "") . ">";
|
||
}
|
||
}
|
||
|
||
// UNA SOLA freccia
|
||
echo "<button type='button' class='propagate-btn' data-column='fixed_{$fx}'><i class='fas fa-arrow-down'></i></button>";
|
||
echo "</div>";
|
||
|
||
if ($key === 'ConsegnaRichiesta' && !$insertedAfterConsegnaTop) {
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'></div>";
|
||
$topColIndex++;
|
||
$insertedAfterConsegnaTop = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
?>
|
||
</div>
|
||
|
||
<div class="grid-row">
|
||
<div class="grid-header button-header" style="flex: 0 0 210px;">Actions</div>
|
||
<?php if ($mainFieldMapping): ?>
|
||
<div class="grid-header" data-index="1" style="flex: 0 0 150px; position: relative;">
|
||
<?= htmlspecialchars($mainFieldMapping['field_label']) ?>
|
||
<div class="resizer"></div>
|
||
</div>
|
||
<?php endif; ?>
|
||
<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 ? 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>";
|
||
$headerIndex++;
|
||
}
|
||
}
|
||
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>";
|
||
$headerIndex++;
|
||
}
|
||
}
|
||
// Aggiunta header per Tested Component
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>Tested Component<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 200px; position: relative;'>AWB Number<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 250px; position: relative;'>Tracking Info<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
|
||
// ---------------- FIXED FIELDS HEADERS ----------------
|
||
if (!empty($fixedFields)) {
|
||
$insertedAfterConsegna = false;
|
||
foreach ($fixedFields as $f) {
|
||
$key = $f['fixed_field_key'];
|
||
$label = $slugMapping[$key] ?? $key;
|
||
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 180px; position: relative;'>"
|
||
. htmlspecialchars($label) .
|
||
"<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
|
||
// Inseriamo le 3 colonne SOLO dopo ConsegnaRichiesta
|
||
if ($key === 'ConsegnaRichiesta' && !$insertedAfterConsegna) {
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>Import Reference Code<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . ($slugMapping['filename_import'] ?? 'filename_import') . "<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
echo "<div class='grid-header' data-index='$headerIndex' style='flex: 0 0 150px; position: relative;'>" . ($slugMapping['importdate'] ?? 'importdate') . "<div class='resizer'></div></div>";
|
||
$headerIndex++;
|
||
$insertedAfterConsegna = true;
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
|
||
</div>
|
||
|
||
<?php foreach ($importedData as $index => $row): ?>
|
||
<div class="grid-row" data-id="<?= $row['iddatadb'] ?>">
|
||
<div class="grid-cell button-cell" style="flex: 0 0 210px; position: relative;">
|
||
<!-- commented only for admin roles -->
|
||
<?php if ((Auth::user()->hasRole('Admin'))) : ?>
|
||
<button type="button" class="export-lims-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Export to LIMS" style="background: #eb0b0bff; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-upload"></i></button>
|
||
<?php endif; ?>
|
||
|
||
<button type="button" class="save-btn action-btn" data-row="<?= $index ?>" title="Save" style="background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-save"></i></button>
|
||
<button type="button" class="photos-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Photos" style="background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-camera"></i></button>
|
||
<button type="button" class="parts-btn action-btn" data-row="<?= $index ?>" data-iddatadb="<?= $row['iddatadb'] ?>" title="Parts" style="background: #ffc107; color: white; border: none; border-radius: 5px; cursor: pointer;"><i class="fas fa-puzzle-piece"></i></button>
|
||
</div>
|
||
<?php if ($mainFieldMapping): ?>
|
||
<?php
|
||
$detail = array_filter($manualDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id'] && $d['datadb_id'] == $row['iddatadb']);
|
||
$detail = reset($detail) ?: ['field_value' => $mainFieldMapping['manual_default']];
|
||
$fieldValue = $detail['field_value'] ?? $mainFieldMapping['manual_default'] ?? '';
|
||
if ($mainFieldMapping['data_type'] === 'Data' && $mainFieldMapping['manual_default'] === 'today' && empty($fieldValue)) {
|
||
$fieldValue = date('Y-m-d');
|
||
}
|
||
$requiredClass = ($mainFieldMapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) ? 'missing-required' : '';
|
||
$inputClass = $mainFieldMapping['is_manual'] ? 'manual-input' : 'auto-input';
|
||
if ($mainFieldMapping['is_required']) $inputClass .= ' required-input';
|
||
?>
|
||
<div class="grid-cell editable-cell <?= $requiredClass ?>" data-col="main_field" data-row="<?= $index ?>" data-index="1" style="flex: 0 0 150px;">
|
||
<?php if ($mainFieldMapping['data_type'] === 'SceltaMultipla'): ?>
|
||
<select name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" class="cell-input dropdown-select <?= $inputClass ?>" data-mapping-id="<?= $mainFieldMapping['id'] ?>" data-field-id="<?= $mainFieldMapping['field_id'] ?>" data-selected-value="<?= htmlspecialchars($fieldValue) ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
|
||
<option value="">Seleziona...</option>
|
||
</select>
|
||
<?php elseif ($mainFieldMapping['data_type'] === 'Data'): ?>
|
||
<input type="text" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input date-picker <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
|
||
<?php elseif ($mainFieldMapping['data_type'] === 'INT'): ?>
|
||
<input type="number" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
|
||
<?php else: ?>
|
||
<input type="text" name="rows[<?= $index ?>][details][<?= $mainFieldMapping['id'] ?>][field_value]" value="<?= htmlspecialchars($fieldValue) ?>" class="cell-input <?= $inputClass ?>" <?= $mainFieldMapping['is_required'] ? 'required' : '' ?>>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
<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 ? 4 : 3;
|
||
$rowDetails = array_filter($manualDetails, fn($d) => $d['datadb_id'] == $row['iddatadb']);
|
||
$autoIndex = 0;
|
||
foreach ($allMappings as $mapping) {
|
||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||
$detail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mapping['id']);
|
||
$detail = reset($detail) ?: ['field_value' => $mapping['manual_default']];
|
||
$fieldValue = $detail['field_value'] ?? $mapping['manual_default'] ?? '';
|
||
|
||
// Auto-fill import date/time only if empty
|
||
if ($fieldValue === '' || $fieldValue === null) {
|
||
$autoVal = getImportAutoValue($mapping);
|
||
if ($autoVal !== '') {
|
||
$fieldValue = $autoVal;
|
||
}
|
||
}
|
||
$requiredClass = ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) ? 'missing-required' : '';
|
||
$inputClass = 'auto-input';
|
||
if ($mapping['is_required']) $inputClass .= ' required-input';
|
||
echo "<div class='grid-cell editable-cell $requiredClass' data-col='auto_$autoIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
|
||
if ($mapping['data_type'] === 'SceltaMultipla') {
|
||
echo "<select name='rows[$index][details][{$mapping['id']}][field_value]' class='cell-input dropdown-select $inputClass' data-mapping-id='{$mapping['id']}' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<option value=''>Seleziona...</option>";
|
||
echo "</select>";
|
||
} elseif ($mapping['data_type'] === 'Data') {
|
||
echo "<input type='text' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input date-picker $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif ($mapping['data_type'] === 'INT') {
|
||
echo "<input type='number' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif (($mapping['auto_value'] ?? 'none') === 'import_time') {
|
||
echo "<input type='time' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif (in_array($mapping['field_label'], $timeLabels)) {
|
||
echo "<input type='time' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} else {
|
||
echo "<input type='text' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
}
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
$autoIndex++;
|
||
}
|
||
}
|
||
$manualIndex = 0;
|
||
foreach ($allMappings as $mapping) {
|
||
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||
$detail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mapping['id']);
|
||
$detail = reset($detail) ?: ['field_value' => $mapping['manual_default']];
|
||
$fieldValue = $detail['field_value'] ?? $mapping['manual_default'] ?? '';
|
||
if ($mapping['data_type'] === 'Data' && $mapping['manual_default'] === 'today' && empty($fieldValue)) {
|
||
$fieldValue = date('Y-m-d');
|
||
}
|
||
$requiredClass = ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) ? 'missing-required' : '';
|
||
$inputClass = 'manual-input';
|
||
if ($mapping['is_required']) $inputClass .= ' required-input';
|
||
echo "<div class='grid-cell editable-cell $requiredClass' data-col='manual_$manualIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
|
||
if ($mapping['data_type'] === 'SceltaMultipla') {
|
||
echo "<select name='rows[$index][details][{$mapping['id']}][field_value]' class='cell-input dropdown-select $inputClass' data-mapping-id='{$mapping['id']}' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
echo "<option value=''>Seleziona...</option>";
|
||
echo "</select>";
|
||
} elseif ($mapping['data_type'] === 'Data') {
|
||
echo "<input type='text' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input date-picker $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif ($mapping['data_type'] === 'INT') {
|
||
echo "<input type='number' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} elseif (in_array($mapping['field_label'], $timeLabels)) {
|
||
echo "<input type='time' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
} else {
|
||
echo "<input type='text' name='rows[$index][details][{$mapping['id']}][field_value]' value='" . htmlspecialchars($fieldValue) . "' class='cell-input $inputClass' " . ($mapping['is_required'] ? 'required' : '') . ">";
|
||
}
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
$manualIndex++;
|
||
}
|
||
}
|
||
// Aggiunta cella per Tested Component
|
||
echo "<div class='grid-cell editable-cell' data-col='tested_component' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px; display: flex; align-items: center;'>";
|
||
echo "<input type='text' name='rows[$index][tested_component]' value='' class='cell-input manual-input' style='flex: 1; margin-right: 5px;'>";
|
||
echo "<button type='button' class='add-part-btn btn btn-sm btn-primary' data-row='$index'><i class='fas fa-plus'></i></button>";
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
?>
|
||
<div class="grid-cell" data-index="<?= $cellIndex ?>" style="flex: 0 0 200px;">
|
||
<select name="rows[<?= $index ?>][carrier]" class="carrier-select">
|
||
<option value="tnt-it">TNT Italy</option>
|
||
<option value="dhl">DHL</option>
|
||
<option value="gls">GLS</option>
|
||
<option value="sda">SDA</option>
|
||
<option value="ups">UPS</option>
|
||
</select>
|
||
<input type="text" name="rows[<?= $index ?>][awb_number]" class="cell-input awb-input" placeholder="Inserisci AWB Number">
|
||
<button type="button" class="go-btn" data-row="<?= $index ?>"><i class="fas fa-play"></i></button>
|
||
</div>
|
||
<?php $cellIndex++; ?>
|
||
<div class="grid-cell tracking-info" data-row="<?= $index ?>" data-index="<?= $cellIndex ?>" style="flex: 0 0 250px;">
|
||
<span class="tracking-result">Shipment Info</span>
|
||
<input type="hidden" name="rows[<?= $index ?>][tracking_info]" class="tracking-hidden">
|
||
</div>
|
||
<?php $cellIndex++; ?>
|
||
|
||
<?php
|
||
// ---------------- FIXED FIELDS CELLS ----------------
|
||
if (!empty($fixedFields)) {
|
||
foreach ($fixedFields as $f) {
|
||
$key = $f['fixed_field_key'];
|
||
$val = $row[$key] ?? '';
|
||
if ($val === '' || $val === null) {
|
||
$val = fixedDefaultValue($f);
|
||
}
|
||
$requiredClass = ((int)$f['is_required'] === 1 && ($val === '' || $val === null)) ? 'missing-required' : '';
|
||
$inputClass = 'manual-input';
|
||
if ((int)$f['is_required'] === 1) $inputClass .= ' required-input';
|
||
|
||
echo "<div class='grid-cell editable-cell $requiredClass'
|
||
data-col='" . htmlspecialchars($key, ENT_QUOTES) . "'
|
||
data-row='$index'
|
||
data-index='$cellIndex'
|
||
style='flex: 0 0 180px;'>";
|
||
|
||
if ($f['data_type'] === 'DATE') {
|
||
echo "<input type='text'
|
||
name='rows[$index][$key]'
|
||
value='" . htmlspecialchars((string)$val, ENT_QUOTES) . "'
|
||
class='cell-input date-picker $inputClass fixed-input'
|
||
data-fixed-key='" . htmlspecialchars($key, ENT_QUOTES) . "' "
|
||
. (((int)$f['is_required'] === 1) ? "required" : "")
|
||
. ">";
|
||
} else {
|
||
$isApiField = in_array($key, ['MoltiplicatorePrezzo', 'ClienteResponsabile', 'AnagraficaCertestObject', 'AnagraficaCertestService'], true);
|
||
$selectClass = $isApiField ? 'api-fixed-select' : '';
|
||
echo "<select
|
||
name='rows[$index][$key]'
|
||
class='cell-input $inputClass fixed-input $selectClass'
|
||
data-fixed-key='" . htmlspecialchars($key, ENT_QUOTES) . "'
|
||
data-current-value='" . htmlspecialchars((string)$val, ENT_QUOTES) . "'
|
||
" . (((int)$f['is_required'] === 1) ? "required" : "") . ">
|
||
<option value=''>Caricamento...</option>
|
||
</select>";
|
||
}
|
||
echo "</div>";
|
||
|
||
$cellIndex++;
|
||
|
||
// === INSERIMENTO dopo ConsegnaRichiesta ===
|
||
if ($key === 'ConsegnaRichiesta') {
|
||
// Import Reference Code
|
||
echo "<div class='grid-cell' data-col='importreferencecode' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
|
||
echo "<span>" . htmlspecialchars($row['importreferencecode']) . "</span>";
|
||
echo "<input type='hidden' name='rows[$index][importreferencecode]' value='" . htmlspecialchars($row['importreferencecode']) . "'>";
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
|
||
// filename_import
|
||
echo "<div class='grid-cell editable-cell' data-col='filename_import' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
|
||
echo "<a href='imported_trf/" . htmlspecialchars($row['filename_import']) . "' target='_blank'>File</a>";
|
||
echo "<input type='hidden' name='rows[$index][filename_import]' value='" . htmlspecialchars($row['filename_import']) . "'>";
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
|
||
// importdate
|
||
echo "<div class='grid-cell editable-cell' data-col='importdate' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
|
||
echo "<span>" . htmlspecialchars($row['importdate']) . "</span>";
|
||
echo "<input type='hidden' name='rows[$index][importdate]' value='" . htmlspecialchars($row['importdate']) . "'>";
|
||
echo "</div>";
|
||
$cellIndex++;
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</form>
|
||
<div id="partsModalContainer"></div>
|
||
<div id="annotationsModalContainer"></div>
|
||
<?php include 'photos_functions.php'; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="overlay toggle-icon"></div>
|
||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||
<?php include('include/footer.php'); ?>
|
||
</div>
|
||
<?php include('jsinclude.php'); ?>
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
<script 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="annotationsModal.js"></script>
|
||
<script src="partsTable.js"></script>
|
||
<script src="tracking.js"></script>
|
||
<script src="export_to_lims.js"></script>
|
||
<script>
|
||
function expandColumn(cell, expand) {
|
||
const columnIndex = cell.getAttribute('data-index');
|
||
if (!columnIndex) return;
|
||
|
||
const allColumnCells = document.querySelectorAll(`.grid-cell[data-index="${columnIndex}"], .grid-header[data-index="${columnIndex}"], .grid-top-cell[data-index="${columnIndex}"]`);
|
||
|
||
allColumnCells.forEach(colCell => {
|
||
if (expand) {
|
||
colCell.classList.add('expanded');
|
||
} else {
|
||
colCell.classList.remove('expanded');
|
||
}
|
||
});
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
flatpickr(".date-picker", {
|
||
dateFormat: "Y-m-d",
|
||
allowInput: true,
|
||
onOpen: function(selectedDates, dateStr, instance) {
|
||
const cell = instance.input.closest('.grid-cell');
|
||
expandColumn(cell, true);
|
||
},
|
||
onClose: function(selectedDates, dateStr, instance) {
|
||
const cell = instance.input.closest('.grid-cell');
|
||
expandColumn(cell, false);
|
||
// Trigger change event to handle unsaved changes
|
||
const event = new Event('change');
|
||
instance.input.dispatchEvent(event);
|
||
}
|
||
});
|
||
flatpickr(".time-picker", {
|
||
enableTime: true,
|
||
noCalendar: true,
|
||
dateFormat: "H:i",
|
||
time_24hr: true,
|
||
allowInput: true
|
||
});
|
||
});
|
||
|
||
$(document).on("click", ".export-lims-btn", function() {
|
||
let rowId = $(this).data("row");
|
||
let idDataDb = $(this).data("iddatadb");
|
||
|
||
$.ajax({
|
||
url: "export_to_lims.php",
|
||
method: "POST",
|
||
data: {
|
||
iddatadb: idDataDb
|
||
},
|
||
dataType: "json",
|
||
beforeSend: function() {
|
||
alert("Export started in background for row " + rowId);
|
||
},
|
||
success: function(response) {
|
||
if (response.success) {
|
||
alert(response.message);
|
||
} else {
|
||
alert("❌ Error: " + response.message);
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
alert("❌ AJAX error: " + error);
|
||
}
|
||
});
|
||
});
|
||
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const inputs = document.querySelectorAll(".cell-input, .dropdown-select, .carrier-select, .awb-input, .date-picker");
|
||
const unsavedDiv = document.getElementById("unsavedChanges");
|
||
const changedRowsEl = document.getElementById("changedRows");
|
||
let hasChanges = false;
|
||
|
||
// ✅ solo righe modificate (Set)
|
||
let changedRows = new Set();
|
||
|
||
function renderChangedRows() {
|
||
const rows = Array.from(changedRows)
|
||
.map(n => Number(n))
|
||
.sort((a, b) => a - b);
|
||
|
||
if (rows.length === 0) {
|
||
unsavedDiv.style.display = "none";
|
||
changedRowsEl.textContent = "";
|
||
return;
|
||
}
|
||
|
||
// Visuale: "Row 1, Row 5, Row 6"
|
||
changedRowsEl.textContent = rows.map(r => `Row ${r + 1}`).join(", ");
|
||
unsavedDiv.style.display = "block";
|
||
}
|
||
|
||
|
||
inputs.forEach(el => {
|
||
el.addEventListener("change", () => {
|
||
hasChanges = true;
|
||
|
||
const gridCell = el.closest(".grid-cell");
|
||
const rowIndex = gridCell?.dataset.row;
|
||
|
||
// ✅ segnamo solo la riga
|
||
if (rowIndex !== undefined) {
|
||
changedRows.add(rowIndex);
|
||
}
|
||
|
||
// (se vuoi mantenere highlight cella gialla, lascia questa riga)
|
||
if (gridCell) gridCell.classList.add("cell-changed");
|
||
|
||
renderChangedRows();
|
||
});
|
||
});
|
||
|
||
|
||
document.querySelectorAll(".save-btn").forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
const rowIndex = btn.dataset.row;
|
||
const row = btn.closest('.grid-row');
|
||
const iddatadb = row.getAttribute('data-id');
|
||
const formData = new FormData();
|
||
|
||
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}][details]"], select[name^="rows[${rowIndex}][details]"]`);
|
||
inputs.forEach(input => {
|
||
const matches = input.name.match(/rows\[\d+\]\[details\]\[(\d+)\]\[field_value\]/);
|
||
if (matches) {
|
||
const mappingId = matches[1];
|
||
formData.append(`details${mappingId}field_value`, input.value);
|
||
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);
|
||
}
|
||
|
||
// ---- FIXED FIELDS (NEW) ----
|
||
const fixedInputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]["], select[name^="rows[${rowIndex}]["]`);
|
||
fixedInputs.forEach(inp => {
|
||
// prendo solo quelli che NON sono details e NON idclient/status/importdate/filename/importreferencecode ecc.
|
||
// filtro tramite classe fixed-input (che abbiamo messo)
|
||
if (!inp.classList.contains('fixed-input')) return;
|
||
|
||
const m = inp.name.match(/rows\[\d+\]\[([^\]]+)\]/);
|
||
// Map: fixed key (logical) -> datadb real column
|
||
const fixedAliasMap = {
|
||
ClienteResponsabile: 'cliente_responsabile_id',
|
||
MoltiplicatorePrezzo: 'moltiplicatore_prezzo_id',
|
||
AnagraficaCertestObject: 'anagrafica_certest_object_id',
|
||
AnagraficaCertestService: 'anagrafica_certest_service_id',
|
||
ConsegnaRichiesta: 'consegna_richiesta'
|
||
};
|
||
|
||
if (m && m[1]) {
|
||
const logicalKey = m[1];
|
||
const realKey = fixedAliasMap[logicalKey] || logicalKey;
|
||
formData.append(realKey, inp.value);
|
||
}
|
||
|
||
});
|
||
|
||
formData.append('iddatadb', iddatadb);
|
||
|
||
fetch('save_edited_row.php', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
if (data.success) {
|
||
const cells = row.querySelectorAll('.grid-cell');
|
||
cells.forEach(cell => {
|
||
cell.classList.remove('flash-success');
|
||
void cell.offsetWidth;
|
||
cell.classList.add('flash-success');
|
||
});
|
||
setTimeout(() => cells.forEach(cell => cell.classList.remove('flash-success')), 500);
|
||
|
||
// ✅ rimuovi riga dal set (non mostrare più Row X)
|
||
changedRows.delete(rowIndex);
|
||
|
||
// se vuoi continuare a togliere highlight giallo di TUTTA la riga:
|
||
document.querySelectorAll(`.grid-cell[data-row="${rowIndex}"]`)
|
||
.forEach(cell => cell.classList.remove("cell-changed"));
|
||
|
||
renderChangedRows();
|
||
hasChanges = changedRows.size > 0;
|
||
|
||
|
||
alert('Salvataggio riga avvenuto con successo!');
|
||
} else {
|
||
alert('Errore durante il salvataggio: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
alert('Errore durante il salvataggio: ' + error.message);
|
||
});
|
||
});
|
||
});
|
||
|
||
document.querySelector('.save-all-btn').addEventListener('click', async () => {
|
||
const rows = document.querySelectorAll('.grid-row');
|
||
let successCount = 0;
|
||
let errorMessages = [];
|
||
|
||
for (const row of rows) {
|
||
const saveBtn = row.querySelector('.save-btn');
|
||
if (!saveBtn) {
|
||
continue;
|
||
}
|
||
const rowIndex = saveBtn.dataset.row;
|
||
const iddatadb = row.getAttribute('data-id');
|
||
if (!rowIndex || !iddatadb) {
|
||
continue;
|
||
}
|
||
const formData = new FormData();
|
||
|
||
const inputs = row.querySelectorAll(`input[name^="rows[${rowIndex}][details]"], select[name^="rows[${rowIndex}][details]"]`);
|
||
inputs.forEach(input => {
|
||
const matches = input.name.match(/rows\[\d+\]\[details\]\[(\d+)\]\[field_value\]/);
|
||
if (matches) {
|
||
const mappingId = matches[1];
|
||
formData.append(`details${mappingId}field_value`, input.value);
|
||
if (input.tagName === 'SELECT' && input.classList.contains('dropdown-select')) {
|
||
input.setAttribute('data-selected-value', input.value);
|
||
}
|
||
}
|
||
});
|
||
|
||
const idclientSelect = row.querySelector(`select[name="rows[${rowIndex}][idclient]"]`);
|
||
if (idclientSelect) {
|
||
formData.append('idclient', idclientSelect.value);
|
||
}
|
||
// ---- FIXED FIELDS (NEW) ----
|
||
const fixedInputs = row.querySelectorAll(`input[name^="rows[${rowIndex}]["], select[name^="rows[${rowIndex}]["]`);
|
||
fixedInputs.forEach(inp => {
|
||
if (!inp.classList.contains('fixed-input')) return;
|
||
const m = inp.name.match(/rows\[\d+\]\[([^\]]+)\]/);
|
||
// Map: fixed key (logical) -> datadb real column
|
||
const fixedAliasMap = {
|
||
ClienteResponsabile: 'cliente_responsabile_id',
|
||
MoltiplicatorePrezzo: 'moltiplicatore_prezzo_id',
|
||
AnagraficaCertestObject: 'anagrafica_certest_object_id',
|
||
AnagraficaCertestService: 'anagrafica_certest_service_id',
|
||
ConsegnaRichiesta: 'consegna_richiesta'
|
||
};
|
||
|
||
if (m && m[1]) {
|
||
const logicalKey = m[1];
|
||
const realKey = fixedAliasMap[logicalKey] || logicalKey;
|
||
formData.append(realKey, inp.value);
|
||
}
|
||
|
||
});
|
||
|
||
formData.append('iddatadb', iddatadb);
|
||
|
||
try {
|
||
const response = await fetch('save_edited_row.php', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
successCount++;
|
||
const cells = row.querySelectorAll('.grid-cell');
|
||
cells.forEach(cell => {
|
||
cell.classList.remove('flash-success');
|
||
void cell.offsetWidth;
|
||
cell.classList.add('flash-success');
|
||
});
|
||
setTimeout(() => cells.forEach(cell => cell.classList.remove('flash-success')), 500);
|
||
|
||
changedRows.delete(rowIndex);
|
||
document.querySelectorAll(`.grid-cell[data-row="${rowIndex}"]`)
|
||
.forEach(cell => cell.classList.remove("cell-changed"));
|
||
|
||
} else {
|
||
errorMessages.push(`Riga ${parseInt(rowIndex) + 1}: ${data.message}`);
|
||
}
|
||
} catch (error) {
|
||
errorMessages.push(`Riga ${parseInt(rowIndex) + 1}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
renderChangedRows();
|
||
hasChanges = changedRows.size > 0;
|
||
|
||
|
||
if (errorMessages.length === 0) {
|
||
alert(`Tutte le ${successCount} righe salvate con successo!`);
|
||
} else {
|
||
alert(`Salvate ${successCount} righe con successo.\nErrori:\n${errorMessages.join('\n')}`);
|
||
}
|
||
});
|
||
|
||
window.addEventListener("beforeunload", function(e) {
|
||
if (hasChanges) {
|
||
e.preventDefault();
|
||
e.returnValue = "";
|
||
}
|
||
});
|
||
});
|
||
|
||
// Gestisci la chiusura dei modali per rimuovere i backdrop
|
||
document.querySelectorAll('#exportConfirmModal, #exportResponseModal').forEach(modal => {
|
||
modal.addEventListener('hidden.bs.modal', () => {
|
||
// Rimuovi tutti i backdrop residui
|
||
document.querySelectorAll('.modal-backdrop').forEach(backdrop => {
|
||
backdrop.remove();
|
||
});
|
||
// Ripristina il body
|
||
document.body.classList.remove('modal-open');
|
||
document.body.style.paddingRight = '';
|
||
// Assicurati che l'overlay sia nascosto
|
||
const overlay = document.querySelector('.overlay.toggle-icon');
|
||
if (overlay) {
|
||
overlay.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<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()} - ${client.CodiceNazioneFatturazione} (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.";
|
||
// ✅ force refresh of header dependent fixed fields
|
||
$('#clientSelect').trigger('change');
|
||
$('.grid-top .api-fixed-select[data-fixed-key="ClienteResponsabile"]').trigger('fixed:reload');
|
||
|
||
} 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()} - ${client.CodiceNazioneFatturazione} (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 => {
|
||
if (input.tagName === 'SELECT' ||
|
||
input.classList.contains('date-picker') ||
|
||
input.type === 'number' ||
|
||
input.type === 'date') {
|
||
input.addEventListener('focus', function() {
|
||
const cell = this.closest('.grid-cell');
|
||
expandColumn(cell, true);
|
||
});
|
||
input.addEventListener('blur', function() {
|
||
const cell = this.closest('.grid-cell');
|
||
expandColumn(cell, false);
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (input.type === 'text') {
|
||
input.addEventListener('focus', function() {
|
||
const cell = this.closest('.grid-cell');
|
||
|
||
expandColumn(cell, true);
|
||
cell.dataset.convertingToTextarea = 'true';
|
||
|
||
const textarea = document.createElement('textarea');
|
||
|
||
textarea.value = this.value;
|
||
textarea.name = this.name;
|
||
textarea.className = this.className;
|
||
textarea.required = this.required;
|
||
textarea.style.border = '1px solid #ced4da';
|
||
textarea.setAttribute('data-mapping-id', this.getAttribute('data-mapping-id') || '');
|
||
textarea.setAttribute('data-field-id', this.getAttribute('data-field-id') || '');
|
||
textarea.setAttribute('data-selected-value', this.getAttribute('data-selected-value') || '');
|
||
textarea.setAttribute('data-current-value', this.getAttribute('data-current-value') || '');
|
||
textarea.setAttribute('data-fixed-key', this.getAttribute('data-fixed-key') || '');
|
||
textarea.dataset.originalInput = 'true';
|
||
|
||
this.parentNode.replaceChild(textarea, this);
|
||
|
||
setTimeout(() => {
|
||
textarea.focus();
|
||
delete cell.dataset.convertingToTextarea;
|
||
}, 0);
|
||
});
|
||
} else {
|
||
input.addEventListener('focus', function() {
|
||
const cell = this.closest('.grid-cell');
|
||
expandColumn(cell, true);
|
||
});
|
||
}
|
||
});
|
||
|
||
document.addEventListener('blur', function(e) {
|
||
const element = e.target;
|
||
if (element.tagName === 'TEXTAREA' && element.dataset.originalInput === 'true') {
|
||
const cell = element.closest('.grid-cell');
|
||
|
||
const input = document.createElement('input');
|
||
input.type = 'text';
|
||
input.value = element.value;
|
||
input.name = element.name;
|
||
input.className = element.className;
|
||
input.required = element.required;
|
||
input.setAttribute('data-mapping-id', element.getAttribute('data-mapping-id') || '');
|
||
input.setAttribute('data-field-id', element.getAttribute('data-field-id') || '');
|
||
input.setAttribute('data-selected-value', element.getAttribute('data-selected-value') || '');
|
||
input.setAttribute('data-current-value', element.getAttribute('data-current-value') || '');
|
||
input.setAttribute('data-fixed-key', element.getAttribute('data-fixed-key') || '');
|
||
|
||
element.parentNode.replaceChild(input, element);
|
||
|
||
expandColumn(cell, false);
|
||
|
||
input.addEventListener('focus', function() {
|
||
const cell = this.closest('.grid-cell');
|
||
expandColumn(cell, true);
|
||
cell.dataset.convertingToTextarea = 'true';
|
||
|
||
const textarea = document.createElement('textarea');
|
||
textarea.value = this.value;
|
||
textarea.name = this.name;
|
||
textarea.className = this.className;
|
||
textarea.required = this.required;
|
||
textarea.style.border = '1px solid #ced4da';
|
||
textarea.setAttribute('data-mapping-id', this.getAttribute('data-mapping-id') || '');
|
||
textarea.setAttribute('data-field-id', this.getAttribute('data-field-id') || '');
|
||
textarea.setAttribute('data-selected-value', this.getAttribute('data-selected-value') || '');
|
||
textarea.setAttribute('data-current-value', this.getAttribute('data-current-value') || '');
|
||
textarea.setAttribute('data-fixed-key', this.getAttribute('data-fixed-key') || '');
|
||
textarea.dataset.originalInput = 'true';
|
||
|
||
this.parentNode.replaceChild(textarea, this);
|
||
setTimeout(() => {
|
||
textarea.focus();
|
||
delete cell.dataset.convertingToTextarea;
|
||
}, 0);
|
||
});
|
||
|
||
const changeEvent = new Event('change', {
|
||
bubbles: true
|
||
});
|
||
input.dispatchEvent(changeEvent);
|
||
} else if (element.tagName === 'INPUT' && element.type === 'text') {
|
||
const cell = element.closest('.grid-cell');
|
||
if (cell && !cell.dataset.convertingToTextarea) {
|
||
expandColumn(cell, false);
|
||
}
|
||
}
|
||
}, true);
|
||
|
||
// Gestione della propagazione
|
||
const propagateButtons = document.querySelectorAll('.propagate-btn');
|
||
propagateButtons.forEach(button => {
|
||
button.addEventListener('click', async function() {
|
||
const column = this.getAttribute('data-column');
|
||
const input = this.previousElementSibling;
|
||
const value = $(input).hasClass('select2-hidden-accessible') ?
|
||
$(input).val() :
|
||
(input.tagName === 'SELECT' || input.tagName === 'INPUT' ? 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('select, input');
|
||
if (targetInput) {
|
||
console.log('Setting value on target input:', targetInput, 'with value:', value); // Debug
|
||
if (targetInput.tagName === 'SELECT') {
|
||
if ($(targetInput).hasClass('select2-hidden-accessible')) {
|
||
$(targetInput).val(value).trigger('change');
|
||
} else {
|
||
targetInput.value = value;
|
||
targetInput.dispatchEvent(new Event('change', {
|
||
bubbles: true
|
||
}));
|
||
}
|
||
} else if (targetInput.classList.contains('date-picker')) {
|
||
const flatpickrInstance = targetInput._flatpickr;
|
||
if (flatpickrInstance && value) {
|
||
flatpickrInstance.setDate(value, true);
|
||
}
|
||
const event = new Event('change', {
|
||
bubbles: true
|
||
});
|
||
targetInput.dispatchEvent(event);
|
||
} else {
|
||
targetInput.value = value;
|
||
const event = new Event('change', {
|
||
bubbles: true
|
||
});
|
||
targetInput.dispatchEvent(event);
|
||
}
|
||
} else {
|
||
console.warn('No target input found in cell'); // Debug
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
requestAnimationFrame(() => {
|
||
setTimeout(() => {
|
||
autoFitColumnsByHeaderKeepWider({
|
||
minPx: 150,
|
||
maxPx: 380,
|
||
extraPx: 40,
|
||
ignoreIndexes: [] // es: ['2'] se vuoi escludere Client
|
||
});
|
||
}, 50);
|
||
});
|
||
|
||
// Gestione del ridimensionamento delle colonne
|
||
const resizers = document.querySelectorAll('.resizer');
|
||
let currentResizer = null;
|
||
let startX = 0;
|
||
let startWidth = 0;
|
||
let columnIndex = 0;
|
||
resizers.forEach(resizer => {
|
||
resizer.addEventListener('mousedown', function(e) {
|
||
currentResizer = resizer;
|
||
const header = resizer.parentElement;
|
||
columnIndex = header.getAttribute('data-index');
|
||
startX = e.pageX;
|
||
startWidth = header.offsetWidth;
|
||
document.addEventListener('mousemove', resize);
|
||
document.addEventListener('mouseup', stopResize);
|
||
});
|
||
|
||
function resize(e) {
|
||
if (currentResizer) {
|
||
const deltaX = e.pageX - startX;
|
||
const newWidth = Math.max(80, startWidth + deltaX);
|
||
const headers = document.querySelectorAll(`.grid-header[data-index="${columnIndex}"]`);
|
||
const cells = document.querySelectorAll(`.grid-cell[data-index="${columnIndex}"]`);
|
||
const topCells = document.querySelectorAll(`.grid-top .grid-cell[data-index="${columnIndex}"]`);
|
||
headers.forEach(header => header.style.flex = `0 0 ${newWidth}px`);
|
||
cells.forEach(cell => cell.style.flex = `0 0 ${newWidth}px`);
|
||
topCells.forEach(cell => cell.style.flex = `0 0 ${newWidth}px`);
|
||
}
|
||
}
|
||
|
||
function stopResize() {
|
||
if (currentResizer) {
|
||
document.removeEventListener('mousemove', resize);
|
||
document.removeEventListener('mouseup', stopResize);
|
||
currentResizer = null;
|
||
}
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const dropdownData = {};
|
||
|
||
async function populateDropdowns() {
|
||
const dropdowns = document.querySelectorAll('.dropdown-select:not(.client-select):not(.api-fixed-select)');
|
||
if (dropdowns.length === 0) {
|
||
return;
|
||
}
|
||
|
||
const uniqueFieldIds = [
|
||
...new Set(Array.from(dropdowns).map(d => d.getAttribute('data-field-id')))
|
||
].filter(fieldId => fieldId);
|
||
|
||
const missingFieldIds = uniqueFieldIds.filter(fieldId => !dropdownData[fieldId]);
|
||
|
||
if (missingFieldIds.length > 0) {
|
||
try {
|
||
const response = await fetch(
|
||
`get_customfield_values.php?field_ids=${missingFieldIds.join(",")}`
|
||
);
|
||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||
const data = await response.json();
|
||
|
||
if (data.error) {} else {
|
||
for (const fieldId of Object.keys(data)) {
|
||
dropdownData[fieldId] = data[fieldId] || [];
|
||
}
|
||
}
|
||
} 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 || '';
|
||
|
||
if (!fieldId || !dropdownData[fieldId]) {
|
||
dropdown.innerHTML = '<option value="">Errore nel caricamento</option>';
|
||
return;
|
||
}
|
||
|
||
const items = dropdownData[fieldId];
|
||
// ✅ Sort alphabetically by Valore (A→Z)
|
||
items.sort((a, b) => String(a.Valore || '').localeCompare(String(b.Valore || ''), 'it', {
|
||
sensitivity: 'base'
|
||
}));
|
||
const valToSelect = currentValue || selectedValue;
|
||
|
||
if (items.length > 12) {
|
||
// Select2 con ricerca
|
||
dropdown.innerHTML = '<option value="">Seleziona...</option>';
|
||
items.forEach(value => {
|
||
const option = document.createElement('option');
|
||
option.value = value.IdCustomFieldsValue;
|
||
option.textContent = value.Valore;
|
||
if (valToSelect && valToSelect === String(value.IdCustomFieldsValue)) {
|
||
option.selected = true;
|
||
}
|
||
dropdown.appendChild(option);
|
||
});
|
||
$(dropdown).select2({
|
||
placeholder: "Seleziona...",
|
||
allowClear: true,
|
||
width: '100%'
|
||
});
|
||
// sposta la select DOPO il container Select2 così previousElementSibling del btn torna corretto
|
||
const s2container = $(dropdown).next('.select2-container');
|
||
if (s2container.length) {
|
||
s2container.after(dropdown);
|
||
}
|
||
if (valToSelect) {
|
||
$(dropdown).val(valToSelect).trigger('change');
|
||
}
|
||
} else {
|
||
// Select nativa
|
||
try {
|
||
$(dropdown).select2('destroy');
|
||
} catch (e) {}
|
||
dropdown.innerHTML = '<option value="">Seleziona...</option>';
|
||
items.forEach(value => {
|
||
const option = document.createElement('option');
|
||
option.value = value.IdCustomFieldsValue;
|
||
option.textContent = value.Valore;
|
||
if (valToSelect && valToSelect === String(value.IdCustomFieldsValue)) {
|
||
option.selected = true;
|
||
}
|
||
dropdown.appendChild(option);
|
||
});
|
||
if (valToSelect) {
|
||
dropdown.value = valToSelect;
|
||
dropdown.dispatchEvent(new Event('change', {
|
||
bubbles: true
|
||
}));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
populateDropdowns();
|
||
});
|
||
|
||
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";
|
||
|
||
// CodiceCliente es: "Bl01858_E" -> vogliamo "E"
|
||
const codiceCliente = (client.CodiceCliente || '').toString().trim();
|
||
const suffix = (codiceCliente.split('_')[1] || '').trim(); // parte dopo "_"
|
||
const shortCode = suffix || '--';
|
||
|
||
const option = new Option(`${nome.trim()} - ${shortCode} (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);
|
||
}
|
||
}
|
||
|
||
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";
|
||
|
||
// CodiceCliente es: "Bl01858_E" -> vogliamo "E"
|
||
const codiceCliente = (client.CodiceCliente || '').toString().trim();
|
||
const suffix = (codiceCliente.split('_')[1] || '').trim(); // parte dopo "_"
|
||
const shortCode = suffix || '--';
|
||
|
||
const option = new Option(`${nome.trim()} - ${shortCode} (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);
|
||
}
|
||
});
|
||
}
|
||
|
||
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);
|
||
}
|
||
});
|
||
});
|
||
|
||
// Quick add part from "Tested Component" (+ button)
|
||
$(document).on('click', '.add-part-btn', async function() {
|
||
const rowIndex = $(this).data('row');
|
||
const $row = $(this).closest('.grid-row');
|
||
const iddatadb = $row.data('id');
|
||
|
||
// Input inside the same cell
|
||
const $cell = $(this).closest('.grid-cell');
|
||
const $input = $cell.find('input[name^="rows["][name$="[tested_component]"]');
|
||
|
||
const raw = ($input.val() || '').trim();
|
||
|
||
// ✅ split multiplo con "|"
|
||
const parts = raw
|
||
.split('|')
|
||
.map(s => s.trim())
|
||
.filter(s => s.length > 0);
|
||
|
||
// (opzionale) dedup per evitare doppioni tipo "Cap | Cap"
|
||
const uniqueParts = [...new Set(parts)];
|
||
|
||
if (!uniqueParts.length) {
|
||
alert('⚠️ Inserisci prima una descrizione nel campo Tested Component.');
|
||
$input.focus();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let okCount = 0;
|
||
const errors = [];
|
||
|
||
// ✅ esegue una chiamata per ogni parte (compatibile con l’attuale add_part_quick.php)
|
||
for (const p of uniqueParts) {
|
||
const formData = new FormData();
|
||
formData.append('iddatadb', iddatadb);
|
||
formData.append('part_description', p);
|
||
|
||
const resp = await fetch('add_part_quick.php', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
let data = {};
|
||
try {
|
||
data = await resp.json();
|
||
} catch (e) {}
|
||
|
||
if (!resp.ok || !data.success) {
|
||
errors.push(`"${p}": ${(data && data.message) ? data.message : ('HTTP ' + resp.status)}`);
|
||
} else {
|
||
okCount++;
|
||
}
|
||
}
|
||
|
||
// feedback UI
|
||
$input.addClass('cell-changed');
|
||
setTimeout(() => $input.removeClass('cell-changed'), 1200);
|
||
$input.trigger('change');
|
||
|
||
if (errors.length === 0) {
|
||
showQuickPartModal(`✅ Parti aggiunte: ${okCount}`);
|
||
} else {
|
||
// aggiunge comunque se alcune sono ok
|
||
showQuickPartModal(`⚠️ Parti aggiunte: ${okCount}\nErrori:\n- ${errors.join('\n- ')}`);
|
||
console.error('Add parts errors:', errors);
|
||
}
|
||
|
||
} catch (err) {
|
||
alert('❌ Errore aggiunta parti: ' + err.message);
|
||
console.error(err);
|
||
}
|
||
|
||
});
|
||
|
||
function showQuickPartModal(message) {
|
||
const modalEl = document.getElementById('quickPartModal');
|
||
if (!modalEl) {
|
||
alert(message); // fallback se il modal non esiste
|
||
return;
|
||
}
|
||
|
||
document.getElementById('quickPartModalMessage').textContent = message;
|
||
|
||
let modal = bootstrap.Modal.getInstance(modalEl);
|
||
if (!modal) modal = new bootstrap.Modal(modalEl, {
|
||
backdrop: true,
|
||
keyboard: true
|
||
});
|
||
modal.show();
|
||
}
|
||
|
||
|
||
$(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>
|
||
<script>
|
||
// Dati globali caricati una volta sola
|
||
let fixedFieldDataCache = {}; // { 'MoltiplicatorePrezzo': [...], 'ClienteResponsabile_4202': [...], ... }
|
||
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const apiFields = {
|
||
'MoltiplicatorePrezzo': {
|
||
endpoint: 'MoltiplicatorePrezzo',
|
||
idKey: 'IdMoltiplicatorePrezzo',
|
||
textKey: 'Descrizione'
|
||
},
|
||
'AnagraficaCertestObject': {
|
||
endpoint: 'AnagraficaCertestObject',
|
||
idKey: 'IdAnagrafica',
|
||
textKey: 'NomeAnagrafica'
|
||
},
|
||
'AnagraficaCertestService': {
|
||
endpoint: 'AnagraficaCertestService',
|
||
idKey: 'IdAnagrafica',
|
||
textKey: 'NomeAnagrafica'
|
||
},
|
||
'ClienteResponsabile': {
|
||
endpoint: 'ClienteResponsabile',
|
||
idKey: 'IdClienteResponsabile',
|
||
textKey: 'Nominativo',
|
||
dependsOn: 'idclient',
|
||
getParams: (clientId) => ({
|
||
id_cliente: clientId
|
||
})
|
||
}
|
||
};
|
||
|
||
// ✅ NEW: returns clientId for both row selects and header (grid-top)
|
||
function getClientIdForSelect($select) {
|
||
const $row = $select.closest('.grid-row');
|
||
|
||
// If the select is inside a row, read the row client dropdown
|
||
if ($row.length) {
|
||
return $row.find('select[name$="[idclient]"]').val() || '';
|
||
}
|
||
|
||
// Otherwise it's in the header (grid-top): read the top client select
|
||
const topClientId = $('#clientSelect').val();
|
||
return topClientId || '';
|
||
}
|
||
|
||
|
||
// Funzione per caricare i dati di un campo (una volta sola)
|
||
async function loadFixedFieldData(fieldKey, clientId = null) {
|
||
const config = apiFields[fieldKey];
|
||
if (!config) return [];
|
||
|
||
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
|
||
if (fixedFieldDataCache[cacheKey]) {
|
||
return fixedFieldDataCache[cacheKey];
|
||
}
|
||
|
||
let urlParams = {
|
||
field: config.endpoint
|
||
};
|
||
if (config.dependsOn && clientId) {
|
||
Object.assign(urlParams, config.getParams(clientId));
|
||
}
|
||
|
||
try {
|
||
const response = await fetch("get_fixed_field_data.php?" + new URLSearchParams(urlParams));
|
||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||
const data = await response.json();
|
||
|
||
let items = [];
|
||
if (fieldKey === 'ClienteResponsabile') {
|
||
items = data.Responsabili || [];
|
||
} else {
|
||
items = data.value || data.d?.results || data || [];
|
||
}
|
||
let results = items.map(item => ({
|
||
id: item[config.idKey],
|
||
text: (item.Codice ? item.Codice + ' - ' : '') + (item[config.textKey] || 'Senza nome')
|
||
}));
|
||
|
||
// ✅ Sort alphabetically by text
|
||
results.sort((a, b) => String(a.text || '').localeCompare(String(b.text || ''), 'it', {
|
||
sensitivity: 'base'
|
||
}));
|
||
|
||
fixedFieldDataCache[cacheKey] = results;
|
||
return results;
|
||
} catch (err) {
|
||
console.error('Errore caricamento ' + fieldKey + ':', err);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// Carica tutti i campi NON dipendenti all'apertura pagina
|
||
async function preloadNonDependentFields() {
|
||
for (const [fieldKey, config] of Object.entries(apiFields)) {
|
||
if (!config.dependsOn) {
|
||
await loadFixedFieldData(fieldKey);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Inizializza tutte le select
|
||
$('.api-fixed-select').each(function() {
|
||
const $select = $(this);
|
||
const fieldKey = $select.data('fixed-key');
|
||
const currentVal = $select.data('current-value') || '';
|
||
const config = apiFields[fieldKey];
|
||
if (!config) return;
|
||
|
||
$select.select2({
|
||
placeholder: config.dependsOn ? "Seleziona cliente prima..." : "Seleziona...",
|
||
allowClear: true,
|
||
width: '100%',
|
||
data: [], // inizialmente vuota, la riempiamo dopo
|
||
minimumInputLength: 0
|
||
});
|
||
|
||
// Funzione per popolare la select con dati già caricati o caricandoli
|
||
async function populateSelect() {
|
||
let results = [];
|
||
if (config.dependsOn) {
|
||
const clientId = getClientIdForSelect($select);
|
||
if (!clientId) {
|
||
// ✅ reset select completely if no client
|
||
$select.empty().append(new Option('Seleziona cliente prima...', '', true, true)).trigger('change');
|
||
return;
|
||
}
|
||
|
||
let rawData = await fetch("get_fixed_field_data.php?" + new URLSearchParams({
|
||
field: config.endpoint,
|
||
...config.getParams(clientId)
|
||
})).then(r => r.json());
|
||
|
||
let items = [];
|
||
if (fieldKey === 'ClienteResponsabile') {
|
||
items = rawData.Responsabili || [];
|
||
} else {
|
||
items = rawData.value || [];
|
||
}
|
||
results = items.map(item => ({
|
||
id: item[config.idKey],
|
||
text: (item.Codice ? item.Codice + ' - ' : '') + (item[config.textKey] || 'Senza nome')
|
||
}));
|
||
// ✅ Sort alphabetically by text
|
||
results.sort((a, b) => String(a.text || '').localeCompare(String(b.text || ''), 'it', {
|
||
sensitivity: 'base'
|
||
}));
|
||
} else {
|
||
results = await loadFixedFieldData(fieldKey);
|
||
}
|
||
|
||
if (results.length > 12) {
|
||
// Select2 con ricerca
|
||
$select.select2('destroy').empty().select2({
|
||
data: [{
|
||
id: '',
|
||
text: 'Seleziona...'
|
||
}, ...results],
|
||
placeholder: "Seleziona...",
|
||
allowClear: true,
|
||
width: '100%'
|
||
});
|
||
} else {
|
||
// Select nativa senza Select2
|
||
try {
|
||
$select.select2('destroy');
|
||
} catch (e) {}
|
||
$select.empty();
|
||
$select.append(new Option('Seleziona...', '', true, false));
|
||
results.forEach(r => {
|
||
$select.append(new Option(r.text, r.id));
|
||
});
|
||
$select.css('width', '100%');
|
||
}
|
||
|
||
// Imposta valore iniziale
|
||
if (currentVal) {
|
||
const found = results.find(r => String(r.id) === String(currentVal));
|
||
if (found) {
|
||
$select.val(currentVal).trigger('change');
|
||
}
|
||
}
|
||
}
|
||
|
||
// ✅ NEW: allow external reload (used by header client change)
|
||
$select.on('fixed:reload', function() {
|
||
populateSelect();
|
||
});
|
||
|
||
|
||
// Per campi dipendenti: popola quando cambia cliente (riga o header)
|
||
if (config.dependsOn) {
|
||
const $row = $select.closest('.grid-row');
|
||
|
||
if ($row.length) {
|
||
// ✅ ROW: bind on row client select
|
||
$row.find('select[name$="[idclient]"]').on('change', populateSelect);
|
||
|
||
// Popola iniziale se cliente già selezionato nella riga
|
||
if ($row.find('select[name$="[idclient]"]').val()) {
|
||
populateSelect();
|
||
}
|
||
} else {
|
||
// ✅ HEADER (grid-top): bind on top client select
|
||
$('#clientSelect').on('change', populateSelect);
|
||
|
||
// Popola iniziale se cliente già selezionato nell’header
|
||
if ($('#clientSelect').val()) {
|
||
populateSelect();
|
||
}
|
||
}
|
||
} else {
|
||
// Campi non dipendenti: popola subito
|
||
populateSelect();
|
||
}
|
||
|
||
});
|
||
|
||
// ✅ NEW: when top client changes, reload only the header ClienteResponsabile select
|
||
$('#clientSelect').on('change', function() {
|
||
$('.grid-top .api-fixed-select[data-fixed-key="ClienteResponsabile"]').trigger('fixed:reload');
|
||
});
|
||
|
||
|
||
// Precarica i campi indipendenti all'avvio
|
||
preloadNonDependentFields();
|
||
|
||
// Propaga anche per i fixed select
|
||
$('.propagate-btn[data-column^="fixed_"]').on('click', function() {
|
||
const column = $(this).data('column');
|
||
const $topSelect = $(this).siblings('select.api-fixed-select');
|
||
if (!$topSelect.length) return;
|
||
const value = $topSelect.val();
|
||
const fieldKey = $topSelect.data('fixed-key');
|
||
|
||
// Propaga solo se il valore è valido
|
||
if (value) {
|
||
$(`.api-fixed-select[data-fixed-key="${fieldKey}"]`).each(function() {
|
||
$(this).val(value).trigger('change');
|
||
});
|
||
}
|
||
});
|
||
|
||
// Fix for sticky columns
|
||
const gridContainer = document.querySelector('.grid-container');
|
||
if (gridContainer) {
|
||
const rows = document.querySelectorAll('.grid-row');
|
||
rows.forEach(row => {
|
||
if (!row.style.minWidth) {
|
||
row.style.minWidth = 'max-content';
|
||
}
|
||
});
|
||
|
||
const gridTop = document.querySelector('.grid-top');
|
||
if (gridTop && !gridTop.style.minWidth) {
|
||
gridTop.style.minWidth = 'max-content';
|
||
}
|
||
|
||
const stickyTopCells = document.querySelectorAll('.grid-top .grid-cell.save-all-cell, .grid-top .grid-cell:nth-child(2)');
|
||
stickyTopCells.forEach(cell => {
|
||
if (!cell.style.minWidth && !cell.style.flex) {
|
||
|
||
const computedStyle = window.getComputedStyle(cell);
|
||
if (computedStyle.flex === '0 0 auto' || !computedStyle.flex.includes('0 0')) {
|
||
cell.style.minWidth = cell.offsetWidth + 'px';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
</script>
|
||
<script>
|
||
function autoFitColumnsByHeaderKeepWider({
|
||
minPx = 150,
|
||
maxPx = 380, // limite solo per l'auto-fit (NON riduce se già più largo)
|
||
extraPx = 40,
|
||
ignoreIndexes = []
|
||
} = {}) {
|
||
|
||
// misura testo con stesso font dell'header
|
||
const measurer = document.createElement('span');
|
||
measurer.style.position = 'absolute';
|
||
measurer.style.visibility = 'hidden';
|
||
measurer.style.whiteSpace = 'nowrap';
|
||
measurer.style.left = '-99999px';
|
||
measurer.style.top = '-99999px';
|
||
document.body.appendChild(measurer);
|
||
|
||
const headers = document.querySelectorAll('.grid-row .grid-header[data-index]');
|
||
headers.forEach(header => {
|
||
const idx = header.getAttribute('data-index');
|
||
if (!idx || ignoreIndexes.includes(String(idx))) return;
|
||
|
||
// testo header senza il resizer
|
||
const clone = header.cloneNode(true);
|
||
clone.querySelectorAll('.resizer').forEach(r => r.remove());
|
||
const label = (clone.textContent || '').replace(/\s+/g, ' ').trim();
|
||
|
||
// misura
|
||
const headerStyle = window.getComputedStyle(header);
|
||
measurer.style.font = headerStyle.font; // importante: usa stesso font completo
|
||
measurer.textContent = label;
|
||
|
||
let desired = Math.ceil(measurer.offsetWidth + extraPx);
|
||
desired = Math.max(minPx, desired);
|
||
desired = Math.min(maxPx, desired); // clamp SOLO della misura calcolata
|
||
|
||
// larghezza corrente (flex-basis), fallback a offsetWidth
|
||
const cs = window.getComputedStyle(header);
|
||
let current = parseFloat(cs.flexBasis);
|
||
if (Number.isNaN(current) || !current) current = header.offsetWidth;
|
||
|
||
// ✅ non restringere mai
|
||
const finalWidth = Math.max(current, desired);
|
||
|
||
// applica a header + celle + top
|
||
const targets = document.querySelectorAll(
|
||
`.grid-header[data-index="${idx}"], .grid-cell[data-index="${idx}"], .grid-top .grid-cell[data-index="${idx}"]`
|
||
);
|
||
|
||
targets.forEach(el => {
|
||
el.style.flex = `0 0 ${finalWidth}px`;
|
||
});
|
||
});
|
||
|
||
measurer.remove();
|
||
}
|
||
</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">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportConfirmModalLabel">Conferma Esportazione</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="exportConfirmMessage">Confermi l'esportazione al LIMS per iddatadb <span id="exportIddatadb"></span>?</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-primary btn-sm" id="exportConfirmBtn">Conferma</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modale di risposta per l'esportazione -->
|
||
<div class="modal fade" id="exportResponseModal" tabindex="-1" aria-labelledby="exportResponseModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportResponseModalLabel">Risultato Esportazione</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="exportResponseMessage"></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
|
||
</html>
|