Compare commits

...

3 Commits

Author SHA1 Message Date
a8330d4aba fixed layout 2026-02-04 15:31:53 +01:00
82d6a2ee18 feat(import_edit2): expand entire column including header and fix sticky columns 2026-02-04 17:00:01 +04:00
d8eddb3aa5 feat(import_edit2): enhance grid UI with sticky columns and textarea conversion
- Fix grid-top alignment and datepicker positioning
- Extend column resizer to grid-top cells
- Add sticky columns for Actions and Sample Description
- Implement input-to-textarea conversion on focus
- Increase Select2 dropdown height
- Improve grid-container CSS for sticky support
2026-02-04 11:59:59 +04:00
2 changed files with 311 additions and 37 deletions

File diff suppressed because one or more lines are too long

View File

@ -156,7 +156,8 @@ function fixedDefaultValue(array $f): string
}
input,
select {
select,
textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid #ced4da;
@ -166,10 +167,36 @@ function fixedDefaultValue(array $f): string
}
input,
select {
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;
@ -197,11 +224,12 @@ function fixedDefaultValue(array $f): string
.grid-container {
overflow-x: auto;
max-width: 100%;
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 {
@ -249,13 +277,69 @@ function fixedDefaultValue(array $f): string
border-right: none;
}
.grid-cell.expanded {
/* 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;
@ -274,8 +358,10 @@ function fixedDefaultValue(array $f): string
.grid-top {
display: flex;
align-items: flex-start;
align-items: stretch;
padding: 10px 0;
min-height: 0;
flex-wrap: nowrap;
}
.grid-top .grid-cell {
@ -285,6 +371,7 @@ function fixedDefaultValue(array $f): string
display: flex;
flex-direction: column;
align-items: center;
overflow: visible;
}
.grid-top .save-all-cell {
@ -292,6 +379,15 @@ function fixedDefaultValue(array $f): string
align-items: flex-start;
}
.grid-top .grid-cell input,
.grid-top .grid-cell select,
.grid-top .grid-cell .date-picker,
.grid-top .grid-cell .fixed-top {
position: relative;
max-width: 100%;
z-index: 1;
}
.propagate-btn {
background: none;
border: none;
@ -489,6 +585,10 @@ function fixedDefaultValue(array $f): string
font-size: 14px;
}
.select2-results__options {
max-height: 400px !important;
}
.select2-container--default .select2-selection--single {
height: 31px;
border: 1px solid #ced4da;
@ -552,8 +652,12 @@ function fixedDefaultValue(array $f): string
<div class="grid-cell save-all-cell">
<button type="button" class="save-all-btn" title="Save All Rows"><i class="fas fa-save"></i> Save All</button>
</div>
<?php if ($mainFieldMapping): ?>
<div class="grid-cell" style="flex: 0 0 150px;">
<?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') {
@ -578,35 +682,45 @@ function fixedDefaultValue(array $f): string
}
?>
</div>
<?php endif; ?>
<div class="grid-cell" style="flex: 0 0 300px;">
<?php endif;
$topColIndex = $mainFieldMapping ? 2 : 1; ?>
<div class="grid-cell grid-top-cell" style="flex: 0 0 300px;" data-index="<?= $mainFieldMapping ? 2 : 1 ?>">
<select class="custom-field dropdown-select client-select searchable-client" data-column="idclient" id="clientSelect" name="idclient">
<option value="">Select a client...</option>
</select>
<button type="button" class="propagate-btn" data-column="idclient"><i class="fas fa-arrow-down"></i></button>
<span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
</div>
<div class="grid-cell" style="flex: 0 0 150px;"></div>
<div class="grid-cell grid-top-cell" style="flex: 0 0 150px;" data-index="<?= $mainFieldMapping ? 3 : 2 ?>"></div>
<?php
$topColIndex = $mainFieldMapping ? 4 : 3;
$autoIndex = 0;
foreach ($allMappings as $mapping) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$inputClass = 'auto-input';
if ($mapping['is_required']) $inputClass .= ' required-input';
if ($mapping['data_type'] === 'SceltaMultipla') {
echo "<div class='grid-cell' style='flex: 0 0 150px;'>";
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 "<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 {
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 150px;' data-index='$topColIndex'></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'] ?? '';
@ -615,7 +729,7 @@ function fixedDefaultValue(array $f): string
}
$inputClass = 'manual-input';
if ($mapping['is_required']) $inputClass .= ' required-input';
echo "<div class='grid-cell' style='flex: 0 0 150px;'>";
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>";
@ -629,18 +743,21 @@ function fixedDefaultValue(array $f): string
}
echo "<button type='button' class='propagate-btn' data-column='manual_$manualIndex'><i class='fas fa-arrow-down'></i></button>";
echo "</div>";
$manualIndex++;
$topColIndex++;
}
}
// Aggiunta per Tested Component (senza propagate)
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 200px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 250px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>";
// ---------------- FIXED FIELDS TOP (propagate) ----------------
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
@ -651,7 +768,8 @@ function fixedDefaultValue(array $f): string
$inputClass = 'manual-input' . ($isRequired ? ' required-input' : '');
$topRequiredClass = ($isRequired && ($val === '' || $val === null)) ? 'missing-required' : '';
echo "<div class='grid-cell {$topRequiredClass}' style='flex: 0 0 180px;'>";
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');
@ -697,6 +815,16 @@ function fixedDefaultValue(array $f): string
// 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;
}
}
}
@ -880,7 +1008,7 @@ function fixedDefaultValue(array $f): string
echo "</div>";
$cellIndex++;
?>
<div class="grid-cell" style="flex: 0 0 200px;">
<div class="grid-cell" data-index="<?= $cellIndex ?>" style="flex: 0 0 200px;">
<select name="rows[<?= $index ?>][carrier]" class="carrier-select">
<option value="tnt-it">TNT Italy</option>
<option value="dhl">DHL</option>
@ -891,10 +1019,12 @@ function fixedDefaultValue(array $f): string
<input type="text" name="rows[<?= $index ?>][awb_number]" class="cell-input awb-input" placeholder="Inserisci AWB Number">
<button type="button" class="go-btn" data-row="<?= $index ?>"><i class="fas fa-play"></i></button>
</div>
<div class="grid-cell tracking-info" data-row="<?= $index ?>" style="flex: 0 0 250px;">
<?php $cellIndex++; ?>
<div class="grid-cell tracking-info" data-row="<?= $index ?>" data-index="<?= $cellIndex ?>" style="flex: 0 0 250px;">
<span class="tracking-result">Shipment Info</span>
<input type="hidden" name="rows[<?= $index ?>][tracking_info]" class="tracking-hidden">
</div>
<?php $cellIndex++; ?>
<?php
// ---------------- FIXED FIELDS CELLS ----------------
@ -992,16 +1122,32 @@ function fixedDefaultValue(array $f): string
<script src="tracking.js"></script>
<script src="export_to_lims.js"></script>
<script>
// Initialize Flatpickr for all date inputs
function expandColumn(cell, expand) {
const columnIndex = cell.getAttribute('data-index');
if (!columnIndex) return;
const allColumnCells = document.querySelectorAll(`.grid-cell[data-index="${columnIndex}"], .grid-header[data-index="${columnIndex}"], .grid-top-cell[data-index="${columnIndex}"]`);
allColumnCells.forEach(colCell => {
if (expand) {
colCell.classList.add('expanded');
} else {
colCell.classList.remove('expanded');
}
});
}
document.addEventListener("DOMContentLoaded", function() {
flatpickr(".date-picker", {
dateFormat: "Y-m-d",
allowInput: true,
onOpen: function(selectedDates, dateStr, instance) {
instance.input.closest('.grid-cell').classList.add('expanded');
const cell = instance.input.closest('.grid-cell');
expandColumn(cell, true);
},
onClose: function(selectedDates, dateStr, instance) {
instance.input.closest('.grid-cell').classList.remove('expanded');
const cell = instance.input.closest('.grid-cell');
expandColumn(cell, false);
// Trigger change event to handle unsaved changes
const event = new Event('change');
instance.input.dispatchEvent(event);
@ -1365,14 +1511,113 @@ function fixedDefaultValue(array $f): string
// Gestione degli input
inputs.forEach(input => {
input.addEventListener('focus', function() {
this.closest('.grid-cell').classList.add('expanded');
});
input.addEventListener('blur', function() {
this.closest('.grid-cell').classList.remove('expanded');
});
if (input.tagName === 'SELECT' ||
input.classList.contains('date-picker') ||
input.type === 'number' ||
input.type === 'date') {
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
expandColumn(cell, true);
});
input.addEventListener('blur', function() {
const cell = this.closest('.grid-cell');
expandColumn(cell, false);
});
return;
}
if (input.type === 'text') {
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
expandColumn(cell, true);
cell.dataset.convertingToTextarea = 'true';
const textarea = document.createElement('textarea');
textarea.value = this.value;
textarea.name = this.name;
textarea.className = this.className;
textarea.required = this.required;
textarea.style.border = '1px solid #ced4da';
textarea.setAttribute('data-mapping-id', this.getAttribute('data-mapping-id') || '');
textarea.setAttribute('data-field-id', this.getAttribute('data-field-id') || '');
textarea.setAttribute('data-selected-value', this.getAttribute('data-selected-value') || '');
textarea.setAttribute('data-current-value', this.getAttribute('data-current-value') || '');
textarea.setAttribute('data-fixed-key', this.getAttribute('data-fixed-key') || '');
textarea.dataset.originalInput = 'true';
this.parentNode.replaceChild(textarea, this);
setTimeout(() => {
textarea.focus();
delete cell.dataset.convertingToTextarea;
}, 0);
});
} else {
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
expandColumn(cell, true);
});
}
});
document.addEventListener('blur', function(e) {
const element = e.target;
if (element.tagName === 'TEXTAREA' && element.dataset.originalInput === 'true') {
const cell = element.closest('.grid-cell');
const input = document.createElement('input');
input.type = 'text';
input.value = element.value;
input.name = element.name;
input.className = element.className;
input.required = element.required;
input.setAttribute('data-mapping-id', element.getAttribute('data-mapping-id') || '');
input.setAttribute('data-field-id', element.getAttribute('data-field-id') || '');
input.setAttribute('data-selected-value', element.getAttribute('data-selected-value') || '');
input.setAttribute('data-current-value', element.getAttribute('data-current-value') || '');
input.setAttribute('data-fixed-key', element.getAttribute('data-fixed-key') || '');
element.parentNode.replaceChild(input, element);
expandColumn(cell, false);
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
expandColumn(cell, true);
cell.dataset.convertingToTextarea = 'true';
const textarea = document.createElement('textarea');
textarea.value = this.value;
textarea.name = this.name;
textarea.className = this.className;
textarea.required = this.required;
textarea.style.border = '1px solid #ced4da';
textarea.setAttribute('data-mapping-id', this.getAttribute('data-mapping-id') || '');
textarea.setAttribute('data-field-id', this.getAttribute('data-field-id') || '');
textarea.setAttribute('data-selected-value', this.getAttribute('data-selected-value') || '');
textarea.setAttribute('data-current-value', this.getAttribute('data-current-value') || '');
textarea.setAttribute('data-fixed-key', this.getAttribute('data-fixed-key') || '');
textarea.dataset.originalInput = 'true';
this.parentNode.replaceChild(textarea, this);
setTimeout(() => {
textarea.focus();
delete cell.dataset.convertingToTextarea;
}, 0);
});
const changeEvent = new Event('change', { bubbles: true });
input.dispatchEvent(changeEvent);
} else if (element.tagName === 'INPUT' && element.type === 'text') {
const cell = element.closest('.grid-cell');
if (cell && !cell.dataset.convertingToTextarea) {
expandColumn(cell, false);
}
}
}, true);
// Gestione della propagazione
const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => {
@ -1453,12 +1698,14 @@ function fixedDefaultValue(array $f): string
function resize(e) {
if (currentResizer) {
const dx = e.pageX - startX;
const newWidth = startWidth + dx;
const deltaX = e.pageX - startX;
const newWidth = Math.max(80, startWidth + deltaX);
const headers = document.querySelectorAll(`.grid-header[data-index="${columnIndex}"]`);
const cells = document.querySelectorAll(`.grid-cell[data-index="${columnIndex}"]`);
const topCells = document.querySelectorAll(`.grid-top .grid-cell[data-index="${columnIndex}"]`);
headers.forEach(header => header.style.flex = `0 0 ${newWidth}px`);
cells.forEach(cell => cell.style.flex = `0 0 ${newWidth}px`);
topCells.forEach(cell => cell.style.flex = `0 0 ${newWidth}px`);
}
}
@ -2009,6 +2256,33 @@ function fixedDefaultValue(array $f): string
});
}
});
// Fix for sticky columns
const gridContainer = document.querySelector('.grid-container');
if (gridContainer) {
const rows = document.querySelectorAll('.grid-row');
rows.forEach(row => {
if (!row.style.minWidth) {
row.style.minWidth = 'max-content';
}
});
const gridTop = document.querySelector('.grid-top');
if (gridTop && !gridTop.style.minWidth) {
gridTop.style.minWidth = 'max-content';
}
const stickyTopCells = document.querySelectorAll('.grid-top .grid-cell.save-all-cell, .grid-top .grid-cell:nth-child(2)');
stickyTopCells.forEach(cell => {
if (!cell.style.minWidth && !cell.style.flex) {
const computedStyle = window.getComputedStyle(cell);
if (computedStyle.flex === '0 0 auto' || !computedStyle.flex.includes('0 0')) {
cell.style.minWidth = cell.offsetWidth + 'px';
}
}
});
}
});
</script>
<!-- Modale di conferma per l'esportazione -->