1364 lines
62 KiB
PHP

<?php
include('include/headscript.php');
// ✅ FIX timezone (Rome)
ini_set('date.timezone', 'Europe/Rome');
date_default_timezone_set('Europe/Rome');
$template_id = intval($_GET['id'] ?? 0);
if (!$template_id) {
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
exit;
}
$user_id = $iduserlogin ?? 1;
$is_readonly = true;
$show_all_users = isset($_GET['all_users']) && $_GET['all_users'] == '1';
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
// 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;
// Fetch records with status='l' (exported to LIMS) for this template
$userFilter = $show_all_users ? '' : 'AND d.user_id = ?';
$stmt = $pdo->prepare("
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
FROM datadb d
LEFT JOIN auth_users u ON d.user_id = u.id
WHERE d.templateid = ? AND d.status = 'l' {$userFilter}
ORDER BY d.iddatadb DESC
");
$params = [$template_id];
if (!$show_all_users) $params[] = $user_id;
$stmt->execute($params);
$importedData = $stmt->fetchAll(PDO::FETCH_ASSOC);
$insertedIds = array_column($importedData, 'iddatadb');
// Fetch custom field details
$manualDetails = [];
if (!empty($insertedIds)) {
$placeholders = implode(',', array_fill(0, count($insertedIds), '?'));
$stmt = $pdo->prepare("
SELECT d.id AS detail_id, d.id AS datadb_id, d.mapping_id, d.field_value,
m.field_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 ({$placeholders})
");
$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',
'ClienteFornitore',
'ClienteAnalisi',
'AnagraficaCertestObject',
'AnagraficaCertestService',
'MoltiplicatorePrezzo',
'ConsegnaRichiesta',
// se ci sono altri campi fixed li mettiamo dopo
];
// No exclusions: fixed fields will be rendered together at the end
$excludeFromFixed = [];
$fixedFields = [];
$tempMap = [];
foreach ($fixedFieldsRaw as $f) {
if (in_array($f['fixed_field_key'], $excludeFromFixed, true)) continue;
$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;
}
// Maps logical fixed_field_key → real datadb column name (mirrors JS fixedAliasMap)
$fixedAliasMap = [
'ClienteResponsabile' => 'cliente_responsabile_id',
'ClienteFornitore' => 'cliente_fornitore_id',
'ClienteAnalisi' => 'clienteAnalisi',
'MoltiplicatorePrezzo' => 'moltiplicatore_prezzo_id',
'AnagraficaCertestObject' => 'anagrafica_certest_object_id',
'AnagraficaCertestService' => 'anagrafica_certest_service_id',
'ConsegnaRichiesta' => 'consegna_richiesta',
];
// 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;
min-width: fit-content;
}
.grid-row:last-child {
border-bottom: none;
}
.grid-row:nth-child(even) {
background-color: #f8f9fa;
}
.grid-row:hover {
background-color: #e9ecef;
}
.grid-row.batch-exporting {
background: linear-gradient(90deg, #fff0f0 0%, #ffe0e0 50%, #fff0f0 100%) !important;
background-size: 200% 100% !important;
animation: batch-pulse 1.5s ease-in-out infinite;
position: relative;
z-index: 1;
}
.grid-row.batch-exporting .button-cell {
background: transparent !important;
}
@keyframes batch-pulse {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
.batch-row-spinner {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #eb0b0b;
font-weight: 500;
}
.batch-row-spinner i {
font-size: 16px;
}
.grid-row.batch-disabled {
opacity: 0.5;
}
.grid-row.batch-disabled .action-btn {
pointer-events: none;
}
.grid-row.batch-row-error {
background: #fff3f3 !important;
border-left: 3px solid #dc3545;
}
.grid-row.batch-row-error .button-cell {
background: #fff3f3 !important;
}
.batch-error-msg {
color: #dc3545;
font-size: 10px;
line-height: 1.2;
display: block;
padding: 2px 0;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.batch-error-msg:hover {
text-decoration: underline;
}
.grid-row.validation-row-error {
background: #fff3f3 !important;
border-left: 3px solid #dc3545;
}
.grid-cell.validation-error {
background-color: #f8d7da !important;
position: relative;
}
.input-validation-error,
.input-validation-error:focus {
border: 2px solid #dc3545 !important;
background-color: #fff5f5 !important;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25) !important;
outline: none !important;
}
.validation-tooltip {
display: none;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #dc3545;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
z-index: 1050;
pointer-events: none;
}
.validation-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: #dc3545;
}
.grid-cell.validation-error:hover .validation-tooltip {
display: block;
}
.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: none !important;
}
.grid-top-original {
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;
display: flex;
align-items: center;
justify-content: center;
overflow: visible;
}
.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;
}
.actions-dropdown .dropdown-toggle {
background-color: #6c757d;
color: white;
border: none;
border-radius: 5px;
padding: 6px 14px;
cursor: pointer;
font-size: 13px;
}
.actions-dropdown .dropdown-toggle:hover,
.actions-dropdown .dropdown-toggle:focus {
background-color: #5a6268;
}
.actions-dropdown .dropdown-menu {
min-width: 160px;
font-size: 13px;
z-index: 1050;
}
.actions-dropdown .dropdown-item i {
width: 18px;
text-align: center;
margin-right: 6px;
}
#exportConfirmModal,
#exportResponseModal,
#exportUnsavedModal,
#exportBatchConfirmModal,
#exportBatchUnsavedModal,
#saveAllConfirmModal,
#saveAllResultModal {
z-index: 1300 !important;
}
#exportConfirmModal .modal-backdrop,
#exportResponseModal .modal-backdrop,
#exportUnsavedModal .modal-backdrop,
#exportBatchConfirmModal .modal-backdrop,
#exportBatchUnsavedModal .modal-backdrop,
#saveAllConfirmModal .modal-backdrop,
#saveAllResultModal .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;
}
/* Hide grid-top (propagate row) */
.grid-top { display: none !important; }
/* Text cells styling */
.grid-row .grid-cell span { font-size: 12px; padding: 2px 4px; }
.propagate-btn { display: none !important; }
.button-cell { flex: 0 0 120px !important; }
/* View modals — ensure they sit above backdrop */
#partsViewModal, #photosViewModal { z-index: 1060 !important; }
.modal-backdrop { z-index: 1050 !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 d-flex align-items-center gap-2">
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
<span class="ms-3">
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
<input type="checkbox" class="form-check-input" id="showAllUsers" <?= $show_all_users ? 'checked' : '' ?>
onchange="window.location.href='tolims.php?id=<?= $template_id ?>' + (this.checked ? '&all_users=1' : '')">
Show all users
</label>
</span>
<span class="text-muted" style="font-size: 12px;">(<?= count($importedData) ?> records<?= !$show_all_users ? ' — my records' : '' ?>)</span>
</div>
<div class="card radius-10">
<div class="card-header">
<div class="d-flex align-items-center" style="min-height: 42px; gap: 12px;">
<span style="font-weight: 600; font-size: 14px;"><i class="fas fa-check-circle" style="color: #28a745;"></i> Exported to LIMS</span>
</div>
</div>
<div class="card-body">
<form id="editForm">
<div class="grid-container">
<div class="grid-top">
<div class="grid-cell save-all-cell"></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 150px;" data-index="<?= $mainFieldMapping ? 2 : 1 ?>"></div>
<div class="grid-cell grid-top-cell" style="flex: 0 0 300px;" data-index="<?= $mainFieldMapping ? 3 : 2 ?>">
<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>
<?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',
'ClienteFornitore',
'ClienteAnalisi',
'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 120px;">View</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 150px; position: relative;">Status<div class="resizer"></div>
</div>
<div class="grid-header" data-index="<?= $mainFieldMapping ? 3 : 2 ?>" style="flex: 0 0 300px; position: relative;">Client<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 120px; position: relative;">
<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):
$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'] ?? '';
?>
<div class="grid-cell" data-col="main_field" data-row="<?= $index ?>" data-index="1" style="flex: 0 0 150px;">
<span><?= htmlspecialchars($fieldValue) ?></span>
</div>
<?php endif; ?>
<div class="grid-cell" data-col="status" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 2 : 1 ?>" style="flex: 0 0 150px;">
<span class="status-badge status-<?= htmlspecialchars($row['status'] ?? 'l') ?>">To LIMS</span>
<?php if (!empty($row['commessaweb'])): ?>
<span class="commessaweb-code" style="display:block; font-size:0.75em; color:#555; margin-top:2px;"><?= htmlspecialchars($row['commessaweb']) ?></span>
<?php endif; ?>
</div>
<div class="grid-cell" data-col="idclient" data-row="<?= $index ?>" data-index="<?= $mainFieldMapping ? 3 : 2 ?>" style="flex: 0 0 300px;">
<span class="client-text" data-client-id="<?= htmlspecialchars($row['idclient'] ?? '') ?>"><?= htmlspecialchars($row['idclient'] ?? '') ?></span>
</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'] ?? '';
echo "<div class='grid-cell' data-col='auto_$autoIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo "<span>" . htmlspecialchars($fieldValue) . "</span>";
echo "</div>";
$cellIndex++;
$autoIndex++;
}
}
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'] ?? '';
echo "<div class='grid-cell' data-col='manual_$autoIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo "<span>" . htmlspecialchars($fieldValue) . "</span>";
echo "</div>";
$cellIndex++;
}
}
// Tested Component (empty for view)
echo "<div class='grid-cell' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'><span></span></div>";
$cellIndex++;
?>
<div class="grid-cell" data-index="<?= $cellIndex ?>" style="flex: 0 0 200px;">
<span></span>
</div>
<?php $cellIndex++; ?>
<div class="grid-cell" data-row="<?= $index ?>" data-index="<?= $cellIndex ?>" style="flex: 0 0 250px;">
<span></span>
</div>
<?php $cellIndex++; ?>
<?php
// ---------------- FIXED FIELDS CELLS (text only) ----------------
if (!empty($fixedFields)) {
foreach ($fixedFields as $f) {
$key = $f['fixed_field_key'];
$dbCol = $fixedAliasMap[$key] ?? $key;
$val = $row[$dbCol] ?? '';
echo "<div class='grid-cell' data-col='" . htmlspecialchars($key) . "' data-row='$index' data-index='$cellIndex' style='flex: 0 0 180px;'>";
echo "<span>" . htmlspecialchars((string)$val) . "</span>";
echo "</div>";
$cellIndex++;
if ($key === 'ConsegnaRichiesta') {
echo "<div class='grid-cell' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'><span>" . htmlspecialchars($row['importreferencecode'] ?? '') . "</span></div>";
$cellIndex++;
echo "<div class='grid-cell' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'><a href='imported_trf/" . htmlspecialchars($row['filename_import'] ?? '') . "' target='_blank'>File</a></div>";
$cellIndex++;
echo "<div class='grid-cell' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'><span>" . htmlspecialchars($row['importdate'] ?? '') . "</span></div>";
$cellIndex++;
}
}
}
?>
</div>
<?php endforeach; ?>
</div>
</form>
<!-- Parts Modal -->
<div class="modal fade" id="partsViewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Parts</h5>
<button type="button" class="btn-close close-parts-modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="partsViewContent"><i class="fas fa-spinner fa-spin"></i> Loading...</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm close-parts-modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Photos Modal -->
<div class="modal fade" id="photosViewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Photos</h5>
<button type="button" class="btn-close close-photos-modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="photosViewContent"><i class="fas fa-spinner fa-spin"></i> Loading...</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary btn-sm close-photos-modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
// Load client names to replace IDs
$.getJSON('get_clienti.php', function(data) {
const clients = data.value || [];
const map = {};
clients.forEach(c => {
const code = (c.CodiceCliente || '').split('_')[1] || c.CodiceCliente || '';
map[c.IdCliente] = (c.Nominativo || '').trim() + (code ? ' - ' + code : '') + ' (' + c.IdCliente + ')';
});
document.querySelectorAll('.client-text').forEach(el => {
const id = el.getAttribute('data-client-id');
if (id && map[id]) el.textContent = map[id];
});
});
// Modal close handlers
$(document).on('click', '.close-parts-modal', function() {
const modal = bootstrap.Modal.getInstance(document.getElementById('partsViewModal'));
if (modal) modal.hide();
});
$(document).on('click', '.close-photos-modal', function() {
const modal = bootstrap.Modal.getInstance(document.getElementById('photosViewModal'));
if (modal) modal.hide();
});
$(document).on('hidden.bs.modal', '#partsViewModal, #photosViewModal', function() {
$('.modal-backdrop').remove();
$('body').removeClass('modal-open').css('padding-right', '');
});
// Parts viewer
$(document).on('click', '.parts-btn', function() {
const iddatadb = $(this).data('iddatadb');
$('#partsViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
new bootstrap.Modal(document.getElementById('partsViewModal')).show();
$.getJSON('load_parts.php', { iddatadb: iddatadb }, function(data) {
if (!data.parts || data.parts.length === 0) {
$('#partsViewContent').html('<p class="text-muted">No parts found.</p>');
return;
}
let html = '<table class="table table-sm table-bordered"><thead><tr><th>#</th><th>Description</th><th>Material</th><th>Color</th><th>Matrice</th><th>Note</th></tr></thead><tbody>';
data.parts.forEach(p => {
html += '<tr><td>' + (p.part_number||'') + '</td><td>' + (p.part_description||'') + '</td><td>' + (p.material||'') + '</td><td>' + (p.color||'') + '</td><td>' + (p.idmatrice||'') + '</td><td>' + (p.note||'') + '</td></tr>';
});
html += '</tbody></table>';
$('#partsViewContent').html(html);
}).fail(function() {
$('#partsViewContent').html('<p class="text-danger">Error loading parts.</p>');
});
});
// Photos viewer
$(document).on('click', '.photos-btn', function() {
const iddatadb = $(this).data('iddatadb');
$('#photosViewContent').html('<i class="fas fa-spinner fa-spin"></i> Loading...');
new bootstrap.Modal(document.getElementById('photosViewModal')).show();
$.getJSON('load_photo.php', { iddatadb: iddatadb }, function(data) {
if (!data.success || !data.photos || data.photos.length === 0) {
$('#photosViewContent').html('<p class="text-muted">No photos found.</p>');
return;
}
let html = '<div class="d-flex flex-wrap gap-2">';
data.photos.forEach(src => {
html += '<div style="text-align:center;"><a href="' + src + '" target="_blank"><img src="' + src + '" style="max-width:200px; max-height:200px; border:1px solid #ddd; border-radius:4px; padding:2px;"></a></div>';
});
html += '</div>';
$('#photosViewContent').html(html);
}).fail(function() {
$('#photosViewContent').html('<p class="text-danger">Error loading photos.</p>');
});
});
});
</script>
</body>
</html>