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
This commit is contained in:
Martin Grigoryan 2026-02-04 11:59:59 +04:00
parent f60dc64b2d
commit d8eddb3aa5

View File

@ -156,7 +156,8 @@ function fixedDefaultValue(array $f): string
} }
input, input,
select { select,
textarea {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #ced4da; border: 1px solid #ced4da;
@ -166,10 +167,36 @@ function fixedDefaultValue(array $f): string
} }
input, input,
select { select,
textarea {
color: #333; 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 { .status-badge {
display: inline-block; display: inline-block;
padding: 2px 8px; padding: 2px 8px;
@ -197,11 +224,12 @@ function fixedDefaultValue(array $f): string
.grid-container { .grid-container {
overflow-x: auto; overflow-x: auto;
max-width: 100%; width: 100%;
margin-bottom: 20px; margin-bottom: 20px;
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 0.25rem; border-radius: 0.25rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
} }
.grid-row { .grid-row {
@ -249,6 +277,55 @@ function fixedDefaultValue(array $f): string
border-right: none; border-right: none;
} }
/* Sticky columns - first column (Actions) */
.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-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-cell.expanded {
max-width: 500px !important; max-width: 500px !important;
white-space: normal !important; white-space: normal !important;
@ -276,6 +353,8 @@ function fixedDefaultValue(array $f): string
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
padding: 10px 0; padding: 10px 0;
min-height: 0;
flex-wrap: nowrap;
} }
.grid-top .grid-cell { .grid-top .grid-cell {
@ -285,6 +364,7 @@ function fixedDefaultValue(array $f): string
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow: visible;
} }
.grid-top .save-all-cell { .grid-top .save-all-cell {
@ -292,6 +372,15 @@ function fixedDefaultValue(array $f): string
align-items: flex-start; 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 { .propagate-btn {
background: none; background: none;
border: none; border: none;
@ -489,6 +578,10 @@ function fixedDefaultValue(array $f): string
font-size: 14px; font-size: 14px;
} }
.select2-results__options {
max-height: 400px !important;
}
.select2-container--default .select2-selection--single { .select2-container--default .select2-selection--single {
height: 31px; height: 31px;
border: 1px solid #ced4da; border: 1px solid #ced4da;
@ -552,8 +645,12 @@ function fixedDefaultValue(array $f): string
<div class="grid-cell save-all-cell"> <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> <button type="button" class="save-all-btn" title="Save All Rows"><i class="fas fa-save"></i> Save All</button>
</div> </div>
<?php if ($mainFieldMapping): ?> <?php
<div class="grid-cell" style="flex: 0 0 150px;">
$topColIndex = 1;
if ($mainFieldMapping): ?>
<div class="grid-cell grid-top-cell" style="flex: 0 0 150px;" data-index="1">
<?php <?php
$fieldValue = $mainFieldMapping['manual_default'] ?? ''; $fieldValue = $mainFieldMapping['manual_default'] ?? '';
if ($mainFieldMapping['data_type'] === 'Data' && $mainFieldMapping['manual_default'] === 'today') { if ($mainFieldMapping['data_type'] === 'Data' && $mainFieldMapping['manual_default'] === 'today') {
@ -578,35 +675,45 @@ function fixedDefaultValue(array $f): string
} }
?> ?>
</div> </div>
<?php endif; ?> <?php endif;
<div class="grid-cell" style="flex: 0 0 300px;">
$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"> <select class="custom-field dropdown-select client-select searchable-client" data-column="idclient" id="clientSelect" name="idclient">
<option value="">Select a client...</option> <option value="">Select a client...</option>
</select> </select>
<button type="button" class="propagate-btn" data-column="idclient"><i class="fas fa-arrow-down"></i></button> <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> <span id="clientLoadingStatus" class="text-muted" style="margin-left: 10px; display: none;">Recupero clienti in corso...</span>
</div> </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 <?php
$topColIndex = $mainFieldMapping ? 4 : 3;
$autoIndex = 0; $autoIndex = 0;
foreach ($allMappings as $mapping) { foreach ($allMappings as $mapping) {
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) { if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$inputClass = 'auto-input'; $inputClass = 'auto-input';
if ($mapping['is_required']) $inputClass .= ' required-input'; if ($mapping['is_required']) $inputClass .= ' required-input';
if ($mapping['data_type'] === 'SceltaMultipla') { if ($mapping['data_type'] === 'SceltaMultipla') {
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'>";
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 "<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 "<option value=''>Seleziona...</option>";
echo "</select>"; echo "</select>";
echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>"; echo "<button type='button' class='propagate-btn' data-column='auto_$autoIndex'><i class='fas fa-arrow-down'></i></button>";
echo "</div>"; echo "</div>";
} else { } 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++; $autoIndex++;
$topColIndex++;
} }
} }
$manualIndex = 0; $manualIndex = 0;
foreach ($allMappings as $mapping) { foreach ($allMappings as $mapping) {
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) { if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
$fieldValue = $mapping['manual_default'] ?? ''; $fieldValue = $mapping['manual_default'] ?? '';
@ -615,7 +722,7 @@ function fixedDefaultValue(array $f): string
} }
$inputClass = 'manual-input'; $inputClass = 'manual-input';
if ($mapping['is_required']) $inputClass .= ' required-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') { 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 "<select class='custom-field dropdown-select $inputClass' data-column='manual_$manualIndex' data-field-id='{$mapping['field_id']}' data-selected-value='" . htmlspecialchars($fieldValue) . "' " . ($mapping['is_required'] ? 'required' : '') . ">";
echo "<option value=''>Seleziona...</option>"; echo "<option value=''>Seleziona...</option>";
@ -629,18 +736,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 "<button type='button' class='propagate-btn' data-column='manual_$manualIndex'><i class='fas fa-arrow-down'></i></button>";
echo "</div>"; echo "</div>";
$manualIndex++; $manualIndex++;
$topColIndex++;
} }
} }
// Aggiunta per Tested Component (senza propagate)
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>";
echo "<div class='grid-cell' style='flex: 0 0 200px;'></div>"; $topColIndex++;
echo "<div class='grid-cell' style='flex: 0 0 250px;'></div>"; echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 200px;' data-index='$topColIndex'></div>";
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; $topColIndex++;
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; echo "<div class='grid-cell grid-top-cell' style='flex: 0 0 250px;' data-index='$topColIndex'></div>";
echo "<div class='grid-cell' style='flex: 0 0 150px;'></div>"; $topColIndex++;
// ---------------- FIXED FIELDS TOP (propagate) ---------------- // ---------------- FIXED FIELDS TOP (propagate) - same order as header: fixed cols, 3 empties after ConsegnaRichiesta ----------------
if (!empty($fixedFields)) { if (!empty($fixedFields)) {
$insertedAfterConsegnaTop = false;
foreach ($fixedFields as $fx => $f) { foreach ($fixedFields as $fx => $f) {
$key = $f['fixed_field_key']; // datadb column $key = $f['fixed_field_key']; // datadb column
@ -651,7 +761,8 @@ function fixedDefaultValue(array $f): string
$inputClass = 'manual-input' . ($isRequired ? ' required-input' : ''); $inputClass = 'manual-input' . ($isRequired ? ' required-input' : '');
$topRequiredClass = ($isRequired && ($val === '' || $val === null)) ? 'missing-required' : ''; $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 // Forza DATE anche se per errore nel DB è diversa
$isDate = ($f['data_type'] === 'DATE' || $key === 'ConsegnaRichiesta'); $isDate = ($f['data_type'] === 'DATE' || $key === 'ConsegnaRichiesta');
@ -697,6 +808,16 @@ function fixedDefaultValue(array $f): string
// UNA SOLA freccia // UNA SOLA freccia
echo "<button type='button' class='propagate-btn' data-column='fixed_{$fx}'><i class='fas fa-arrow-down'></i></button>"; echo "<button type='button' class='propagate-btn' data-column='fixed_{$fx}'><i class='fas fa-arrow-down'></i></button>";
echo "</div>"; 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 +1001,7 @@ function fixedDefaultValue(array $f): string
echo "</div>"; echo "</div>";
$cellIndex++; $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"> <select name="rows[<?= $index ?>][carrier]" class="carrier-select">
<option value="tnt-it">TNT Italy</option> <option value="tnt-it">TNT Italy</option>
<option value="dhl">DHL</option> <option value="dhl">DHL</option>
@ -891,10 +1012,12 @@ function fixedDefaultValue(array $f): string
<input type="text" name="rows[<?= $index ?>][awb_number]" class="cell-input awb-input" placeholder="Inserisci AWB Number"> <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> <button type="button" class="go-btn" data-row="<?= $index ?>"><i class="fas fa-play"></i></button>
</div> </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> <span class="tracking-result">Shipment Info</span>
<input type="hidden" name="rows[<?= $index ?>][tracking_info]" class="tracking-hidden"> <input type="hidden" name="rows[<?= $index ?>][tracking_info]" class="tracking-hidden">
</div> </div>
<?php $cellIndex++; ?>
<?php <?php
// ---------------- FIXED FIELDS CELLS ---------------- // ---------------- FIXED FIELDS CELLS ----------------
@ -1365,14 +1488,110 @@ function fixedDefaultValue(array $f): string
// Gestione degli input // Gestione degli input
inputs.forEach(input => { inputs.forEach(input => {
input.addEventListener('focus', function() { if (input.tagName === 'SELECT' ||
this.closest('.grid-cell').classList.add('expanded'); input.classList.contains('date-picker') ||
}); input.type === 'number' ||
input.addEventListener('blur', function() { input.type === 'date') {
this.closest('.grid-cell').classList.remove('expanded'); input.addEventListener('focus', function() {
}); this.closest('.grid-cell').classList.add('expanded');
});
input.addEventListener('blur', function() {
this.closest('.grid-cell').classList.remove('expanded');
});
return;
}
if (input.type === 'text') {
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
cell.classList.add('expanded');
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() {
this.closest('.grid-cell').classList.add('expanded');
});
}
}); });
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);
cell.classList.remove('expanded');
input.addEventListener('focus', function() {
const cell = this.closest('.grid-cell');
cell.classList.add('expanded');
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) {
cell.classList.remove('expanded');
}
}
}, true);
// Gestione della propagazione // Gestione della propagazione
const propagateButtons = document.querySelectorAll('.propagate-btn'); const propagateButtons = document.querySelectorAll('.propagate-btn');
propagateButtons.forEach(button => { propagateButtons.forEach(button => {
@ -1453,12 +1672,14 @@ function fixedDefaultValue(array $f): string
function resize(e) { function resize(e) {
if (currentResizer) { if (currentResizer) {
const dx = e.pageX - startX; const deltaX = e.pageX - startX;
const newWidth = startWidth + dx; const newWidth = Math.max(80, startWidth + deltaX);
const headers = document.querySelectorAll(`.grid-header[data-index="${columnIndex}"]`); const headers = document.querySelectorAll(`.grid-header[data-index="${columnIndex}"]`);
const cells = document.querySelectorAll(`.grid-cell[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`); headers.forEach(header => header.style.flex = `0 0 ${newWidth}px`);
cells.forEach(cell => cell.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 +2230,17 @@ 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';
}
});
}
}); });
</script> </script>
<!-- Modale di conferma per l'esportazione --> <!-- Modale di conferma per l'esportazione -->