1239 lines
42 KiB
PHP
1239 lines
42 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';
|
|
|
|
$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 = ?';
|
|
$stmt = $pdo->prepare("
|
|
SELECT d.*, CONCAT(u.first_name, ' ', u.last_name) AS user_name
|
|
FROM datadb d
|
|
LEFT JOIN auth_users u ON d.user_id = u.id
|
|
WHERE d.templateid = ? AND d.status = 'i' {$userFilter}
|
|
ORDER BY d.iddatadb DESC
|
|
");
|
|
$params = [$template_id];
|
|
if (!$show_all_users) $params[] = $user_id;
|
|
$stmt->execute($params);
|
|
$importedData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$insertedIds = array_column($importedData, 'iddatadb');
|
|
|
|
// Fetch custom field details
|
|
$manualDetails = [];
|
|
if (!empty($insertedIds)) {
|
|
$placeholders = implode(',', array_fill(0, count($insertedIds), '?'));
|
|
$stmt = $pdo->prepare("
|
|
SELECT d.id AS detail_id, d.id AS datadb_id, d.mapping_id, d.field_value,
|
|
m.field_id, m.field_label, m.data_type, m.is_required, m.manual_default
|
|
FROM import_data_details d
|
|
JOIN template_mapping m ON d.mapping_id = m.id
|
|
WHERE d.id IN ({$placeholders})
|
|
");
|
|
$stmt->execute($insertedIds);
|
|
$manualDetails = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
// Recupera il mapping globale per mostrare gli slug leggibili
|
|
$stmt = $pdo->query("SELECT mysql_column_name, user_friendly_slug FROM column_mapping");
|
|
$slugMapping = [];
|
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$slugMapping[$row['mysql_column_name']] = $row['user_friendly_slug'];
|
|
}
|
|
// --- FIX LABELS ONLY (do not change keys) ---
|
|
$slugMapping['AnagraficaCertestObject'] = 'Object';
|
|
$slugMapping['AnagraficaCertestService'] = 'Service';
|
|
|
|
// ---------------- FIXED FIELDS (from template_fixed_mapping) ----------------
|
|
$fixedStmt = $pdo->prepare("
|
|
SELECT id, fixed_field_key, is_manual, data_type, is_required, default_value, is_visible_import
|
|
FROM template_fixed_mapping
|
|
WHERE template_id = ? AND is_visible_import = 1
|
|
ORDER BY id
|
|
");
|
|
$fixedStmt->execute([$template_id]);
|
|
$fixedFieldsRaw = $fixedStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Ordine desiderato: Cliente Responsabile prima, poi gli altri, ConsegnaRichiesta prima delle 3 speciali
|
|
$desiredOrder = [
|
|
'ClienteResponsabile',
|
|
'ClienteFornitore',
|
|
'ClienteAnalisi',
|
|
'AnagraficaCertestObject',
|
|
'AnagraficaCertestService',
|
|
'MoltiplicatorePrezzo',
|
|
'ConsegnaRichiesta',
|
|
// se ci sono altri campi fixed li mettiamo dopo
|
|
];
|
|
|
|
// 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,
|
|
'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) {
|
|
$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) {
|
|
$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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
</style>
|
|
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="wrapper">
|
|
<?php include('include/navbar.php'); ?>
|
|
<?php include('include/topbar.php'); ?>
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
<div class="mb-3 text d-flex align-items-center gap-2">
|
|
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
|
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
|
<span class="ms-3">
|
|
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
|
<input type="checkbox" class="form-check-input" id="showAllUsers" <?= $show_all_users ? 'checked' : '' ?>
|
|
onchange="window.location.href='imported.php?id=<?= $template_id ?>' + (this.checked ? '&all_users=1' : '')">
|
|
Show all users
|
|
</label>
|
|
</span>
|
|
<span class="text-muted" style="font-size: 12px;">(<?= count($importedData) ?> records<?= !$show_all_users ? ' — my records only' : '' ?>)</span>
|
|
</div>
|
|
<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">
|
|
<?php if ((Auth::user()->hasRole('Admin'))) : ?>
|
|
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
|
<?php endif; ?>
|
|
<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="grid-container">
|
|
<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="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>
|
|
|
|
<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>
|