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 = '';