Compare commits
2
Commits
3f3885130a
...
b828d60bb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b828d60bb2 | ||
|
|
8a617ecb22 |
No files matched your search
@@ -1901,12 +1901,14 @@ body {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-suggestion-item:hover {
|
.autocomplete-suggestion-item:hover,
|
||||||
|
.autocomplete-suggestion-item.selected {
|
||||||
background: var(--accent-primary);
|
background: var(--accent-primary);
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-suggestion-item:hover span {
|
.autocomplete-suggestion-item:hover span,
|
||||||
|
.autocomplete-suggestion-item.selected span {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Start the app when DOM is ready
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const RicercaSpudorataView = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async render() {
|
async render() {
|
||||||
|
await App.ensureLookups();
|
||||||
Filters.currentMode = 'spudorata';
|
Filters.currentMode = 'spudorata';
|
||||||
Filters.load();
|
Filters.load();
|
||||||
|
|
||||||
@@ -48,8 +49,6 @@ const RicercaSpudorataView = {
|
|||||||
|
|
||||||
this.renderContainer(container);
|
this.renderContainer(container);
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
|
||||||
App.ensureLookups();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderContainer(container) {
|
renderContainer(container) {
|
||||||
@@ -496,6 +495,9 @@ const RicercaSpudorataView = {
|
|||||||
const batchApply = document.getElementById('batch-apply');
|
const batchApply = document.getElementById('batch-apply');
|
||||||
if (batchApply) batchApply.addEventListener('click', () => this.applyBatch());
|
if (batchApply) batchApply.addEventListener('click', () => this.applyBatch());
|
||||||
|
|
||||||
|
const batchAddGroup = document.getElementById('batch-add-group');
|
||||||
|
if (batchAddGroup) batchAddGroup.addEventListener('click', () => this.addToGroup());
|
||||||
|
|
||||||
const batchMerge = document.getElementById('batch-merge');
|
const batchMerge = document.getElementById('batch-merge');
|
||||||
if (batchMerge) batchMerge.addEventListener('click', () => this.mergeBatch());
|
if (batchMerge) batchMerge.addEventListener('click', () => this.mergeBatch());
|
||||||
|
|
||||||
@@ -538,6 +540,13 @@ const RicercaSpudorataView = {
|
|||||||
});
|
});
|
||||||
} catch (err) { console.error(err); }
|
} 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.addEventListener('focus', () => {
|
||||||
batchQueueSearchInput.value = '';
|
batchQueueSearchInput.value = '';
|
||||||
if (batchQueueIdInput) batchQueueIdInput.value = '';
|
if (batchQueueIdInput) batchQueueIdInput.value = '';
|
||||||
@@ -716,10 +725,10 @@ const RicercaSpudorataView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ticketIds = Array.from(this.selectedIds).map(id => parseInt(id));
|
const ticket_ids = Array.from(this.selectedIds).map(id => parseInt(id, 10));
|
||||||
const res = await App.api('/api/tickets/batch', {
|
const res = await App.api('/api/tickets/batch/update', {
|
||||||
method: 'POST',
|
method: 'PATCH',
|
||||||
body: JSON.stringify({ ticketIds, updates })
|
body: JSON.stringify({ ticket_ids, updates })
|
||||||
});
|
});
|
||||||
|
|
||||||
Toast.success(res.message || 'Ticket aggiornati con successo');
|
Toast.success(res.message || 'Ticket aggiornati con successo');
|
||||||
@@ -745,6 +754,12 @@ const RicercaSpudorataView = {
|
|||||||
t.owner_last = foundUser.last_name;
|
t.owner_last = foundUser.last_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (updates.responsible_user_id) {
|
||||||
|
t.responsible_user_id = updates.responsible_user_id;
|
||||||
|
}
|
||||||
|
if (updates.type_id) {
|
||||||
|
t.type_id = updates.type_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -756,6 +771,77 @@ const RicercaSpudorataView = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async addToGroup() {
|
||||||
|
if (this.selectedIds.size === 0) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const groups = await App.api('/api/groups');
|
||||||
|
if (!groups || groups.length === 0) {
|
||||||
|
Toast.warning('non sono presenti gruppi');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupOptions = groups.map(g => `<option value="${g.id}">${App.escapeHtml(g.nome)}</option>`).join('');
|
||||||
|
const dialogHtml = `
|
||||||
|
<div id="batch-group-modal" style="position: fixed; inset: 0; z-index: 8000; background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center;">
|
||||||
|
<div style="background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); width: 400px; max-width: 90vw; padding: var(--space-lg); box-shadow: var(--shadow-lg);">
|
||||||
|
<h4 style="margin: 0 0 var(--space-md) 0; font-size: 1rem; font-weight: 600; color: var(--text-primary);">Aggiungi a Gruppo</h4>
|
||||||
|
<p style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: var(--space-md);">Seleziona il gruppo a cui aggiungere i <strong>${this.selectedIds.size}</strong> ticket selezionati:</p>
|
||||||
|
<select id="batch-group-select" class="form-select" style="width: 100%; margin-bottom: var(--space-lg);">
|
||||||
|
${groupOptions}
|
||||||
|
</select>
|
||||||
|
<div style="display: flex; gap: var(--space-sm); justify-content: flex-end;">
|
||||||
|
<button class="btn btn-ghost btn-sm" id="batch-group-cancel">Annulla</button>
|
||||||
|
<button class="btn btn-primary btn-sm" id="batch-group-confirm">Aggiungi</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modalContainer = document.createElement('div');
|
||||||
|
modalContainer.innerHTML = dialogHtml;
|
||||||
|
document.body.appendChild(modalContainer);
|
||||||
|
|
||||||
|
const closeModal = () => modalContainer.remove();
|
||||||
|
|
||||||
|
document.getElementById('batch-group-cancel').addEventListener('click', closeModal);
|
||||||
|
document.getElementById('batch-group-confirm').addEventListener('click', async () => {
|
||||||
|
const groupId = document.getElementById('batch-group-select').value;
|
||||||
|
if (!groupId) return;
|
||||||
|
|
||||||
|
const confirmBtn = document.getElementById('batch-group-confirm');
|
||||||
|
confirmBtn.disabled = true;
|
||||||
|
confirmBtn.textContent = 'Aggiunta...';
|
||||||
|
|
||||||
|
let addedCount = 0;
|
||||||
|
let errorsCount = 0;
|
||||||
|
|
||||||
|
for (const ticketId of this.selectedIds) {
|
||||||
|
try {
|
||||||
|
await App.api(`/api/groups/${groupId}/tickets`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ ticket_identifier: ticketId })
|
||||||
|
});
|
||||||
|
addedCount++;
|
||||||
|
} catch (err) {
|
||||||
|
errorsCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
|
||||||
|
if (addedCount > 0) {
|
||||||
|
Toast.success(`${addedCount} ticket aggiunti al gruppo!`);
|
||||||
|
this.cancelBatchSelection();
|
||||||
|
} else {
|
||||||
|
Toast.error('Impossibile aggiungere i ticket al gruppo selezionato.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Toast.error('Errore caricamento gruppi: ' + err.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async mergeBatch() {
|
async mergeBatch() {
|
||||||
if (this.selectedOrder.length < 2) return;
|
if (this.selectedOrder.length < 2) return;
|
||||||
|
|
||||||
|
|||||||
@@ -379,27 +379,31 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userSuggestionsDiv.innerHTML = users.map(u => `
|
userSuggestionsDiv.innerHTML = users.map(u => `
|
||||||
<div class="autocomplete-suggestion-item" data-login="${App.escapeHtml(u.login || '')}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml((u.first_name + ' ' + u.last_name).trim() || u.login || '')}">
|
<div class="autocomplete-suggestion-item" data-id="${App.escapeHtml(u.login || '')}" data-login="${App.escapeHtml(u.login || '')}" data-customer-id="${App.escapeHtml(u.customer_id || '')}" data-name="${App.escapeHtml((u.first_name + ' ' + u.last_name).trim() || u.login || '')}">
|
||||||
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
<strong>${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}</strong>
|
||||||
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login || '')} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
<span style="font-size:0.75rem; color:var(--text-muted); margin-left:var(--space-xs); font-weight:normal;">(Login: ${App.escapeHtml(u.login || '')} | Azienda: ${App.escapeHtml(u.customer_id || '—')})</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
userSuggestionsDiv.style.display = 'block';
|
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
|
// Use mousedown instead of click to fire before blur event
|
||||||
userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
|
||||||
if (item.dataset.login) {
|
if (item.dataset.login) {
|
||||||
item.addEventListener('mousedown', (e) => {
|
item.addEventListener('mousedown', (e) => {
|
||||||
e.preventDefault(); // prevent input from losing focus before value is set
|
e.preventDefault(); // prevent input from losing focus before value is set
|
||||||
userSearchInput.value = item.dataset.name;
|
selectUserItem(item);
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -408,6 +412,17 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 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', () => {
|
userSearchInput.addEventListener('focus', () => {
|
||||||
if (!customerUserIdInput.value) {
|
if (!customerUserIdInput.value) {
|
||||||
// Only search if no user is selected yet
|
// Only search if no user is selected yet
|
||||||
@@ -462,6 +477,13 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 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.addEventListener('focus', () => {
|
||||||
ownerSearchInput.value = '';
|
ownerSearchInput.value = '';
|
||||||
ownerIdInput.value = '';
|
ownerIdInput.value = '';
|
||||||
@@ -510,6 +532,13 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 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.addEventListener('focus', () => {
|
||||||
responsibleSearchInput.value = '';
|
responsibleSearchInput.value = '';
|
||||||
responsibleIdInput.value = '';
|
responsibleIdInput.value = '';
|
||||||
@@ -557,6 +586,13 @@ const TicketCreateView = {
|
|||||||
}
|
}
|
||||||
}, 150);
|
}, 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.addEventListener('focus', () => {
|
||||||
queueSearchInput.value = '';
|
queueSearchInput.value = '';
|
||||||
queueIdInput.value = '';
|
queueIdInput.value = '';
|
||||||
|
|||||||
@@ -694,6 +694,15 @@ const TicketDetailView = {
|
|||||||
}, 150);
|
}, 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.addEventListener('focus', () => {
|
||||||
queueSearchInput.value = '';
|
queueSearchInput.value = '';
|
||||||
queueIdInput.value = '';
|
queueIdInput.value = '';
|
||||||
|
|||||||
@@ -735,6 +735,13 @@ const TicketListView = {
|
|||||||
}
|
}
|
||||||
}, 300);
|
}, 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.addEventListener('focus', () => {
|
||||||
batchQueueSearchInput.value = '';
|
batchQueueSearchInput.value = '';
|
||||||
if (batchQueueIdInput) batchQueueIdInput.value = '';
|
if (batchQueueIdInput) batchQueueIdInput.value = '';
|
||||||
|
|||||||
Reference in new issue
Block a user