From b828d60bb2978f73171b5eeefc55d35f66b5b699 Mon Sep 17 00:00:00 2001 From: Gabriele Cimaschi Date: Mon, 31 Aug 2026 16:03:05 +0200 Subject: [PATCH] feat: ora i menu a tendina sono freccettabili. fix: sistemata la modifica massiva nella pagina ricerca spudorata --- public/css/style.css | 6 ++- public/js/app.js | 61 +++++++++++++++++++++++++++++ public/js/views/ricercaSpudorata.js | 10 ++++- public/js/views/ticketCreate.js | 56 +++++++++++++++++++++----- public/js/views/ticketDetail.js | 9 +++++ public/js/views/ticketList.js | 7 ++++ 6 files changed, 135 insertions(+), 14 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index c847a7d..744e78e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1901,12 +1901,14 @@ body { white-space: nowrap; } -.autocomplete-suggestion-item:hover { +.autocomplete-suggestion-item:hover, +.autocomplete-suggestion-item.selected { background: var(--accent-primary); color: #ffffff !important; } -.autocomplete-suggestion-item:hover span { +.autocomplete-suggestion-item:hover span, +.autocomplete-suggestion-item.selected span { color: #ffffff !important; } diff --git a/public/js/app.js b/public/js/app.js index f46364d..79281df 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1080,6 +1080,67 @@ const App = { }); }); }, + + /** + * Helper to enable arrow keys (Up/Down), Enter, and Escape navigation on autocomplete inputs. + * @param {HTMLInputElement} inputEl + * @param {HTMLElement} suggestionsEl + * @param {Function} onSelect Callback(itemElement) when an item is selected via Enter or Click + */ + setupAutocompleteKeyboardNavigation(inputEl, suggestionsEl, onSelect) { + if (!inputEl || !suggestionsEl) return; + + inputEl.addEventListener('keydown', (e) => { + if (suggestionsEl.style.display === 'none' || !suggestionsEl.children.length) { + return; + } + + const items = Array.from(suggestionsEl.querySelectorAll('.autocomplete-suggestion-item[data-id]')); + if (items.length === 0) return; + + let currentIndex = items.findIndex(item => item.classList.contains('selected')); + + if (e.key === 'ArrowDown') { + e.preventDefault(); + if (currentIndex < items.length - 1) { + currentIndex++; + } else { + currentIndex = 0; + } + items.forEach((item, idx) => { + if (idx === currentIndex) { + item.classList.add('selected'); + item.scrollIntoView({ block: 'nearest' }); + } else { + item.classList.remove('selected'); + } + }); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + if (currentIndex > 0) { + currentIndex--; + } else { + currentIndex = items.length - 1; + } + items.forEach((item, idx) => { + if (idx === currentIndex) { + item.classList.add('selected'); + item.scrollIntoView({ block: 'nearest' }); + } else { + item.classList.remove('selected'); + } + }); + } else if (e.key === 'Enter') { + if (currentIndex >= 0 && items[currentIndex]) { + e.preventDefault(); + e.stopPropagation(); + onSelect(items[currentIndex]); + } + } else if (e.key === 'Escape') { + suggestionsEl.style.display = 'none'; + } + }); + }, }; // Start the app when DOM is ready diff --git a/public/js/views/ricercaSpudorata.js b/public/js/views/ricercaSpudorata.js index 6a7417b..3fdf668 100644 --- a/public/js/views/ricercaSpudorata.js +++ b/public/js/views/ricercaSpudorata.js @@ -20,6 +20,7 @@ const RicercaSpudorataView = { }, async render() { + await App.ensureLookups(); Filters.currentMode = 'spudorata'; Filters.load(); @@ -48,8 +49,6 @@ const RicercaSpudorataView = { this.renderContainer(container); this.bindEvents(); - - App.ensureLookups(); }, renderContainer(container) { @@ -541,6 +540,13 @@ const RicercaSpudorataView = { }); } catch (err) { console.error(err); } }); + App.setupAutocompleteKeyboardNavigation(batchQueueSearchInput, batchQueueSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + batchQueueSearchInput.value = item.dataset.name; + if (batchQueueIdInput) batchQueueIdInput.value = item.dataset.id; + batchQueueSuggestionsDiv.style.display = 'none'; + } + }); batchQueueSearchInput.addEventListener('focus', () => { batchQueueSearchInput.value = ''; if (batchQueueIdInput) batchQueueIdInput.value = ''; diff --git a/public/js/views/ticketCreate.js b/public/js/views/ticketCreate.js index 410efff..af59c25 100644 --- a/public/js/views/ticketCreate.js +++ b/public/js/views/ticketCreate.js @@ -379,27 +379,31 @@ const TicketCreateView = { } userSuggestionsDiv.innerHTML = users.map(u => ` -
+
${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)} (Login: ${App.escapeHtml(u.login || '')} | Azienda: ${App.escapeHtml(u.customer_id || '—')})
`).join(''); userSuggestionsDiv.style.display = 'block'; + const selectUserItem = (item) => { + userSearchInput.value = item.dataset.name; + customerUserIdInput.value = item.dataset.login; + userSuggestionsDiv.style.display = 'none'; + + // Auto-fill company inside Advanced Options + if (item.dataset.customerId) { + customerIdInput.value = item.dataset.customerId; + companySearchInput.value = item.dataset.customerId; + } + }; + // Use mousedown instead of click to fire before blur event userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => { if (item.dataset.login) { item.addEventListener('mousedown', (e) => { e.preventDefault(); // prevent input from losing focus before value is set - userSearchInput.value = item.dataset.name; - customerUserIdInput.value = item.dataset.login; - userSuggestionsDiv.style.display = 'none'; - - // Auto-fill company inside Advanced Options - if (item.dataset.customerId) { - customerIdInput.value = item.dataset.customerId; - companySearchInput.value = item.dataset.customerId; - } + selectUserItem(item); }); } }); @@ -408,6 +412,17 @@ const TicketCreateView = { } }, 300); }); + App.setupAutocompleteKeyboardNavigation(userSearchInput, userSuggestionsDiv, (item) => { + if (item && item.dataset.login) { + userSearchInput.value = item.dataset.name; + customerUserIdInput.value = item.dataset.login; + userSuggestionsDiv.style.display = 'none'; + if (item.dataset.customerId) { + customerIdInput.value = item.dataset.customerId; + companySearchInput.value = item.dataset.customerId; + } + } + }); userSearchInput.addEventListener('focus', () => { if (!customerUserIdInput.value) { // Only search if no user is selected yet @@ -462,6 +477,13 @@ const TicketCreateView = { } }, 300); }); + App.setupAutocompleteKeyboardNavigation(ownerSearchInput, ownerSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + ownerSearchInput.value = item.dataset.name; + ownerIdInput.value = item.dataset.id; + ownerSuggestionsDiv.style.display = 'none'; + } + }); ownerSearchInput.addEventListener('focus', () => { ownerSearchInput.value = ''; ownerIdInput.value = ''; @@ -510,6 +532,13 @@ const TicketCreateView = { } }, 300); }); + App.setupAutocompleteKeyboardNavigation(responsibleSearchInput, responsibleSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + responsibleSearchInput.value = item.dataset.name; + responsibleIdInput.value = item.dataset.id; + responsibleSuggestionsDiv.style.display = 'none'; + } + }); responsibleSearchInput.addEventListener('focus', () => { responsibleSearchInput.value = ''; responsibleIdInput.value = ''; @@ -557,6 +586,13 @@ const TicketCreateView = { } }, 150); }); + App.setupAutocompleteKeyboardNavigation(queueSearchInput, queueSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + queueSearchInput.value = item.dataset.name; + queueIdInput.value = item.dataset.id; + queueSuggestionsDiv.style.display = 'none'; + } + }); queueSearchInput.addEventListener('focus', () => { queueSearchInput.value = ''; queueIdInput.value = ''; diff --git a/public/js/views/ticketDetail.js b/public/js/views/ticketDetail.js index 3908850..5fcbc2f 100644 --- a/public/js/views/ticketDetail.js +++ b/public/js/views/ticketDetail.js @@ -694,6 +694,15 @@ const TicketDetailView = { }, 150); }); + App.setupAutocompleteKeyboardNavigation(queueSearchInput, queueSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + queueSearchInput.value = item.dataset.name; + queueIdInput.value = item.dataset.id; + queueSuggestionsDiv.style.display = 'none'; + queueIdInput.dispatchEvent(new Event('change')); + } + }); + queueSearchInput.addEventListener('focus', () => { queueSearchInput.value = ''; queueIdInput.value = ''; diff --git a/public/js/views/ticketList.js b/public/js/views/ticketList.js index 39cc976..f495169 100644 --- a/public/js/views/ticketList.js +++ b/public/js/views/ticketList.js @@ -735,6 +735,13 @@ const TicketListView = { } }, 300); }); + App.setupAutocompleteKeyboardNavigation(batchQueueSearchInput, batchQueueSuggestionsDiv, (item) => { + if (item && item.dataset.id) { + batchQueueSearchInput.value = item.dataset.name; + if (batchQueueIdInput) batchQueueIdInput.value = item.dataset.id; + batchQueueSuggestionsDiv.style.display = 'none'; + } + }); batchQueueSearchInput.addEventListener('focus', () => { batchQueueSearchInput.value = ''; if (batchQueueIdInput) batchQueueIdInput.value = '';