1921 lines
91 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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';
$allowedLimits = [20, 40, 60, 100];
$rawLimit = (int)($_GET['limit'] ?? 20);
$perPage = in_array($rawLimit, $allowedLimits) ? $rawLimit : 20;
$page = max(1, (int)($_GET['page'] ?? 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;
// Maps logical fixed_field_key → real datadb column name
$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',
];
// Fetch records with status='l' (exported to LIMS) for this template
$userFilter = $show_all_users ? '' : 'AND d.user_id = ?';
$filters = $_GET['f'] ?? [];
if (!is_array($filters)) $filters = [];
// Map of filter keys → datadb columns (direct columns)
$directColumnMap = [
'commessaweb' => 'd.commessaweb',
'idclient' => 'd.idclient',
'importreferencecode' => 'd.importreferencecode',
'importdate' => 'd.importdate',
'filename_import' => 'd.filename_import',
'user_name' => "CONCAT(u.first_name, ' ', u.last_name)",
];
// Add fixed field columns
foreach ($fixedAliasMap as $fixedKey => $dbCol) {
$directColumnMap[$fixedKey] = 'd.' . $dbCol;
}
$filterSQL = '';
$filterParams = [];
foreach ($filters as $key => $val) {
$val = trim($val);
if ($val === '') continue;
if (isset($directColumnMap[$key])) {
// Direct datadb column
$filterSQL .= " AND {$directColumnMap[$key]} LIKE ?";
$filterParams[] = '%' . $val . '%';
} elseif (str_starts_with($key, 'detail_')) {
// import_data_details field: detail_{mapping_id}
$mappingId = (int)substr($key, 7);
if ($mappingId > 0) {
$filterSQL .= " AND EXISTS (SELECT 1 FROM import_data_details dd WHERE dd.id = d.iddatadb AND dd.mapping_id = ? AND dd.field_value LIKE ?)";
$filterParams[] = $mappingId;
$filterParams[] = '%' . $val . '%';
}
}
}
$baseWhere = "WHERE d.templateid = ? AND d.status = 'l' {$userFilter} {$filterSQL}";
$baseParams = [$template_id];
if (!$show_all_users) $baseParams[] = $user_id;
$baseParams = array_merge($baseParams, $filterParams);
// Check if any filter is active
$hasActiveFilters = !empty(array_filter($filters, fn($v) => trim($v) !== ''));
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d LEFT JOIN auth_users u ON d.user_id = u.id {$baseWhere}");
$countStmt->execute($baseParams);
$totalRows = (int)$countStmt->fetchColumn();
$totalPages = max(1, (int)ceil($totalRows / $perPage));
if ($page > $totalPages) $page = $totalPages;
$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
{$baseWhere}
ORDER BY d.iddatadb DESC
LIMIT {$perPage} OFFSET " . (($page - 1) * $perPage) . "
");
$stmt->execute($baseParams);
$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;
}
// 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;
}
// ── Build lookup maps: id → label for fixed fields ──
$fixedLookup = []; // e.g. $fixedLookup['MoltiplicatorePrezzo'][2] = 'Urgente (1.5x)'
function loadCacheJson(string $path): ?array {
if (file_exists($path) && (time() - filemtime($path) < 3600)) {
return json_decode(file_get_contents($path), true);
}
return null;
}
// MoltiplicatorePrezzo
$cached = loadCacheJson(__DIR__ . '/cache/moltiplicatori_prezzo.json');
if ($cached) {
$items = $cached['value'] ?? $cached;
foreach ($items as $item) {
$id = $item['IdMoltiplicatorePrezzo'] ?? null;
if ($id !== null) $fixedLookup['MoltiplicatorePrezzo'][$id] = $item['Descrizione'] ?? $item['Codice'] ?? $id;
}
}
// AnagraficaCertestObject
$cached = loadCacheJson(__DIR__ . '/cache/anagrafica_certest_object.json');
if ($cached) {
$items = $cached['value'] ?? $cached;
foreach ($items as $item) {
$id = $item['IdAnagrafica'] ?? null;
if ($id !== null) $fixedLookup['AnagraficaCertestObject'][$id] = $item['NomeAnagrafica'] ?? $item['Codice'] ?? $id;
}
}
// AnagraficaCertestService
$cached = loadCacheJson(__DIR__ . '/cache/anagrafica_certest_service.json');
if ($cached) {
$items = $cached['value'] ?? $cached;
foreach ($items as $item) {
$id = $item['IdAnagrafica'] ?? null;
if ($id !== null) $fixedLookup['AnagraficaCertestService'][$id] = $item['NomeAnagrafica'] ?? $item['Codice'] ?? $id;
}
}
// ClienteResponsabile — per-client cache files
$clienteIds = array_unique(array_filter(array_column($importedData, 'idclient')));
foreach ($clienteIds as $cid) {
$cached = loadCacheJson(__DIR__ . '/cache/cliente_responsabili_' . (int)$cid . '.json');
if ($cached) {
$items = $cached['Responsabili'] ?? [];
foreach ($items as $item) {
$id = $item['IdClienteResponsabile'] ?? null;
if ($id !== null) $fixedLookup['ClienteResponsabile'][$id] = $item['Nominativo'] ?? $id;
}
}
}
// Helper: resolve fixed field id → label
function resolveFixedValue(string $key, $val, array $fixedLookup): string {
if ($val === '' || $val === null) return '';
if (isset($fixedLookup[$key][(int)$val])) return $fixedLookup[$key][(int)$val];
return (string)$val;
}
?>
<!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; }
/* ── Grid filter row ── */
.grid-filter-row {
background: #f0f4f8 !important;
border-bottom: 2px solid #dee2e6 !important;
position: sticky;
top: 0;
z-index: 5;
}
.grid-filter-row:hover {
background: #f0f4f8 !important;
}
.grid-filter-row .grid-cell {
padding: 4px 6px !important;
}
.grid-filter-input {
width: 100% !important;
padding: 3px 6px !important;
font-size: 11px !important;
border: 1px solid #ced4da !important;
border-radius: 3px !important;
background: #fff !important;
color: #333 !important;
box-sizing: border-box;
}
.grid-filter-input:focus {
border-color: #80bdff !important;
box-shadow: 0 0 0 2px rgba(0,123,255,.15) !important;
outline: none !important;
}
.grid-filter-input::placeholder {
color: #adb5bd;
font-style: italic;
}
.grid-filter-row .grid-filter-input.has-value {
border-color: #0d6efd !important;
background: #f0f7ff !important;
}
select.grid-filter-input {
cursor: pointer;
padding-right: 18px !important;
appearance: auto;
}
.filter-wrap {
display: flex;
align-items: center;
gap: 2px;
width: 100%;
}
.filter-wrap .grid-filter-input {
flex: 1;
min-width: 0;
}
.filter-clear-btn {
flex-shrink: 0;
border: none;
background: #dc3545;
color: #fff;
font-size: 10px;
cursor: pointer;
padding: 2px 4px;
line-height: 1;
border-radius: 3px;
}
.filter-clear-btn:hover {
background: #c82333;
}
.grid-filter-row .grid-cell {
background: #f0f4f8 !important;
}
/* First sticky cell fills full row height */
.grid-filter-row .grid-cell.button-cell {
align-self: stretch;
}
/* ── Pagination bar ── */
.pager-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 6px 14px;
font-size: 13px;
color: #495057;
}
.pager-rows-per-page {
display: flex;
align-items: center;
gap: 8px;
}
.pager-label {
font-weight: 500;
white-space: nowrap;
}
.pager-limit-group {
display: inline-flex;
border: 1px solid #ced4da;
border-radius: 4px;
overflow: hidden;
}
.pager-limit-btn {
padding: 3px 10px;
text-decoration: none;
color: #495057;
border-right: 1px solid #ced4da;
transition: background .15s, color .15s;
font-weight: 500;
}
.pager-limit-btn:last-child { border-right: none; }
.pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
.pager-limit-btn.active {
background: #0d6efd;
color: #fff;
}
.pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
.pager-nav {
display: flex;
align-items: center;
gap: 4px;
}
.pager-info {
margin-right: 8px;
font-weight: 500;
white-space: nowrap;
}
.pager-btn, .pager-num {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 28px;
height: 28px;
padding: 0 6px;
border: 1px solid #ced4da;
border-radius: 4px;
text-decoration: none;
color: #495057;
font-weight: 500;
transition: background .15s, color .15s, border-color .15s;
}
.pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
.pager-num.active {
background: #0d6efd;
color: #fff;
border-color: #0d6efd;
}
.pager-btn.disabled {
pointer-events: none;
opacity: .4;
}
.pager-dots {
padding: 0 2px;
color: #adb5bd;
user-select: none;
}
</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;">
<?php
$toggleParams = $_GET;
unset($toggleParams['all_users']);
$toggleBase = 'tolims.php?' . http_build_query($toggleParams);
?>
<input type="checkbox" class="form-check-input" id="showAllUsers" <?= $show_all_users ? 'checked' : '' ?>
onchange="window.location.href='<?= htmlspecialchars($toggleBase) ?>' + (this.checked ? '&all_users=1' : '')">
Show all users
</label>
</span>
<span class="text-muted" style="font-size: 12px;">
(<?= count($importedData) ?> of <?= $totalRows ?> records<?= !$show_all_users ? ' — my records' : '' ?>)
</span>
</div>
<?php
$baseQuery = $_GET;
unset($baseQuery['limit'], $baseQuery['page']);
$pageQuery = $_GET;
unset($pageQuery['page']);
$pageBase = 'tolims.php?' . http_build_query($pageQuery);
$fromRow = ($page - 1) * $perPage + 1;
$toRow = min($page * $perPage, $totalRows);
?>
<div class="pager-bar mb-2">
<div class="pager-rows-per-page">
<span class="pager-label">Rows per page</span>
<div class="pager-limit-group">
<?php foreach ($allowedLimits as $lim):
$isActive = ($perPage === $lim);
$url = 'tolims.php?' . http_build_query(array_merge($baseQuery, ['limit' => $lim]));
?>
<a href="<?= htmlspecialchars($url) ?>" class="pager-limit-btn <?= $isActive ? 'active' : '' ?>"><?= $lim ?></a>
<?php endforeach; ?>
</div>
</div>
<?php if ($totalPages > 1): ?>
<div class="pager-nav">
<span class="pager-info"><?= $fromRow ?><?= $toRow ?> of <?= $totalRows ?></span>
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="First"><i class="fas fa-angle-double-left"></i></a>
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page - 1)) ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="Previous"><i class="fas fa-angle-left"></i></a>
<?php
$startPage = max(1, $page - 2);
$endPage = min($totalPages, $page + 2);
if ($startPage > 1): ?>
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-num">1</a>
<?php if ($startPage > 2): ?><span class="pager-dots">...</span><?php endif; ?>
<?php endif;
for ($p = $startPage; $p <= $endPage; $p++): ?>
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
<?php endfor;
if ($endPage < $totalPages): ?>
<?php if ($endPage < $totalPages - 1): ?><span class="pager-dots">...</span><?php endif; ?>
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-num"><?= $totalPages ?></a>
<?php endif; ?>
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page + 1)) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Next"><i class="fas fa-angle-right"></i></a>
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Last"><i class="fas fa-angle-double-right"></i></a>
</div>
<?php endif; ?>
</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>
<?php if ($hasActiveFilters): ?>
<a href="tolims.php?id=<?= $template_id ?><?= $show_all_users ? '&all_users=1' : '' ?><?= $perPage !== 20 ? '&limit=' . $perPage : '' ?>" class="btn btn-outline-danger btn-sm" style="font-size: 12px;"><i class="fas fa-times"></i> Clear filters</a>
<?php endif; ?>
</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>
<!-- Filter row (server-side) -->
<?php
// Helper: wrap filter element with clear button
function wrapFilter(string $inner, bool $hasValue): string {
$btn = $hasValue
? "<button type='button' class='filter-clear-btn' onclick='clearOneFilter(this)' title='Clear'>&times;</button>"
: '';
return "<div class='filter-wrap'>{$inner}{$btn}</div>";
}
// Helper: render a select filter from a lookup array
function renderFilterSelect(string $name, array $options, string $currentVal, string $style = ''): string {
$html = "<select class='grid-filter-input' name='" . htmlspecialchars($name) . "' style='" . htmlspecialchars($style) . "' onchange='submitFilters()'>";
$html .= "<option value=''>All</option>";
foreach ($options as $id => $label) {
$sel = ((string)$id === $currentVal) ? ' selected' : '';
$html .= "<option value='" . htmlspecialchars((string)$id) . "'{$sel}>" . htmlspecialchars($label) . "</option>";
}
$html .= "</select>";
return wrapFilter($html, $currentVal !== '');
}
// Helper: render a text filter
function renderFilterInput(string $name, string $currentVal): string {
$inner = "<input type='text' class='grid-filter-input' name='" . htmlspecialchars($name) . "' value='" . htmlspecialchars($currentVal) . "' placeholder='&#8981;'>";
return wrapFilter($inner, $currentVal !== '');
}
// Helper: render a select filter populated by JS
function renderFilterSelectJS(string $name, string $currentVal, string $jsClass, string $dataAttr = ''): string {
$html = "<select class='grid-filter-input {$jsClass}' name='" . htmlspecialchars($name) . "' {$dataAttr} onchange='submitFilters()'>";
$html .= "<option value=''>All</option>";
if ($currentVal !== '') {
$html .= "<option value='" . htmlspecialchars($currentVal) . "' selected>" . htmlspecialchars($currentVal) . "</option>";
}
$html .= "</select>";
return wrapFilter($html, $currentVal !== '');
}
// Fields that are client-id lookups
$clientFilterKeys = ['ClienteFornitore', 'ClienteAnalisi'];
?>
<div class="grid-row grid-filter-row" id="gridFilterRow">
<div class="grid-cell button-cell"></div>
<?php if ($mainFieldMapping):
$fKey = 'detail_' . $mainFieldMapping['id'];
$fVal = $filters[$fKey] ?? '';
?>
<div class="grid-cell" style="flex: 0 0 150px; padding: 4px 6px;">
<?php if ($mainFieldMapping['data_type'] === 'SceltaMultipla'): ?>
<?= renderFilterSelectJS("f[$fKey]", $fVal, 'scelta-filter', "data-field-id='{$mainFieldMapping['field_id']}'") ?>
<?php else: ?>
<?= renderFilterInput("f[$fKey]", $fVal) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Status/CommessaWeb -->
<div class="grid-cell" style="flex: 0 0 150px; padding: 4px 6px;">
<?= renderFilterInput('f[commessaweb]', $filters['commessaweb'] ?? '') ?>
</div>
<!-- Client -->
<div class="grid-cell" style="flex: 0 0 300px; padding: 4px 6px;">
<?= renderFilterSelectJS('f[idclient]', $filters['idclient'] ?? '', 'client-filter') ?>
</div>
<?php
// Auto fields
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$fKey = 'detail_' . $mapping['id'];
$fVal = $filters[$fKey] ?? '';
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'>";
if ($mapping['data_type'] === 'SceltaMultipla') {
echo renderFilterSelectJS("f[$fKey]", $fVal, 'scelta-filter', "data-field-id='{$mapping['field_id']}'");
} else {
echo renderFilterInput("f[$fKey]", $fVal);
}
echo "</div>";
}
}
// Manual fields
foreach ($allMappings as $mapping) {
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$fKey = 'detail_' . $mapping['id'];
$fVal = $filters[$fKey] ?? '';
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'>";
if ($mapping['data_type'] === 'SceltaMultipla') {
echo renderFilterSelectJS("f[$fKey]", $fVal, 'scelta-filter', "data-field-id='{$mapping['field_id']}'");
} else {
echo renderFilterInput("f[$fKey]", $fVal);
}
echo "</div>";
}
}
// Tested Component, AWB, Tracking
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 200px; padding: 4px 6px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 250px; padding: 4px 6px;'></div>";
// Fixed fields
if (!empty($fixedFields)) {
$insertedFilterAfterConsegna = false;
foreach ($fixedFields as $f) {
$key = $f['fixed_field_key'];
$fVal = $filters[$key] ?? '';
echo "<div class='grid-cell' style='flex: 0 0 180px; padding: 4px 6px;'>";
if (in_array($key, $clientFilterKeys, true)) {
// Client-based selects (populated by JS)
echo renderFilterSelectJS("f[$key]", $fVal, 'client-filter');
} elseif (isset($fixedLookup[$key]) && !empty($fixedLookup[$key])) {
// PHP-cached lookups
echo renderFilterSelect("f[$key]", $fixedLookup[$key], $fVal);
} elseif ($key === 'ConsegnaRichiesta') {
echo renderFilterInput("f[$key]", $fVal);
} else {
echo renderFilterInput("f[$key]", $fVal);
}
echo "</div>";
if ($key === 'ConsegnaRichiesta' && !$insertedFilterAfterConsegna) {
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'>" . renderFilterInput('f[importreferencecode]', $filters['importreferencecode'] ?? '') . "</div>";
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'>" . renderFilterInput('f[filename_import]', $filters['filename_import'] ?? '') . "</div>";
echo "<div class='grid-cell' style='flex: 0 0 150px; padding: 4px 6px;'>" . renderFilterInput('f[importdate]', $filters['importdate'] ?? '') . "</div>";
$insertedFilterAfterConsegna = 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'] ?? '';
$isScelta = ($mainFieldMapping['data_type'] === 'SceltaMultipla');
?>
<div class="grid-cell" data-col="main_field" data-row="<?= $index ?>" data-index="1" style="flex: 0 0 150px;">
<span <?= $isScelta ? "class='scelta-value' data-field-id='{$mainFieldMapping['field_id']}'" : '' ?>><?= 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'] ?? '';
$isScelta = ($mapping['data_type'] === 'SceltaMultipla');
echo "<div class='grid-cell' data-col='auto_$autoIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo $isScelta
? "<span class='scelta-value' data-field-id='{$mapping['field_id']}'>" . htmlspecialchars($fieldValue) . "</span>"
: "<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'] ?? '';
$isScelta = ($mapping['data_type'] === 'SceltaMultipla');
echo "<div class='grid-cell' data-col='manual_$autoIndex' data-row='$index' data-index='$cellIndex' style='flex: 0 0 150px;'>";
echo $isScelta
? "<span class='scelta-value' data-field-id='{$mapping['field_id']}'>" . htmlspecialchars($fieldValue) . "</span>"
: "<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] ?? '';
$isClientField = in_array($key, ['ClienteFornitore', 'ClienteAnalisi'], true);
$displayVal = $isClientField ? (string)$val : resolveFixedValue($key, $val, $fixedLookup);
echo "<div class='grid-cell' data-col='" . htmlspecialchars($key) . "' data-row='$index' data-index='$cellIndex' style='flex: 0 0 180px;'>";
if ($isClientField && $val !== '' && $val !== null) {
echo "<span class='client-text' data-client-id='" . htmlspecialchars((string)$val) . "'>" . htmlspecialchars((string)$val) . "</span>";
} else {
echo "<span>" . htmlspecialchars($displayVal) . "</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() {
// Resolve SceltaMultipla IDs → Valore (cells + filter selects)
(function() {
const sceltaEls = document.querySelectorAll('.scelta-value');
const sceltaFilters = document.querySelectorAll('select.scelta-filter');
const fieldIds = new Set();
sceltaEls.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
sceltaFilters.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
if (!fieldIds.size) return;
$.getJSON('get_customfield_values.php', { field_ids: [...fieldIds].join(',') }, function(data) {
const lookup = {};
for (const [fid, values] of Object.entries(data)) {
lookup[fid] = {};
(values || []).forEach(v => {
lookup[fid][v.IdCustomFieldsValue] = v.Valore;
});
}
// Resolve cell values
sceltaEls.forEach(el => {
const fid = el.dataset.fieldId;
const raw = (el.textContent || '').trim();
if (fid && lookup[fid] && lookup[fid][raw]) {
el.textContent = lookup[fid][raw];
}
});
// Populate filter selects
sceltaFilters.forEach(sel => {
const fid = sel.dataset.fieldId;
if (!fid || !lookup[fid]) return;
const currentVal = sel.value;
// Sort by Valore
const sorted = Object.entries(lookup[fid]).sort((a, b) => a[1].localeCompare(b[1]));
sel.innerHTML = '<option value="">All</option>';
sorted.forEach(([id, label]) => {
const opt = document.createElement('option');
opt.value = id;
opt.textContent = label;
if (id === currentVal) opt.selected = true;
sel.appendChild(opt);
});
});
});
})();
// Load client names → resolve cells + populate filter selects
$.getJSON('get_clienti.php', function(data) {
const clients = data.value || [];
const map = {};
const sorted = [];
clients.forEach(c => {
const name = (c.Nominativo || '').trim();
map[c.IdCliente] = name;
sorted.push({ id: c.IdCliente, name: name });
});
sorted.sort((a, b) => a.name.localeCompare(b.name));
// Resolve cell values
document.querySelectorAll('.client-text').forEach(el => {
const id = el.getAttribute('data-client-id');
if (id && map[id]) el.textContent = map[id];
});
// Populate client filter selects
document.querySelectorAll('select.client-filter').forEach(sel => {
const currentVal = sel.value;
sel.innerHTML = '<option value="">All</option>';
sorted.forEach(c => {
const opt = document.createElement('option');
opt.value = c.id;
opt.textContent = c.name;
if (String(c.id) === currentVal) opt.selected = true;
sel.appendChild(opt);
});
});
});
// 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>');
});
});
});
// ── Column filters ──
function clearOneFilter(btn) {
const wrap = btn.closest('.filter-wrap');
const el = wrap.querySelector('.grid-filter-input');
if (el) { el.value = ''; submitFilters(); }
}
function submitFilters() {
const filterRow = document.getElementById('gridFilterRow');
if (!filterRow) return;
const params = new URLSearchParams(window.location.search);
for (const key of [...params.keys()]) {
if (key.startsWith('f[') || key === 'page') params.delete(key);
}
filterRow.querySelectorAll('.grid-filter-input').forEach(el => {
const val = el.value.trim();
if (val) params.set(el.name, val);
});
window.location.href = 'tolims.php?' + params.toString();
}
(function() {
const filterRow = document.getElementById('gridFilterRow');
if (!filterRow) return;
filterRow.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); submitFilters(); }
});
filterRow.querySelectorAll('.grid-filter-input').forEach(el => {
el.classList.toggle('has-value', el.value.trim() !== '');
el.addEventListener('input', () => el.classList.toggle('has-value', el.value.trim() !== ''));
el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== ''));
});
})();
</script>
</body>
</html>