-
-
-
📎 Allega file
+
+
-
-
-
-
-
- ${emailBtnText}
-
-
-
- Invia Nota
-
+
+
+
+
+
+
+
+
+
+ 📎 Allega file
+
+
+
Data/Ora:
+
+
+
Tempo (min):
+
+
+
+
+ ${emailBtnText}
+
+
Reset
+
+ 💾 Salva Modifiche / Invia Nota
+
+
@@ -2789,9 +2773,14 @@ const TicketDetailView = {
const fields = document.querySelectorAll('.quick-edit-select:not(#qe-queue-search), #qe-queue, #qe-customer-user-id, #qe-customer-id');
const saveBtn = document.getElementById('qe-save');
const resetBtn = document.getElementById('qe-reset');
- const timeUnitInput = document.getElementById('qe-time-unit');
- const noteSubjectInput = document.getElementById('qe-note-subject');
- const noteInput = document.getElementById('qe-note');
+ const queueSearchInput = document.getElementById('qe-queue-search');
+ const queueIdInput = document.getElementById('qe-queue');
+ const customerSearchInput = document.getElementById('qe-customer-search');
+ const customerUserIdInput = document.getElementById('qe-customer-user-id');
+ const customerIdInput = document.getElementById('qe-customer-id');
+ const noteSubjectInput = document.getElementById('note-subject');
+ const noteTimeUnitsInput = document.getElementById('note-time-units');
+ const noteCreateTimeInput = document.getElementById('note-create-time');
const checkChanges = () => {
let hasChanges = false;
@@ -2801,374 +2790,180 @@ const TicketDetailView = {
const current = el.value;
const changed = current !== original;
if (el.id === 'qe-queue') {
- const searchInput = document.getElementById('qe-queue-search');
- if (searchInput) searchInput.classList.toggle('changed', changed);
+ if (queueSearchInput) queueSearchInput.classList.toggle('changed', changed);
} else {
el.classList.toggle('changed', changed);
}
if (changed) hasChanges = true;
});
- const timeVal = parseFloat(timeUnitInput ? timeUnitInput.value : '0') || 0;
- if (timeVal > 0) {
- hasChanges = true;
- }
-
const noteSubText = noteSubjectInput ? noteSubjectInput.value.trim() : '';
- if (noteSubjectInput) {
- noteSubjectInput.classList.toggle('changed', noteSubText.length > 0);
- }
- if (noteSubText.length > 0) {
- hasChanges = true;
- }
+ if (noteSubText.length > 0) hasChanges = true;
- const noteText = noteInput ? noteInput.value.trim() : '';
- if (noteInput) {
- noteInput.classList.toggle('changed', noteText.length > 0);
- }
- if (noteText.length > 0) {
- hasChanges = true;
- }
+ const noteBodyText = (this.noteQuill ? this.noteQuill.root.innerText : '').trim();
+ if (noteBodyText.length > 0) hasChanges = true;
- saveBtn.disabled = !hasChanges;
+ const noteTimeUnitsVal = parseFloat(noteTimeUnitsInput ? noteTimeUnitsInput.value : '0') || 0;
+ if (noteTimeUnitsVal > 0) hasChanges = true;
+
+ if (noteCreateTimeInput && noteCreateTimeInput.value) hasChanges = true;
+
+ if (this.noteAttachments && this.noteAttachments.length > 0) hasChanges = true;
+
+ if (saveBtn) saveBtn.disabled = !hasChanges;
};
fields.forEach(el => {
el.addEventListener('change', checkChanges);
});
- if (timeUnitInput) {
- timeUnitInput.addEventListener('input', checkChanges);
- timeUnitInput.addEventListener('change', checkChanges);
- }
+
if (noteSubjectInput) {
noteSubjectInput.addEventListener('input', checkChanges);
- noteSubjectInput.addEventListener('change', checkChanges);
}
- if (noteInput) {
- noteInput.addEventListener('input', checkChanges);
- noteInput.addEventListener('change', checkChanges);
+ if (noteTimeUnitsInput) {
+ noteTimeUnitsInput.addEventListener('input', checkChanges);
+ }
+ if (noteCreateTimeInput) {
+ noteCreateTimeInput.addEventListener('change', checkChanges);
+ }
+ if (this.noteQuill) {
+ this.noteQuill.on('text-change', checkChanges);
}
- // Queue Autocomplete inside Quick Edit
- const queueSearchInput = document.getElementById('qe-queue-search');
- const queueSuggestionsDiv = document.getElementById('qe-queue-suggestions');
- const queueIdInput = document.getElementById('qe-queue');
-
- let queueDebounce;
- if (queueSearchInput) {
- queueSearchInput.addEventListener('input', () => {
- clearTimeout(queueDebounce);
- const q = queueSearchInput.value.trim();
-
- queueDebounce = setTimeout(async () => {
- try {
- const queues = await App.api(`/api/queues/search?q=${encodeURIComponent(q)}`);
- if (queueSearchInput.value.trim() !== q) {
- return;
- }
- if (queues.length === 0) {
- queueSuggestionsDiv.innerHTML = '
Nessuna coda trovata
';
- queueSuggestionsDiv.style.display = 'block';
- return;
- }
-
- queueSuggestionsDiv.innerHTML = queues.map(q => {
- const displayName = q.name.replace(/::/g, ' › ');
- return `
-
- ${App.escapeHtml(displayName)}
-
- `;
- }).join('');
- queueSuggestionsDiv.style.display = 'block';
-
- // Bind click/mousedown
- queueSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
- if (item.dataset.id) {
- item.addEventListener('mousedown', (e) => {
- e.preventDefault();
- queueSearchInput.value = item.dataset.name;
- queueIdInput.value = item.dataset.id;
- queueSuggestionsDiv.style.display = 'none';
- queueIdInput.dispatchEvent(new Event('change'));
- });
- }
- });
- } catch (err) {
- console.error(err);
- }
- }, 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'));
+ // Reset quick-edit & note form
+ if (resetBtn) {
+ resetBtn.addEventListener('click', () => {
+ fields.forEach(el => {
+ el.value = this.originalValues[el.dataset.field] || '';
+ el.classList.remove('changed');
+ });
+ if (queueSearchInput) {
+ queueSearchInput.classList.remove('changed');
+ queueSearchInput.value = this.originalValues['queue_name'] || '';
}
- });
-
- queueSearchInput.addEventListener('focus', () => {
- queueSearchInput.value = '';
- queueIdInput.value = '';
- queueSearchInput.dispatchEvent(new Event('input'));
- });
-
- queueSearchInput.addEventListener('blur', () => {
- setTimeout(() => { queueSuggestionsDiv.style.display = 'none'; }, 150);
- });
- }
-
- // Customer User Autocomplete inside Quick Edit
- const customerSearchInput = document.getElementById('qe-customer-search');
- const customerSuggestionsDiv = document.getElementById('qe-customer-suggestions');
- const customerUserIdInput = document.getElementById('qe-customer-user-id');
- const customerIdInput = document.getElementById('qe-customer-id');
-
- let customerDebounce;
- if (customerSearchInput) {
- customerSearchInput.addEventListener('input', () => {
- clearTimeout(customerDebounce);
- const q = customerSearchInput.value.trim();
-
- customerDebounce = setTimeout(async () => {
- try {
- const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
- console.log('[Frontend LDAP Search Detail] Users returned:', users);
- // Prevent race conditions: discard results if the input value has changed
- if (customerSearchInput.value.trim() !== q) {
- console.log('[Frontend LDAP Search Detail] Discarding stale results for query:', q);
- return;
- }
- if (users.length === 0) {
- customerSuggestionsDiv.innerHTML = '
Nessun utente trovato
';
- customerSuggestionsDiv.style.display = 'block';
- return;
- }
-
- customerSuggestionsDiv.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('');
- customerSuggestionsDiv.style.display = 'block';
-
- // Use mousedown instead of click to fire before blur event
- customerSuggestionsDiv.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
- customerSearchInput.value = item.dataset.name;
- customerUserIdInput.value = item.dataset.login;
- customerIdInput.value = item.dataset.customerId || '';
- customerSuggestionsDiv.style.display = 'none';
- customerUserIdInput.dispatchEvent(new Event('change'));
- customerIdInput.dispatchEvent(new Event('change'));
- });
- }
- });
- } catch (err) {
- console.error(err);
- }
- }, 300);
- });
- customerSearchInput.addEventListener('focus', () => {
- if (!customerUserIdInput.value) {
- customerSearchInput.dispatchEvent(new Event('input'));
+ if (customerSearchInput) {
+ const first = this.originalValues['customer_first'] || '';
+ const last = this.originalValues['customer_last'] || '';
+ customerSearchInput.value = first ? `${first} ${last}` : (this.originalValues['customer_user_id'] || '');
}
- });
- customerSearchInput.addEventListener('blur', () => {
- setTimeout(() => { customerSuggestionsDiv.style.display = 'none'; }, 150);
+ if (noteSubjectInput) noteSubjectInput.value = '';
+ if (this.noteQuill) this.noteQuill.root.innerHTML = '';
+ if (noteTimeUnitsInput) noteTimeUnitsInput.value = '';
+ if (noteCreateTimeInput) noteCreateTimeInput.value = '';
+ this.noteAttachments = [];
+ this.updateNoteAttachmentList();
+ App.clearDraft(this.ticketId, 'note');
+ if (saveBtn) saveBtn.disabled = true;
});
}
- // Reset quick-edit
- resetBtn.addEventListener('click', () => {
- fields.forEach(el => {
- el.value = this.originalValues[el.dataset.field] || '';
- el.classList.remove('changed');
- });
- if (queueSearchInput) {
- queueSearchInput.classList.remove('changed');
- queueSearchInput.value = this.originalValues['queue_name'] || '';
- }
- if (customerSearchInput) {
- const first = this.originalValues['customer_first'] || '';
- const last = this.originalValues['customer_last'] || '';
- customerSearchInput.value = first ? `${first} ${last}` : (this.originalValues['customer_user_id'] || '');
- }
- if (timeUnitInput) {
- timeUnitInput.value = '';
- }
- if (noteSubjectInput) {
- noteSubjectInput.value = '';
- noteSubjectInput.classList.remove('changed');
- }
- if (noteInput) {
- noteInput.value = '';
- noteInput.classList.remove('changed');
- }
- saveBtn.disabled = true;
- });
+ // Unified Save Quick Edit & Send Note action
+ if (saveBtn) {
+ saveBtn.addEventListener('click', async () => {
+ const queueSearch = queueSearchInput ? queueSearchInput.value.trim() : '';
+ if (queueSearchInput && !queueIdInput.value) {
+ Toast.warning('Seleziona una coda valida dall\'elenco.');
+ return;
+ }
- // Save quick-edit
- saveBtn.addEventListener('click', async () => {
- const queueSearch = queueSearchInput ? queueSearchInput.value.trim() : '';
- if (queueSearchInput && !queueIdInput.value) {
- Toast.warning('Seleziona una coda valida dall\'elenco.');
+ const customerSearch = customerSearchInput ? customerSearchInput.value.trim() : '';
+ if (customerSearch && customerUserIdInput && !customerUserIdInput.value) {
+ customerUserIdInput.value = customerSearch;
+ if (customerIdInput) customerIdInput.value = customerSearch;
+ }
+
+ // Check metadata updates
+ const updates = {};
+ fields.forEach(el => {
+ const field = el.dataset.field;
+ let val;
+ if (field === 'customer_id' || field === 'customer_user_id') {
+ val = el.value || null;
+ } else {
+ val = el.value ? parseInt(el.value) : null;
+ }
+
+ const origVal = this.originalValues[field] || null;
+ if (val !== origVal) {
+ updates[field] = val;
+ }
+ });
+
+ // Check note content
+ let noteBody = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : '';
+ if (noteBody === '
') noteBody = '';
+ let noteSubject = noteSubjectInput ? noteSubjectInput.value.trim() : '';
+ const timeUnitVal = noteTimeUnitsInput ? noteTimeUnitsInput.value.trim() : '';
+ const noteCreateTimeVal = noteCreateTimeInput ? noteCreateTimeInput.value : '';
+
+ const hasMetadataChanges = Object.keys(updates).length > 0;
+ const hasNoteContent = !!(noteBody || noteSubject || timeUnitVal || this.noteAttachments.length > 0 || noteCreateTimeVal);
+
+ if (!hasMetadataChanges && !hasNoteContent) {
+ Toast.warning('Nessuna modifica o nota da salvare');
return;
}
- const customerSearch = customerSearchInput ? customerSearchInput.value.trim() : '';
- if (customerSearch && !customerUserIdInput.value) {
- customerUserIdInput.value = customerSearch;
- customerIdInput.value = customerSearch;
- }
-
- const updates = {};
- fields.forEach(el => {
- const field = el.dataset.field;
- let val;
- if (field === 'customer_id' || field === 'customer_user_id') {
- val = el.value || null;
- } else {
- val = el.value ? parseInt(el.value) : null;
- }
-
- const origVal = this.originalValues[field] || null;
- if (val !== origVal) {
- updates[field] = val;
- }
- });
-
- if (timeUnitInput && timeUnitInput.value.trim()) {
- const timeVal = parseFloat(timeUnitInput.value.trim());
- if (!isNaN(timeVal) && timeVal > 0) {
- updates.time_unit = timeVal;
- }
- }
-
- if (noteSubjectInput && noteSubjectInput.value.trim()) {
- updates.note_subject = noteSubjectInput.value.trim();
- }
-
- if (noteInput && noteInput.value.trim()) {
- updates.note = noteInput.value.trim();
- }
-
- if (Object.keys(updates).length === 0) return;
-
try {
saveBtn.disabled = true;
- saveBtn.textContent = 'Salvando...';
- const res = await App.api(`/api/tickets/${this.ticketId}`, {
- method: 'PATCH',
- body: JSON.stringify(updates),
- });
- Toast.success(res.message || 'Ticket aggiornato!');
- TicketListView.invalidateCache();
- // Refresh the view
- this.render(this.ticketId);
- } catch (err) {
- Toast.error('Errore: ' + err.message);
- saveBtn.disabled = false;
- saveBtn.textContent = 'Salva Modifiche';
- }
- });
+ saveBtn.innerHTML = '
Salvataggio...';
- // Shortcut for Quick Edit save
- const quickEditContainer = container.querySelector('.quick-edit');
- if (quickEditContainer) {
- quickEditContainer.addEventListener('keydown', (e) => {
- if (App.isShortcutTriggered(e, 'quick_edit_save')) {
- e.preventDefault();
- if (saveBtn && !saveBtn.disabled) {
- saveBtn.click();
+ // 1. Perform metadata updates if changed
+ if (hasMetadataChanges) {
+ await App.api(`/api/tickets/${this.ticketId}`, {
+ method: 'PATCH',
+ body: JSON.stringify(updates),
+ });
+ }
+
+ // 2. Perform article note creation if note fields are populated
+ if (hasNoteContent) {
+ if (!noteBody && noteSubject) {
+ noteBody = `
${App.escapeHtml(noteSubject)}
`;
+ }
+ if (noteBody && !noteSubject) {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = noteBody;
+ const plainText = tempDiv.textContent || tempDiv.innerText || '';
+ noteSubject = plainText.trim().substring(0, 50) || 'Nota interna';
+ }
+
+ const resNote = await App.api(`/api/tickets/${this.ticketId}/articles`, {
+ method: 'POST',
+ body: JSON.stringify({
+ subject: noteSubject,
+ body: noteBody,
+ time_unit: timeUnitVal,
+ attachments: this.noteAttachments.length > 0 ? this.noteAttachments : undefined
+ }),
+ });
+
+ this.noteAttachments = []; // Clear attachments after successful send
+
+ if (noteCreateTimeVal && resNote.article_id) {
+ try {
+ const formattedDate = noteCreateTimeVal.replace('T', ' ');
+ await App.api(`/api/tickets/articles/${resNote.article_id}/retrodata-article`, {
+ method: 'POST',
+ body: JSON.stringify({ create_time: formattedDate })
+ });
+ } catch (retErr) {
+ console.warn('Note retrodate error:', retErr);
+ }
}
}
- });
- }
-
- // Send note
- const noteSendBtn = document.getElementById('note-send');
- noteSendBtn.addEventListener('click', async () => {
- let body = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : '';
- if (body === '
') {
- body = '';
- }
- let subject = document.getElementById('note-subject').value.trim();
- const time_unit = document.getElementById('note-time-units').value.trim();
- const note_create_time = document.getElementById('note-create-time')?.value;
-
- // If body is empty but subject is provided, fill body with subject text
- if (!body && subject) {
- body = `
${App.escapeHtml(subject)}
`;
- if (this.noteQuill) {
- this.noteQuill.root.innerHTML = body;
- }
- }
-
- // If subject is empty but body is provided, fill subject with truncated plain text of body
- if (body && !subject) {
- const tempDiv = document.createElement('div');
- tempDiv.innerHTML = body;
- const plainText = tempDiv.textContent || tempDiv.innerText || '';
- subject = plainText.trim().substring(0, 50) || 'Nota interna';
- document.getElementById('note-subject').value = subject;
- }
-
- // If both are still empty, show warning
- if (!body && !subject) {
- Toast.warning('Inserisci un oggetto o il corpo della nota prima di inviare');
- return;
- }
-
- const noteStateSelect = document.getElementById('note-ticket-state');
- const ticket_state_id = noteStateSelect ? noteStateSelect.value : null;
-
- try {
- noteSendBtn.disabled = true;
- noteSendBtn.innerHTML = '
Invio...';
-
- const res = await App.api(`/api/tickets/${this.ticketId}/articles`, {
- method: 'POST',
- body: JSON.stringify({
- subject,
- body,
- time_unit,
- ticket_state_id: ticket_state_id ? parseInt(ticket_state_id) : undefined,
- attachments: this.noteAttachments.length > 0 ? this.noteAttachments : undefined
- }),
- });
-
- this.noteAttachments = []; // Clear attachments
-
- if (note_create_time && res.article_id) {
- try {
- const formattedDate = note_create_time.replace('T', ' ');
- await App.api(`/api/tickets/articles/${res.article_id}/retrodata-article`, {
- method: 'POST',
- body: JSON.stringify({ create_time: formattedDate })
- });
- Toast.success('Nota aggiunta e retrodatata!');
- } catch (retErr) {
- Toast.warning('Nota aggiunta, ma errore durante la retrodatazione: ' + retErr.message);
- }
- } else {
- Toast.success(res.message || 'Nota aggiunta!');
- }
+ Toast.success('Modifiche e note salvate con successo!');
App.clearDraft(this.ticketId, 'note');
App.updateDailyTimer();
+ TicketListView.invalidateCache();
this.render(this.ticketId);
} catch (err) {
- Toast.error('Errore: ' + err.message);
- noteSendBtn.disabled = false;
- noteSendBtn.innerHTML = 'Invia Nota';
+ Toast.error('Errore durante il salvataggio: ' + err.message);
+ saveBtn.disabled = false;
}
});
+ }
// Note Attachments Upload Handlers
const noteAttachBtn = document.getElementById('btn-note-add-attachments');
@@ -3630,5 +3425,22 @@ const TicketDetailView = {
}
});
});
+
+ // Keydown listener for Ticket Detail shortcut (Save Quick Edit / Send Note)
+ const handleTicketDetailShortcut = (e) => {
+ if (!document.getElementById('qe-save')) {
+ document.removeEventListener('keydown', handleTicketDetailShortcut, true);
+ return;
+ }
+ if (App.isShortcutTriggered(e, 'quick_edit_save')) {
+ e.preventDefault();
+ e.stopPropagation();
+ const btn = document.getElementById('qe-save');
+ if (btn && !btn.disabled) {
+ btn.click();
+ }
+ }
+ };
+ document.addEventListener('keydown', handleTicketDetailShortcut, true);
},
};