diff --git a/public/userarea/add_record.php b/public/userarea/add_record.php
index fa711f7..4eafe9b 100644
--- a/public/userarea/add_record.php
+++ b/public/userarea/add_record.php
@@ -33,15 +33,26 @@ try {
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
$iddatadb = (int)$pdo->lastInsertId();
- // Create empty import_data_details for all mappings
- $mappingStmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
+ // Create import_data_details for all mappings (with auto_value support)
+ $mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
$mappingStmt->execute([$templateId]);
- $mappings = $mappingStmt->fetchAll(PDO::FETCH_COLUMN);
+ $mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($mappings)) {
- $insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, '')");
- foreach ($mappings as $mappingId) {
- $insertStmt->execute([$iddatadb, $mappingId]);
+ $insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
+ foreach ($mappings as $m) {
+ $val = '';
+ $auto = $m['auto_value'] ?? 'none';
+ if ($auto === 'import_date') {
+ $val = date('Y-m-d');
+ } elseif ($auto === 'import_time') {
+ $val = date('H:i');
+ } elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
+ $val = date('Y-m-d');
+ } elseif (!empty($m['manual_default'])) {
+ $val = $m['manual_default'];
+ }
+ $insertStmt->execute([$iddatadb, $m['id'], $val]);
}
}
diff --git a/public/userarea/get_clienti.php b/public/userarea/get_clienti.php
index 0e6dd83..7996167 100644
--- a/public/userarea/get_clienti.php
+++ b/public/userarea/get_clienti.php
@@ -74,10 +74,21 @@ try {
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
}
+ // Cache file (1 hour TTL)
+ $cacheFile = __DIR__ . '/cache/clienti.json';
+ if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
+ readfile($cacheFile);
+ exit;
+ }
+
// Esegui la chiamata con retry
$data = makeApiRequest($api, $endpoint);
- echo json_encode($data);
+ $json = json_encode($data);
+ if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
+ file_put_contents($cacheFile, $json);
+
+ echo $json;
} catch (Exception $e) {
http_response_code(500);
$errorResponse = [
diff --git a/public/userarea/get_customfield_values.php b/public/userarea/get_customfield_values.php
index 5770073..71ea7a4 100644
--- a/public/userarea/get_customfield_values.php
+++ b/public/userarea/get_customfield_values.php
@@ -21,17 +21,26 @@ try {
}
$results = [];
+ $cacheDir = __DIR__ . '/cache';
+ if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
foreach ($fieldIds as $customFieldId) {
+ $cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
+
+ // Use cache if fresh (1 hour)
+ if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
+ $results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
+ continue;
+ }
+
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
$data = $api->get($endpoint);
+ $values = $data['CustomFieldsValues'] ?? [];
+ $results[$customFieldId] = $values;
- $results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
+ file_put_contents($cacheFile, json_encode($values));
}
- // Debug ფაილი
- file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
-
echo json_encode($results);
} catch (Exception $e) {
http_response_code(500);
diff --git a/public/userarea/gridRenderer.js b/public/userarea/gridRenderer.js
index 63c2c96..502c21d 100644
--- a/public/userarea/gridRenderer.js
+++ b/public/userarea/gridRenderer.js
@@ -10,7 +10,8 @@
const PAGE_SIZE = 20;
let revealedCount = PAGE_SIZE;
- let dropdownOptionsCache = {}; // fieldId -> [{id, text}]
+ let dropdownOptionsCache = {}; // fieldId -> [{id, text}] — used only for small lists
+ const dropdownNameCache = {}; // "fieldId_valueId" -> label
let clientData = []; // loaded from get_clienti.php
let fixedFieldCache = window.fixedFieldDataCache || {};
window.fixedFieldDataCache = fixedFieldCache;
@@ -53,36 +54,75 @@
data[rowIndex]._dirty = true;
}
- // ── Client data loading ────────────────────────────────────────────────
+ // ── Client data (AJAX Select2, no bulk loading) ──────────────────────
function formatClientLabel(client) {
- const nome = client.Nominativo || '';
- const id = client.IdCliente || '';
- const code = (client.CodiceCliente || '').toString().trim();
- const suffix = (code.split('_')[1] || '').trim();
- const short = suffix || (code ? code.charAt(0) : '--');
- return `${nome.trim()} - ${short} (ID: ${id})`;
+ return (client.Nominativo || '').trim();
}
+ // Cache of resolved client names: id → name
+ const clientNameCache = {};
+
+ // Build select with only the selected option
function buildClientOptionsHTML(selectedId) {
let html = '';
- clientData.forEach(c => {
- const id = c.IdCliente || '';
- const sel = String(id) === String(selectedId) ? ' selected' : '';
- html += ``;
- });
+ if (selectedId) {
+ const label = clientNameCache[selectedId] || selectedId;
+ html += ``;
+ }
return html;
}
- async function loadClientData() {
- if (clientData.length > 0) return;
- try {
- const resp = await fetch('get_clienti.php');
- const json = await resp.json();
- clientData = json.value || [];
- } catch (e) {
- console.error('Failed to load clients:', e);
+ // Pre-resolve all unique client IDs used in data rows, then re-render
+ async function resolveClientNames() {
+ const ids = new Set();
+ data.forEach(row => {
+ if (row.idclient) ids.add(String(row.idclient));
+ if (row.cliente_fornitore_id) ids.add(String(row.cliente_fornitore_id));
+ // Fixed fields that are client-sourced
+ if (row.fixedFields) {
+ for (const [key, val] of Object.entries(row.fixedFields)) {
+ const cfg = fixedFieldApiConfig[key];
+ if (cfg && cfg.source === 'clients' && val) ids.add(String(val));
+ }
+ }
+ });
+
+ // Batch resolve via single request per ID
+ const unresolvedIds = [...ids].filter(id => !clientNameCache[id]);
+ if (unresolvedIds.length === 0) return;
+
+ await Promise.all(unresolvedIds.map(async (id) => {
+ try {
+ const resp = await fetch('search_clienti.php?id=' + encodeURIComponent(id));
+ const json = await resp.json();
+ const item = (json.results || [])[0];
+ if (item) clientNameCache[id] = item.text;
+ } catch (e) { /* ignore */ }
+ }));
+ }
+
+ // Select2 AJAX config for client selects
+ const clientSelect2Config = {
+ placeholder: 'Search client...',
+ allowClear: true,
+ width: '100%',
+ minimumInputLength: 0,
+ dropdownCssClass: 'select2-dropdown-smaller',
+ ajax: {
+ url: 'search_clienti.php',
+ dataType: 'json',
+ delay: 150,
+ data: function(params) { return { q: params.term || '', limit: 20 }; },
+ processResults: function(data) { return { results: data.results || [] }; },
+ cache: true
}
+ };
+
+ async function loadClientData() {
+ // No longer loads all clients — AJAX Select2 handles search
+ // Just resolve names for pre-selected values
+ await resolveClientNames();
}
// ── Fixed field data loading ───────────────────────────────────────────
@@ -102,13 +142,10 @@
const config = fixedFieldApiConfig[fieldKey];
if (!config) return [];
- // Client-sourced fields
+ // Client-sourced fields — handled by AJAX Select2, skip preloading
if (config.source === 'clients') {
- await loadClientData();
- const results = clientData.map(c => ({ id: c.IdCliente, text: formatClientLabel(c) }));
- results.sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }));
- fixedFieldCache[fieldKey] = results;
- return results;
+ fixedFieldCache[fieldKey] = [];
+ return [];
}
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
@@ -144,26 +181,23 @@
// ── Custom field dropdown data loading ─────────────────────────────────
- async function loadDropdownOptions(fieldIds) {
- const missing = fieldIds.filter(id => !dropdownOptionsCache[id]);
- if (missing.length > 0) {
- try {
- const resp = await fetch('get_customfield_values.php?field_ids=' + missing.join(','));
- const json = await resp.json();
- // API returns { fieldId: [values] } directly (no success/data wrapper)
- const entries = json.data ? json.data : json;
- for (const [fid, values] of Object.entries(entries)) {
- if (Array.isArray(values)) {
- const sorted = values.sort((a, b) =>
- String(a.Valore || '').localeCompare(String(b.Valore || ''), 'it', { sensitivity: 'base' })
- );
- dropdownOptionsCache[fid] = sorted;
- }
- }
- } catch (e) {
- console.error('Failed to load dropdown options:', e);
+
+ // Select2 AJAX config factory for SceltaMultipla
+ function sceltaSelect2Config(fieldId) {
+ return {
+ placeholder: 'Search...',
+ allowClear: true,
+ width: '100%',
+ minimumInputLength: 0,
+ ajax: {
+ url: 'search_customfield_values.php',
+ dataType: 'json',
+ delay: 150,
+ data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 10 }; },
+ processResults: function(data) { return { results: data.results || [] }; },
+ cache: true
}
- }
+ };
}
// ── Preload all data ───────────────────────────────────────────────────
@@ -192,13 +226,26 @@
}
}
- // 4. Custom field dropdowns
- const fieldIds = columns
- .filter(c => c.type === 'detail' && c.dataType === 'SceltaMultipla' && c.fieldId)
- .map(c => String(c.fieldId));
- const uniqueIds = [...new Set(fieldIds)];
- if (uniqueIds.length > 0) {
- await loadDropdownOptions(uniqueIds);
+ // 4. Warm server cache + resolve SceltaMultipla names in one request
+ const allFieldIds = [...new Set(
+ columns.filter(c => (c.type === 'detail' || c.type === 'main_field') && c.dataType === 'SceltaMultipla' && c.fieldId)
+ .map(c => String(c.fieldId))
+ )];
+ if (allFieldIds.length > 0) {
+ try {
+ const resp = await fetch('get_customfield_values.php?field_ids=' + allFieldIds.join(','));
+ const json = await resp.json();
+ const entries = json.data ? json.data : json;
+ for (const [fid, values] of Object.entries(entries)) {
+ if (Array.isArray(values)) {
+ values.forEach(v => {
+ dropdownNameCache[fid + '_' + v.IdCustomFieldsValue] = v.Valore || '';
+ });
+ }
+ }
+ } catch (e) {
+ console.error('Failed to preload dropdown data:', e);
+ }
}
console.log('[gridRenderer] preload done:', {
@@ -308,7 +355,7 @@
if (col.dataType === 'SceltaMultipla') {
const options = buildDropdownOptionsHTML(col.fieldId, value);
- return ``;
+ return ``;
}
if (col.dataType === 'Data') {
return ``;
@@ -329,11 +376,24 @@
return ``;
}
+ // Client-sourced fields → AJAX Select2 (like idclient)
+ const config = fixedFieldApiConfig[col.key];
+ if (config && config.source === 'clients') {
+ const reqCls = col.isRequired ? ' required-input' : '';
+ const req = col.isRequired ? ' required' : '';
+ let opts = '';
+ if (value) {
+ const label = clientNameCache[value] || value;
+ opts += ``;
+ }
+ return ``;
+ }
+
// Select — build from cache
- const isApiField = !!fixedFieldApiConfig[col.key];
+ const isApiField = !!config;
const selectClass = isApiField ? 'api-fixed-select' : '';
let options = '';
- const cacheKey = fixedFieldApiConfig[col.key]?.dependsOn
+ const cacheKey = config?.dependsOn
? col.key + '_' + (data[rowIndex].idclient || '')
: col.key;
const items = fixedFieldCache[cacheKey] || [];
@@ -349,11 +409,10 @@
function buildDropdownOptionsHTML(fieldId, selectedValue) {
let html = '';
- const items = dropdownOptionsCache[fieldId] || [];
- items.forEach(item => {
- const sel = String(item.IdCustomFieldsValue) === String(selectedValue) ? ' selected' : '';
- html += ``;
- });
+ if (selectedValue) {
+ const label = dropdownNameCache[fieldId + '_' + selectedValue] || selectedValue;
+ html += ``;
+ }
return html;
}
@@ -597,16 +656,16 @@
}
async function populateTopRowSelects() {
- // Client selects in top row
+ // Client selects in top row — AJAX mode
const clientSel = document.getElementById('clientSelect');
if (clientSel) {
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
- $(clientSel).select2({ placeholder: 'Select a client...', allowClear: true, width: '100%', minimumInputLength: 1 });
+ $(clientSel).select2(clientSelect2Config);
}
const fornitSel = document.getElementById('clienteFornitoreSelect');
if (fornitSel) {
fornitSel.innerHTML = buildClientOptionsHTML('');
- $(fornitSel).select2({ placeholder: 'Select a supplier...', allowClear: true, width: '100%', minimumInputLength: 1 });
+ $(fornitSel).select2(clientSelect2Config);
}
// Fixed field selects in top row
@@ -614,6 +673,12 @@
const fieldKey = sel.dataset.fixedKey;
const config = fixedFieldApiConfig[fieldKey];
+ // Client-sourced → init as AJAX Select2
+ if (config && config.source === 'clients') {
+ $(sel).select2(clientSelect2Config);
+ return;
+ }
+
if (config && config.dependsOn) {
// For dependent fields: merge all cached values across all clientIds
const allItems = new Map();
@@ -633,14 +698,10 @@
}
});
- // Custom field dropdowns in top row
+ // Custom field dropdowns in top row — AJAX Select2
topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
const fieldId = sel.dataset.fieldId;
- const items = dropdownOptionsCache[fieldId] || [];
- sel.innerHTML = '';
- items.forEach(item => {
- sel.add(new Option(item.Valore, item.IdCustomFieldsValue));
- });
+ if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
});
// Flatpickr in top row
@@ -700,6 +761,17 @@
if (!input) return;
const value = $(input).hasClass('select2-hidden-accessible') ? $(input).val() : input.value;
+ // Cache Select2 label so re-render shows name not ID
+ if (value && $(input).hasClass('select2-hidden-accessible')) {
+ const label = $(input).find('option:selected').text();
+ if (label && label !== value) {
+ clientNameCache[value] = label;
+ // Also cache for SceltaMultipla
+ const fieldId = input.dataset?.fieldId;
+ if (fieldId) dropdownNameCache[fieldId + '_' + value] = label;
+ }
+ }
+
const col = columns[colIndex] || null;
if (column === 'idclient') {
@@ -730,6 +802,12 @@
const colType = cell.dataset.colType;
const value = $(this).val() || '';
+ // Cache selected label
+ if (value) {
+ const label = $(this).find('option:selected').text();
+ if (label && label !== value) clientNameCache[value] = label;
+ }
+
if (colType === 'idclient') {
data[rowIndex].idclient = value;
data[rowIndex]._dirty = true;
@@ -741,6 +819,16 @@
updateDirtyIndicator();
});
+ // Cache labels on SceltaMultipla change
+ $(rowContainer).on('change', '.searchable-dropdown', function () {
+ const val = $(this).val();
+ const fieldId = this.dataset.fieldId;
+ if (val && fieldId) {
+ const label = $(this).find('option:selected').text();
+ if (label && label !== val) dropdownNameCache[fieldId + '_' + val] = label;
+ }
+ });
+
// Select2 change on fixed field selects
$(rowContainer).on('change', '.api-fixed-select', function () {
const cell = this.closest('.grid-cell');
@@ -826,18 +914,11 @@
function initLazySelect2() {
$(document).on('mouseenter', '.grid-row .grid-cell', function () {
$(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
- $(this).select2({
- placeholder: 'Select a client...',
- allowClear: true,
- width: '100%',
- dropdownCssClass: 'select2-dropdown-smaller',
- minimumInputLength: 1
- });
+ $(this).select2(clientSelect2Config);
});
- $(this).find('select[data-field-id]:not(.select2-hidden-accessible)').each(function () {
- if ((this.options || []).length > 12) {
- $(this).select2({ placeholder: 'Seleziona...', allowClear: true, width: '100%' });
- }
+ $(this).find('.searchable-dropdown:not(.select2-hidden-accessible)').each(function () {
+ const fieldId = this.dataset.fieldId;
+ if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
});
});
}
diff --git a/public/userarea/import_insert.php b/public/userarea/import_insert.php
index 8ddd231..1d1561d 100644
--- a/public/userarea/import_insert.php
+++ b/public/userarea/import_insert.php
@@ -47,7 +47,7 @@ $pdo = $db->getConnection();
$importReferenceCode = date('YmdHis') . '-' . uniqid();
// Recupera tutti i mapping dal template
-$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
+$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
$stmt->execute([$template_id]);
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
@@ -135,6 +135,15 @@ foreach ($selected_rows as $rowIndex) {
$fieldValue = date('Y-m-d');
}
}
+ // Apply auto_value if field is still empty
+ if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
+ if ($mapping['auto_value'] === 'import_date') {
+ $fieldValue = date('Y-m-d');
+ } elseif ($mapping['auto_value'] === 'import_time') {
+ $fieldValue = date('H:i');
+ }
+ }
+
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
}
diff --git a/public/userarea/search_clienti.php b/public/userarea/search_clienti.php
new file mode 100644
index 0000000..1d87d91
--- /dev/null
+++ b/public/userarea/search_clienti.php
@@ -0,0 +1,58 @@
+ 'IdCliente,Nominativo,CodiceCliente',
+ '$orderby' => 'Nominativo asc'
+ ];
+ $data = $api->get("Cliente?" . http_build_query($params));
+ if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
+ file_put_contents($cacheFile, json_encode($data));
+ }
+
+ $clients = $data['value'] ?? [];
+
+ // If requesting by specific ID (for loading selected value)
+ if ($id !== null) {
+ foreach ($clients as $c) {
+ if ((int)$c['IdCliente'] === $id) {
+ echo json_encode(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
+ exit;
+ }
+ }
+ echo json_encode(['results' => []]);
+ exit;
+ }
+
+ // Search by query
+ $results = [];
+ foreach ($clients as $c) {
+ $name = trim($c['Nominativo'] ?? '');
+ $code = trim($c['CodiceCliente'] ?? '');
+ if ($q === '' || mb_strpos(mb_strtolower($name), $q) !== false || mb_strpos(mb_strtolower($code), $q) !== false) {
+ $results[] = ['id' => $c['IdCliente'], 'text' => $name];
+ if (count($results) >= $limit) break;
+ }
+ }
+
+ echo json_encode(['results' => $results]);
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['error' => $e->getMessage()]);
+}
diff --git a/public/userarea/search_customfield_values.php b/public/userarea/search_customfield_values.php
new file mode 100644
index 0000000..65fb77e
--- /dev/null
+++ b/public/userarea/search_customfield_values.php
@@ -0,0 +1,62 @@
+ []]);
+ exit;
+}
+
+try {
+ $cacheDir = __DIR__ . '/cache';
+ $cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
+
+ if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
+ $values = json_decode(file_get_contents($cacheFile), true);
+ } else {
+ $api = VisualLimsApiClient::getInstance();
+ $data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
+ $values = $data['CustomFieldsValues'] ?? [];
+ if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
+ file_put_contents($cacheFile, json_encode($values));
+ }
+
+ // Lookup by ID
+ if ($id !== null) {
+ foreach ($values as $v) {
+ if ((int)($v['IdCustomFieldsValue'] ?? 0) === $id) {
+ echo json_encode(['results' => [['id' => $v['IdCustomFieldsValue'], 'text' => $v['Valore'] ?? '']]]);
+ exit;
+ }
+ }
+ echo json_encode(['results' => []]);
+ exit;
+ }
+
+ // Search by query
+ $results = [];
+ foreach ($values as $v) {
+ $text = $v['Valore'] ?? '';
+ if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
+ $results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
+ if (count($results) >= $limit) break;
+ }
+ }
+
+ // Sort alphabetically
+ usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
+
+ echo json_encode(['results' => $results]);
+} catch (Exception $e) {
+ http_response_code(500);
+ echo json_encode(['error' => $e->getMessage()]);
+}
diff --git a/public/userarea/tolims.php b/public/userarea/tolims.php
index a803a96..2bd16a0 100644
--- a/public/userarea/tolims.php
+++ b/public/userarea/tolims.php
@@ -1007,6 +1007,48 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
padding-right: 18px !important;
appearance: auto;
}
+ /* Select2 inside filter row */
+ .grid-filter-row .select2-container {
+ width: 100% !important;
+ min-width: 0;
+ flex: 1;
+ }
+ .grid-filter-row .select2-container .select2-selection--single {
+ height: 26px !important;
+ min-height: 26px !important;
+ display: flex !important;
+ align-items: center !important;
+ padding: 0 24px 0 6px;
+ font-size: 11px;
+ border: 1px solid #ced4da;
+ border-radius: 3px;
+ position: relative;
+ }
+ .grid-filter-row .select2-container .select2-selection__rendered {
+ line-height: 1 !important;
+ font-size: 11px;
+ padding: 0 !important;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .grid-filter-row .select2-container .select2-selection__arrow {
+ height: 26px !important;
+ position: absolute;
+ right: 2px;
+ top: 0;
+ }
+ .grid-filter-row .filter-wrap {
+ overflow: visible;
+ }
+ /* Hide our clear button when Select2 is active — Select2 has its own allowClear */
+ .filter-wrap:has(.select2-container) .filter-clear-btn {
+ display: none;
+ }
+ .grid-filter-row .grid-cell {
+ overflow: visible !important;
+ }
.filter-wrap {
display: flex;
align-items: center;
@@ -1747,15 +1789,14 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
+