/** * Ticket Create View * Minimal, fast form for creating new tickets. */ const TicketCreateView = { savedState: null, attachments: [], updateAttachmentList() { const listEl = document.getElementById('create-file-list'); if (!listEl) return; listEl.innerHTML = this.attachments.map((att, idx) => `
📎 ${App.escapeHtml(att.filename)} (${Math.round(att.content.length * 0.75 / 1024)} KB)
`).join(''); // Bind remove clicks listEl.querySelectorAll('.upload-file-remove').forEach(btn => { btn.addEventListener('click', (e) => { const idx = parseInt(btn.dataset.index, 10); this.attachments.splice(idx, 1); this.updateAttachmentList(); }); }); }, saveState() { const stateEl = document.getElementById('create-state'); if (!stateEl) return; this.savedState = { state_id: stateEl.value, title: document.getElementById('create-title').value, queue_name: document.getElementById('create-queue-search').value, queue_id: document.getElementById('create-queue').value, type_id: document.getElementById('create-type').value, owner_name: document.getElementById('create-owner-search').value, owner_id: document.getElementById('create-owner').value, responsible_name: document.getElementById('create-responsible-search').value, responsible_id: document.getElementById('create-responsible').value, user_search: document.getElementById('create-user-search').value, customer_user_id: document.getElementById('create-customer-user-id').value, customer_id: document.getElementById('create-customer-id').value, company_search: document.getElementById('create-company-search').value, priority_id: document.getElementById('create-priority').value, subject: document.getElementById('create-subject').value, body: this.quill ? this.quill.root.innerHTML : '', isAdvancedVisible: document.getElementById('advanced-options').style.display !== 'none' }; }, async render() { const container = document.getElementById('view-container'); container.innerHTML = '

Caricamento form...

'; try { await App.ensureLookups(); // Check if we have incoming query parameters (e.g. from Teams integration) const hashParts = window.location.hash.split('?'); const searchStr = hashParts.length > 1 ? hashParts[1] : window.location.search; const urlParams = new URLSearchParams(searchStr); const incomingBody = urlParams.get('body'); const incomingCustomer = urlParams.get('customer'); const incomingSubject = urlParams.get('subject'); if (incomingBody || incomingCustomer || incomingSubject) { let foundCustomer = null; if (incomingCustomer) { try { const results = await App.api(`/api/customer-users/search?q=${encodeURIComponent(incomingCustomer)}`); if (results && results.length > 0) { foundCustomer = results[0]; } } catch (e) { console.warn('Failed to search customer for Teams integration:', e); } } // Prepopulate default active agent details const currentAgentId = localStorage.getItem('activeAgentId') || '1'; const activeAgent = (App.lookups.users || []).find(u => String(u.id) === String(currentAgentId)); const agentName = activeAgent ? `${activeAgent.first_name} ${activeAgent.last_name}` : ''; const agentId = activeAgent ? activeAgent.id : ''; this.savedState = { state_id: '1', // default state title: 'Segnalazione da Teams', queue_name: App.lookups.queues && App.lookups.queues.length > 0 ? App.lookups.queues[0].name : '', queue_id: App.lookups.queues && App.lookups.queues.length > 0 ? App.lookups.queues[0].id : '', type_id: '', owner_name: agentName, owner_id: agentId, responsible_name: agentName, responsible_id: agentId, user_search: foundCustomer ? `${foundCustomer.first_name} ${foundCustomer.last_name} <${foundCustomer.email}>` : (incomingCustomer || ''), customer_user_id: foundCustomer ? foundCustomer.login : (incomingCustomer || ''), customer_id: foundCustomer ? foundCustomer.customer_id : '', company_search: foundCustomer ? foundCustomer.customer_id : '', priority_id: '3', subject: incomingSubject || 'Messaggio da Microsoft Teams', body: incomingBody || '', isAdvancedVisible: false }; // Clean query parameters from URL to prevent duplicate inserts on reload window.history.replaceState({}, document.title, window.location.pathname + window.location.hash.split('?')[0]); } container.innerHTML = ` Torna indietro
Crea Nuovo Ticket
`; // Initialize Quill Editor if (window.Quill) { this.quill = new Quill('#create-body-editor', { theme: 'snow', placeholder: 'Descrivi il problema in dettaglio...', modules: { toolbar: [ ['bold', 'italic', 'underline', 'strike'], [{ 'header': [1, 2, 3, false] }], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['link', 'image'], ['clean'] ], keyboard: { bindings: { tab: { key: 'Tab', handler: function() { return true; } } } } } }); // Set saved body/state if available if (this.savedState && this.savedState.body) { this.quill.root.innerHTML = this.savedState.body; } // Prevent tab navigation on toolbar items const toolbar = container.querySelector('.ql-toolbar'); if (toolbar) { toolbar.querySelectorAll('button, select, span[role="button"], input').forEach(el => { el.setAttribute('tabindex', '-1'); }); } } else { this.quill = null; } this.bindEvents(); } catch (err) { container.innerHTML = `
⚠️
Errore caricamento form
${App.escapeHtml(err.message)}
`; } }, bindEvents() { const submitBtn = document.getElementById('create-submit'); const companySearchInput = document.getElementById('create-company-search'); const customerIdInput = document.getElementById('create-customer-id'); const userSearchInput = document.getElementById('create-user-search'); const userSuggestionsDiv = document.getElementById('user-suggestions'); const customerUserIdInput = document.getElementById('create-customer-user-id'); const ownerSearchInput = document.getElementById('create-owner-search'); const ownerSuggestionsDiv = document.getElementById('owner-suggestions'); const ownerIdInput = document.getElementById('create-owner'); const responsibleSearchInput = document.getElementById('create-responsible-search'); const responsibleSuggestionsDiv = document.getElementById('responsible-suggestions'); const responsibleIdInput = document.getElementById('create-responsible'); const queueSearchInput = document.getElementById('create-queue-search'); const queueSuggestionsDiv = document.getElementById('queue-suggestions'); const queueIdInput = document.getElementById('create-queue'); const stateIdInput = document.getElementById('create-state'); const toggleBtn = document.getElementById('toggle-advanced'); const advancedOptions = document.getElementById('advanced-options'); const arrow = document.getElementById('advanced-arrow'); // Restore saved state if exists, otherwise load defaults asynchronously if (this.savedState) { document.getElementById('create-state').value = this.savedState.state_id; document.getElementById('create-title').value = this.savedState.title; document.getElementById('create-queue-search').value = this.savedState.queue_name; document.getElementById('create-queue').value = this.savedState.queue_id; if (document.getElementById('create-type')) { document.getElementById('create-type').value = this.savedState.type_id; } document.getElementById('create-owner-search').value = this.savedState.owner_name; document.getElementById('create-owner').value = this.savedState.owner_id; document.getElementById('create-responsible-search').value = this.savedState.responsible_name; document.getElementById('create-responsible').value = this.savedState.responsible_id; document.getElementById('create-user-search').value = this.savedState.user_search; document.getElementById('create-customer-user-id').value = this.savedState.customer_user_id; document.getElementById('create-customer-id').value = this.savedState.customer_id; document.getElementById('create-company-search').value = this.savedState.company_search; document.getElementById('create-priority').value = this.savedState.priority_id; document.getElementById('create-subject').value = this.savedState.subject; if (this.savedState.isAdvancedVisible) { advancedOptions.style.display = 'grid'; if (arrow) arrow.style.transform = 'rotate(90deg)'; } } else { setTimeout(async () => { // 1. Owner & Responsible pre-population with active agent const currentAgentId = localStorage.getItem('activeAgentId') || '1'; const activeAgent = (App.lookups.users || []).find(u => String(u.id) === String(currentAgentId)); if (activeAgent) { if (ownerSearchInput && ownerIdInput) { ownerSearchInput.value = `${activeAgent.first_name} ${activeAgent.last_name}`; ownerIdInput.value = activeAgent.id; } if (responsibleSearchInput && responsibleIdInput) { responsibleSearchInput.value = `${activeAgent.first_name} ${activeAgent.last_name}`; responsibleIdInput.value = activeAgent.id; } } // 2. Customer User pre-population with first match try { const companies = await App.api('/api/customer-companies/search?q='); if (companies.length > 0 && customerIdInput && companySearchInput) { const defaultCompany = companies[0]; customerIdInput.value = defaultCompany.customer_id; companySearchInput.value = defaultCompany.customer_id; // Search users for this company const users = await App.api(`/api/customer-users/search?q=&customer_company_id=${encodeURIComponent(defaultCompany.customer_id)}`); if (users.length > 0 && userSearchInput && customerUserIdInput) { const defaultUser = users[0]; userSearchInput.value = `${defaultUser.first_name} ${defaultUser.last_name}`; customerUserIdInput.value = defaultUser.login; } else { // Fallback: use company name as customer user ID userSearchInput.value = defaultCompany.name; customerUserIdInput.value = defaultCompany.customer_id; } } } catch (err) { console.error('Error pre-populating defaults:', err); } // 3. Queue pre-population try { const queues = await App.api('/api/queues/search?q='); if (queues.length > 0 && queueSearchInput && queueIdInput) { queueSearchInput.value = queues[0].name; queueIdInput.value = queues[0].id; } } catch (err) { console.error('Error pre-populating queues:', err); } }, 50); } // Collapsible Options Toggle if (toggleBtn && advancedOptions && arrow) { toggleBtn.addEventListener('click', () => { const isHidden = advancedOptions.style.display === 'none'; advancedOptions.style.display = isHidden ? 'grid' : 'none'; arrow.style.transform = isHidden ? 'rotate(90deg)' : 'rotate(0deg)'; }); } // User Autocomplete let userDebounce; if (userSearchInput) { userSearchInput.addEventListener('input', () => { clearTimeout(userDebounce); const q = userSearchInput.value.trim(); userDebounce = setTimeout(async () => { try { const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`); console.log('[Frontend LDAP Search] Users returned:', users); // Prevent race conditions: discard results if the input value has changed if (userSearchInput.value.trim() !== q) { console.log('[Frontend LDAP Search] Discarding stale results for query:', q); return; } if (users.length === 0) { userSuggestionsDiv.innerHTML = '
Nessun utente trovato
'; userSuggestionsDiv.style.display = 'block'; return; } 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'; // 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; } }); } }); } catch (err) { console.error(err); } }, 300); }); userSearchInput.addEventListener('focus', () => { if (!customerUserIdInput.value) { // Only search if no user is selected yet userSearchInput.dispatchEvent(new Event('input')); } }); userSearchInput.addEventListener('blur', () => { // Small delay to allow mousedown on item to fire first setTimeout(() => { userSuggestionsDiv.style.display = 'none'; }, 150); }); } // Owner Autocomplete (dynamic backend search) let ownerDebounce; if (ownerSearchInput) { ownerSearchInput.addEventListener('input', () => { clearTimeout(ownerDebounce); const q = ownerSearchInput.value.trim(); // Do not block empty query to allow all agent results on focus ownerDebounce = setTimeout(async () => { try { const agents = await App.api(`/api/agents/search?q=${encodeURIComponent(q)}`); if (agents.length === 0) { ownerSuggestionsDiv.innerHTML = '
Nessun agente trovato
'; ownerSuggestionsDiv.style.display = 'block'; return; } ownerSuggestionsDiv.innerHTML = agents.map(u => `
${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)} (Login: ${App.escapeHtml(u.login)})
`).join(''); ownerSuggestionsDiv.style.display = 'block'; // Bind click ownerSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => { if (item.dataset.id) { item.addEventListener('click', () => { ownerSearchInput.value = item.dataset.name; ownerIdInput.value = item.dataset.id; ownerSuggestionsDiv.style.display = 'none'; }); } }); } catch (err) { console.error(err); } }, 300); }); ownerSearchInput.addEventListener('focus', () => { ownerSearchInput.value = ''; ownerIdInput.value = ''; ownerSearchInput.dispatchEvent(new Event('input')); }); } // Responsible Autocomplete (dynamic backend search) let responsibleDebounce; if (responsibleSearchInput) { responsibleSearchInput.addEventListener('input', () => { clearTimeout(responsibleDebounce); const q = responsibleSearchInput.value.trim(); // Do not block empty query to allow all agent results on focus responsibleDebounce = setTimeout(async () => { try { const agents = await App.api(`/api/agents/search?q=${encodeURIComponent(q)}`); if (agents.length === 0) { responsibleSuggestionsDiv.innerHTML = '
Nessun agente trovato
'; responsibleSuggestionsDiv.style.display = 'block'; return; } responsibleSuggestionsDiv.innerHTML = agents.map(u => `
${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)} (Login: ${App.escapeHtml(u.login)})
`).join(''); responsibleSuggestionsDiv.style.display = 'block'; // Bind click responsibleSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => { if (item.dataset.id) { item.addEventListener('click', () => { responsibleSearchInput.value = item.dataset.name; responsibleIdInput.value = item.dataset.id; responsibleSuggestionsDiv.style.display = 'none'; }); } }); } catch (err) { console.error(err); } }, 300); }); responsibleSearchInput.addEventListener('focus', () => { responsibleSearchInput.value = ''; responsibleIdInput.value = ''; responsibleSearchInput.dispatchEvent(new Event('input')); }); } // Queue Autocomplete 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 (queues.length === 0) { queueSuggestionsDiv.innerHTML = '
Nessuna coda trovata
'; queueSuggestionsDiv.style.display = 'block'; return; } queueSuggestionsDiv.innerHTML = queues.map(q => { const parts = q.name.split('::'); const lastPart = parts[parts.length - 1]; const parentPath = parts.length > 1 ? parts.slice(0, -1).join(' › ') + ' › ' : ''; return `
${App.escapeHtml(parentPath)} ${App.escapeHtml(lastPart)}
`; }).join(''); queueSuggestionsDiv.style.display = 'block'; // Bind click queueSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => { if (item.dataset.id) { item.addEventListener('click', () => { queueSearchInput.value = item.dataset.name; queueIdInput.value = item.dataset.id; queueSuggestionsDiv.style.display = 'none'; }); } }); } catch (err) { console.error(err); } }, 150); }); queueSearchInput.addEventListener('focus', () => { queueSearchInput.value = ''; queueIdInput.value = ''; queueSearchInput.dispatchEvent(new Event('input')); }); } // Close suggestions on click outside document.addEventListener('click', (e) => { if (userSearchInput && e.target !== userSearchInput && e.target !== userSuggestionsDiv) { userSuggestionsDiv.style.display = 'none'; } if (ownerSearchInput && e.target !== ownerSearchInput && e.target !== ownerSuggestionsDiv) { ownerSuggestionsDiv.style.display = 'none'; } if (responsibleSearchInput && e.target !== responsibleSearchInput && e.target !== responsibleSuggestionsDiv) { responsibleSuggestionsDiv.style.display = 'none'; } if (queueSearchInput && e.target !== queueSearchInput && e.target !== queueSuggestionsDiv) { queueSuggestionsDiv.style.display = 'none'; } }); submitBtn.addEventListener('click', async () => { const title = document.getElementById('create-title').value.trim(); const queue_id = queueIdInput.value; const state_id = stateIdInput.value; const priority_id = document.getElementById('create-priority').value; const type_id = document.getElementById('create-type')?.value; const userSearchVal = userSearchInput.value.trim(); let customerId = customerIdInput.value; let customerUserId = customerUserIdInput.value; if (!customerUserId && userSearchVal) { customerUserId = userSearchVal; if (!customerId) { customerId = userSearchVal; } } const ownerId = ownerIdInput.value; const responsibleId = responsibleIdInput.value; // Validation if (!title) { Toast.warning('Il titolo è obbligatorio'); document.getElementById('create-title').focus(); return; } if (!queue_id) { Toast.warning('Seleziona una coda'); queueSearchInput.focus(); return; } if (!state_id) { Toast.warning('Seleziona uno stato'); document.getElementById('create-state').focus(); return; } if (!customerId && !customerUserId) { Toast.warning('Seleziona un Utente Cliente'); userSearchInput.focus(); return; } // Company fallback if no individual user selected if (customerId && !customerUserId) { customerUserId = customerId; } const payload = { title, queue_id: parseInt(queue_id), state_id: parseInt(state_id), priority_id: parseInt(priority_id), user_id: ownerId ? parseInt(ownerId) : undefined, responsible_user_id: responsibleId ? parseInt(responsibleId) : undefined, type_id: type_id ? parseInt(type_id) : undefined, customer_id: customerId || undefined, customer_user_id: customerUserId || undefined, subject: document.getElementById('create-subject').value.trim() || undefined, body: this.quill ? this.quill.root.innerHTML.trim() : undefined, attachments: this.attachments.length > 0 ? this.attachments : undefined }; try { submitBtn.disabled = true; submitBtn.innerHTML = '
Creazione...'; const result = await App.api('/api/tickets', { method: 'POST', body: JSON.stringify(payload), }); Toast.success(`Ticket #${result.tn} creato!`); this.savedState = null; // Clear cached state on success this.attachments = []; // Clear attachments array // Update daily timer App.updateDailyTimer(); // Navigate to the new ticket window.location.hash = `#/tickets/${result.id}`; } catch (err) { Toast.error('Errore creazione: ' + err.message); submitBtn.disabled = false; submitBtn.innerHTML = ` Crea Ticket `; } }); // Attachments Upload Handlers const attachBtn = document.getElementById('btn-add-attachments'); const fileInput = document.getElementById('create-attachments'); if (attachBtn && fileInput) { attachBtn.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', (e) => { const files = Array.from(e.target.files); for (const file of files) { const reader = new FileReader(); reader.onload = () => { const base64Data = reader.result.split(',')[1]; this.attachments.push({ filename: file.name, content: base64Data, content_type: file.type }); this.updateAttachmentList(); }; reader.readAsDataURL(file); } fileInput.value = ''; // Reset input to allow re-selecting the same file }); } // Save state on any input/change in the form document.querySelectorAll('.create-form input, .create-form select, .create-form textarea').forEach(el => { el.addEventListener('input', () => this.saveState()); el.addEventListener('change', () => this.saveState()); }); }, };