1518 lines
53 KiB
PHP
1518 lines
53 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;
|
||
$show_all_users = isset($_GET['all_users']) && $_GET['all_users'] == '1';
|
||
$importref = trim($_GET['importref'] ?? '');
|
||
$usePagination = ($importref === '');
|
||
$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;
|
||
|
||
// Fetch records with status='i' for this template
|
||
$userFilter = $show_all_users ? '' : 'AND d.user_id = ?';
|
||
$importrefFilter = $importref !== '' ? 'AND d.importreferencecode = ?' : '';
|
||
$baseWhere = "WHERE d.templateid = ? AND d.status = 'i' {$userFilter} {$importrefFilter}";
|
||
$baseParams = [$template_id];
|
||
if (!$show_all_users) $baseParams[] = $user_id;
|
||
if ($importref !== '') $baseParams[] = $importref;
|
||
|
||
// Count total records
|
||
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM datadb d {$baseWhere}");
|
||
$countStmt->execute($baseParams);
|
||
$totalRows = (int)$countStmt->fetchColumn();
|
||
$totalPages = $usePagination ? max(1, (int)ceil($totalRows / $perPage)) : 1;
|
||
if ($page > $totalPages) $page = $totalPages;
|
||
|
||
$limitClause = $usePagination ? 'LIMIT ' . $perPage . ' OFFSET ' . (($page - 1) * $perPage) : '';
|
||
$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
|
||
{$limitClause}
|
||
");
|
||
$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
|
||
];
|
||
|
||
// ClienteFornitore rendered as standalone column, exclude from fixed fields
|
||
$excludeFromFixed = ['ClienteFornitore'];
|
||
|
||
$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;
|
||
}
|
||
|
||
// ────────────────────────────────────────────────────────────
|
||
// Build gridData (JSON) — all row data for client-side rendering
|
||
// ────────────────────────────────────────────────────────────
|
||
|
||
$gridDataArray = [];
|
||
foreach ($importedData as $index => $row) {
|
||
$rowObj = [
|
||
'iddatadb' => (int)$row['iddatadb'],
|
||
'status' => $row['status'] ?? 'i',
|
||
'idclient' => $row['idclient'] ?? $default_idclient,
|
||
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
|
||
'tested_component' => $row['tested_component'] ?? '',
|
||
'commessaweb' => $row['commessaweb'] ?? null,
|
||
'user_name' => $row['user_name'] ?? '',
|
||
'importreferencecode' => $row['importreferencecode'] ?? '',
|
||
'filename_import' => $row['filename_import'] ?? '',
|
||
'importdate' => $row['importdate'] ?? '',
|
||
];
|
||
// Fixed fields
|
||
$rowObj['fixedFields'] = [];
|
||
foreach ($fixedFields as $f) {
|
||
$key = $f['fixed_field_key'];
|
||
$dbCol = $fixedAliasMap[$key] ?? $key;
|
||
$val = $row[$dbCol] ?? '';
|
||
if ($val === '' || $val === null) {
|
||
$val = fixedDefaultValue($f);
|
||
}
|
||
$rowObj['fixedFields'][$key] = (string)$val;
|
||
}
|
||
|
||
// Detail fields (from import_data_details)
|
||
$rowObj['details'] = [];
|
||
$rowDetails = array_filter($manualDetails, fn($d) => $d['datadb_id'] == $row['iddatadb']);
|
||
foreach ($rowDetails as $d) {
|
||
$rowObj['details'][(string)$d['mapping_id']] = $d['field_value'] ?? '';
|
||
}
|
||
|
||
// Main field value
|
||
if ($mainFieldMapping) {
|
||
$mainDetail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id']);
|
||
$mainDetail = reset($mainDetail) ?: ['field_value' => $mainFieldMapping['manual_default'] ?? ''];
|
||
$rowObj['mainFieldValue'] = $mainDetail['field_value'] ?? $mainFieldMapping['manual_default'] ?? '';
|
||
}
|
||
|
||
$rowObj['_dirty'] = false;
|
||
$gridDataArray[] = $rowObj;
|
||
}
|
||
|
||
// Build columns in display order
|
||
$gridColumns = [];
|
||
|
||
// 1. Main field
|
||
if ($mainFieldMapping) {
|
||
$gridColumns[] = [
|
||
'type' => 'main_field',
|
||
'key' => (string)$mainFieldMapping['id'],
|
||
'label' => $mainFieldMapping['field_label'],
|
||
'dataType' => $mainFieldMapping['data_type'],
|
||
'isManual' => (bool)$mainFieldMapping['is_manual'],
|
||
'isRequired' => (bool)$mainFieldMapping['is_required'],
|
||
'fieldId' => $mainFieldMapping['field_id'] ?? null,
|
||
'width' => 150,
|
||
];
|
||
}
|
||
|
||
// 2. Status
|
||
$gridColumns[] = ['type' => 'status', 'key' => 'status', 'label' => 'Status', 'width' => 150, 'editable' => false];
|
||
|
||
// 3. Client
|
||
$gridColumns[] = ['type' => 'idclient', 'key' => 'idclient', 'label' => 'Client', 'width' => 300];
|
||
|
||
// 4. Cliente Fornitore
|
||
$gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_id', 'label' => $slugMapping['ClienteFornitore'] ?? 'ClienteFornitore', 'width' => 300];
|
||
|
||
// 5. Auto fields
|
||
foreach ($allMappings as $mapping) {
|
||
if (
|
||
!$mapping['is_manual']
|
||
&& $mapping['main_field'] != 1
|
||
&& $mapping['is_visible_import'] == 1
|
||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||
) {
|
||
$gridColumns[] = [
|
||
'type' => 'detail',
|
||
'key' => (string)$mapping['id'],
|
||
'label' => $mapping['field_label'],
|
||
'dataType' => $mapping['data_type'],
|
||
'isManual' => false,
|
||
'isRequired' => (bool)$mapping['is_required'],
|
||
'fieldId' => $mapping['field_id'] ?? null,
|
||
'autoValue' => $mapping['auto_value'] ?? 'none',
|
||
'width' => 150,
|
||
];
|
||
}
|
||
}
|
||
|
||
// 6. Manual fields
|
||
foreach ($allMappings as $mapping) {
|
||
if (
|
||
$mapping['is_manual']
|
||
&& $mapping['main_field'] != 1
|
||
&& $mapping['is_visible_import'] == 1
|
||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||
) {
|
||
$gridColumns[] = [
|
||
'type' => 'detail',
|
||
'key' => (string)$mapping['id'],
|
||
'label' => $mapping['field_label'],
|
||
'dataType' => $mapping['data_type'],
|
||
'isManual' => true,
|
||
'isRequired' => (bool)$mapping['is_required'],
|
||
'fieldId' => $mapping['field_id'] ?? null,
|
||
'manualDefault' => $mapping['manual_default'] ?? '',
|
||
'width' => 150,
|
||
];
|
||
}
|
||
}
|
||
|
||
// 7. Tested Component
|
||
$gridColumns[] = ['type' => 'tested_component', 'key' => 'tested_component', 'label' => 'Tested Component', 'width' => 150];
|
||
|
||
// 8. AWB
|
||
$gridColumns[] = ['type' => 'awb', 'key' => 'awb', 'label' => 'AWB Number', 'width' => 200];
|
||
|
||
// 9. Tracking
|
||
$gridColumns[] = ['type' => 'tracking', 'key' => 'tracking', 'label' => 'Tracking Info', 'width' => 250];
|
||
|
||
// 10. Fixed fields (with importreferencecode/filename/importdate after ConsegnaRichiesta)
|
||
foreach ($fixedFields as $f) {
|
||
$key = $f['fixed_field_key'];
|
||
$label = $slugMapping[$key] ?? $key;
|
||
$gridColumns[] = [
|
||
'type' => 'fixed',
|
||
'key' => $key,
|
||
'label' => $label,
|
||
'dataType' => $f['data_type'],
|
||
'isRequired' => (bool)$f['is_required'],
|
||
'width' => 180,
|
||
];
|
||
if ($key === 'ConsegnaRichiesta') {
|
||
$gridColumns[] = ['type' => 'static', 'key' => 'importreferencecode', 'label' => $slugMapping['importreferencecode'] ?? 'Import Reference Code', 'width' => 150, 'editable' => false];
|
||
$gridColumns[] = ['type' => 'static', 'key' => 'filename_import', 'label' => $slugMapping['filename_import'] ?? 'File', 'width' => 150, 'editable' => false];
|
||
$gridColumns[] = ['type' => 'static', 'key' => 'importdate', 'label' => $slugMapping['importdate'] ?? 'Import Date', 'width' => 150, 'editable' => false];
|
||
}
|
||
}
|
||
|
||
$gridMeta = [
|
||
'templateId' => $template_id,
|
||
'defaultIdclient' => $default_idclient,
|
||
'isAdmin' => Auth::user()->hasRole('Admin'),
|
||
'userId' => $user_id,
|
||
'fixedAliasMap' => $fixedAliasMap,
|
||
'slugMapping' => $slugMapping,
|
||
'timeLabels' => $timeLabels,
|
||
'columns' => $gridColumns,
|
||
'mainFieldMapping' => $mainFieldMapping,
|
||
'totalRows' => count($gridDataArray),
|
||
];
|
||
|
||
?>
|
||
<script>
|
||
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||
</script>
|
||
|
||
<!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'); ?>
|
||
<!-- FontAwesome - ensure loaded from kit -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css">
|
||
<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;
|
||
}
|
||
|
||
.grid-container input,
|
||
.grid-container select,
|
||
.grid-container 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;
|
||
}
|
||
|
||
.top-scrollbar {
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
width: 100%;
|
||
height: 18px;
|
||
margin-bottom: 8px;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.25rem;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.top-scrollbar-inner {
|
||
height: 1px;
|
||
}
|
||
|
||
.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: 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 220px;
|
||
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;
|
||
}
|
||
|
||
/* Custom photo modal (not Bootstrap) */
|
||
#photosModal {
|
||
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);
|
||
}
|
||
|
||
#photosModal>.modal-content {
|
||
background-color: #fff;
|
||
margin: 5% auto;
|
||
padding: 20px;
|
||
width: 80%;
|
||
max-width: 900px;
|
||
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 {
|
||
flex: 0 0 220px !important;
|
||
min-width: 220px !important;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 6px 10px;
|
||
margin-right: 5px;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.action-btn i {
|
||
font-size: 14px;
|
||
margin: 0;
|
||
}
|
||
|
||
.flash-success {
|
||
background-color: #d4edda !important;
|
||
transition: background-color 0.3s ease;
|
||
}
|
||
|
||
@keyframes new-row-pulse {
|
||
|
||
0%,
|
||
100% {
|
||
background-color: transparent;
|
||
}
|
||
|
||
50% {
|
||
background-color: #cce5ff;
|
||
}
|
||
}
|
||
|
||
.row-just-created {
|
||
animation: new-row-pulse 1s ease-in-out 3;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* ── 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>
|
||
<?php if ($importref === ''): ?>
|
||
<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='imported.php?id=<?= $template_id ?>' + (this.checked ? '&all_users=1' : '')">
|
||
Show all users
|
||
</label>
|
||
</span>
|
||
<span class="text-muted" style="font-size: 12px;">
|
||
(<?= $usePagination ? count($importedData) . " of {$totalRows}" : count($importedData) ?> records<?= !$show_all_users ? ' — my records only' : '' ?>)
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php if ($usePagination): ?>
|
||
<?php
|
||
$baseQuery = $_GET;
|
||
unset($baseQuery['limit'], $baseQuery['page']);
|
||
$pageQuery = $_GET;
|
||
unset($pageQuery['page']);
|
||
$pageBase = 'imported.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 = 'imported.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>
|
||
<?php endif; ?>
|
||
<div class="card radius-10">
|
||
<div class="card-header">
|
||
<div class="d-flex align-items-center" style="min-height: 42px; gap: 12px;">
|
||
<div class="dropdown actions-dropdown" style="flex-shrink: 0;">
|
||
<button class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||
<i class="fas fa-cogs"></i> Actions
|
||
</button>
|
||
<ul class="dropdown-menu dropdown-menu-end">
|
||
|
||
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
||
|
||
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
|
||
</ul>
|
||
</div>
|
||
<button type="button" class="btn btn-outline-success btn-sm" id="addRowBtn" style="flex-shrink:0;"><i class="fas fa-plus"></i> Add Row</button>
|
||
<div id="unsavedChanges" style="display:none; color: red; font-weight: bold; font-size: 13px;">
|
||
⚠️ Unsaved changes detected!
|
||
<span id="changedRows" style="font-weight:normal; color:darkred;"></span>
|
||
</div>
|
||
<div id="batchExportBar" style="display:none; flex: 1; min-width: 0;">
|
||
<div class="d-flex align-items-center gap-2">
|
||
<i class="fas fa-spinner fa-spin" style="color: #eb0b0b;"></i>
|
||
<span id="batchExportStatus" style="font-size: 13px; font-weight: 500;">Esportazione...</span>
|
||
<button type="button" class="btn btn-outline-danger btn-sm" id="exportBatchCancelBtn" style="padding: 2px 10px; font-size: 12px;">Annulla</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="editForm">
|
||
<div class="top-scrollbar" id="topScrollbar">
|
||
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
|
||
</div>
|
||
|
||
<div class="grid-container" id="gridContainer">
|
||
<div class="grid-top" id="gridTopContainer"></div>
|
||
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
||
<div id="gridRowContainer"></div>
|
||
</div>
|
||
</form>
|
||
<div id="partsModalContainer"></div>
|
||
<div id="analysisModalContainer"></div>
|
||
<div id="annotationsModalContainer"></div>
|
||
<?php include 'photos_functions.php'; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="overlay toggle-icon"></div>
|
||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||
<?php include('include/footer.php'); ?>
|
||
</div>
|
||
<style>
|
||
.btn i {
|
||
margin-top: 0 !important;
|
||
margin-bottom: 0 !important;
|
||
}
|
||
</style>
|
||
<?php include('jsinclude.php'); ?>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||
<script src="tracking.js"></script>
|
||
<script src="gridRenderer.js"></script>
|
||
<script src="saveAll.js"></script>
|
||
<script src="exportLims_gridData.js"></script>
|
||
<script src="modals_gridData.js"></script>
|
||
<script src="photos.js"></script>
|
||
<script src="annotationsModal.js"></script>
|
||
<script src="partsTable.js"></script>
|
||
<script src="analysisModal.js"></script>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const topScrollbar = document.getElementById('topScrollbar');
|
||
const topScrollbarInner = document.getElementById('topScrollbarInner');
|
||
const gridContainer = document.getElementById('gridContainer');
|
||
|
||
if (!topScrollbar || !topScrollbarInner || !gridContainer) return;
|
||
|
||
let syncingFromTop = false;
|
||
let syncingFromGrid = false;
|
||
|
||
function updateTopScrollbarWidth() {
|
||
topScrollbarInner.style.width = gridContainer.scrollWidth + 'px';
|
||
|
||
// Mostra la barra solo se serve davvero
|
||
if (gridContainer.scrollWidth > gridContainer.clientWidth) {
|
||
topScrollbar.style.display = 'block';
|
||
} else {
|
||
topScrollbar.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
topScrollbar.addEventListener('scroll', function() {
|
||
if (syncingFromGrid) return;
|
||
syncingFromTop = true;
|
||
gridContainer.scrollLeft = topScrollbar.scrollLeft;
|
||
syncingFromTop = false;
|
||
});
|
||
|
||
gridContainer.addEventListener('scroll', function() {
|
||
if (syncingFromTop) return;
|
||
syncingFromGrid = true;
|
||
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
||
syncingFromGrid = false;
|
||
});
|
||
|
||
updateTopScrollbarWidth();
|
||
|
||
window.addEventListener('resize', updateTopScrollbarWidth);
|
||
|
||
// Ritarda un attimo per sicurezza, visto che la griglia viene renderizzata via JS
|
||
setTimeout(updateTopScrollbarWidth, 200);
|
||
setTimeout(updateTopScrollbarWidth, 600);
|
||
setTimeout(updateTopScrollbarWidth, 1200);
|
||
});
|
||
</script>
|
||
|
||
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportConfirmModalLabel">Conferma Esportazione</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="exportConfirmMessage">Confermi l'esportazione al LIMS per iddatadb <span id="exportIddatadb"></span>?</p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-primary btn-sm" id="exportConfirmBtn">Conferma</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modale di risposta per l'esportazione -->
|
||
<div class="modal fade" id="exportResponseModal" tabindex="-1" aria-labelledby="exportResponseModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportResponseModalLabel">Risultato Esportazione</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="exportResponseMessage"></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: unsaved changes before export -->
|
||
<div class="modal fade" id="exportUnsavedModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Modifiche non salvate</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
La riga contiene modifiche non salvate. Salvare prima di procedere con l'esportazione?
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-primary btn-sm" id="saveAndExportBtn">Salva ed Esporta</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: batch export confirm -->
|
||
<div class="modal fade" id="exportBatchConfirmModal" tabindex="-1" aria-labelledby="exportBatchConfirmModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="exportBatchConfirmModalLabel">Conferma Export All</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
Verranno esportate <strong><span id="exportBatchCount"></span></strong> righe al LIMS, una alla volta dall'alto verso il basso.<br>
|
||
Potrai annullare il batch in qualsiasi momento.
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-danger btn-sm" id="exportBatchConfirmBtn"><i class="fas fa-upload"></i> Avvia Export</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: batch export unsaved changes -->
|
||
<div class="modal fade" id="exportBatchUnsavedModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Modifiche non salvate</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
Ci sono modifiche non salvate. Salvare tutte le righe prima di procedere con l'esportazione?
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-primary btn-sm" id="batchSaveAndExportBtn"><i class="fas fa-save"></i> Salva ed Esporta</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Save All confirm -->
|
||
<!-- Delete Confirm Modal -->
|
||
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Confirm Delete</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
Delete record <strong id="deleteIddatadbText"></strong>? This cannot be undone.
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" class="btn btn-danger btn-sm" id="deleteConfirmBtn"><i class="fas fa-trash"></i> Delete</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="saveAllConfirmModal" tabindex="-1" aria-labelledby="saveAllConfirmModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="saveAllConfirmModalLabel">Conferma Salvataggio</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
Confermi il salvataggio di tutte le righe?
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Annulla</button>
|
||
<button type="button" class="btn btn-success btn-sm" id="saveAllConfirmBtn"><i class="fas fa-save"></i> Conferma</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal: Save All result -->
|
||
<div class="modal fade" id="saveAllResultModal" tabindex="-1" aria-labelledby="saveAllResultModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="saveAllResultModalLabel">Risultato Salvataggio</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p id="saveAllResultMessage"></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast: row saved successfully -->
|
||
<div class="position-fixed bottom-0 end-0 p-3" style="z-index:9999">
|
||
<div id="saveSuccessToast" class="toast align-items-center text-bg-success border-0" role="alert" data-bs-delay="2500">
|
||
<div class="d-flex">
|
||
<div class="toast-body"><i class="fas fa-check-circle me-2"></i>Riga salvata con successo.</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
|
||
</html>
|