/** * Ticket Bulk Creation View * Tabular layout for creating multiple tickets. */ const TicketBulkView = { rowCount: 0, activeRequests: false, savedRows: null, rowAttachments: {}, async render() { const container = document.getElementById('view-container'); container.innerHTML = '

Caricamento dati...

'; 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); } } const initialRowData = { state_id: '1', 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: '', owner_id: '', responsible_name: '', responsible_id: '', customer_search: foundCustomer ? `${foundCustomer.first_name} ${foundCustomer.last_name}` : (incomingCustomer || ''), customer_id: foundCustomer ? foundCustomer.customer_id : '', customer_user_id: foundCustomer ? foundCustomer.login : (incomingCustomer || ''), subject: incomingSubject || 'Messaggio da Microsoft Teams', body: incomingBody || '', priority_id: '3', company_display: foundCustomer ? foundCustomer.customer_id : '', isExpanded: true, statusDotClass: '', statusDotTitle: '' }; if (!this.savedRows) this.savedRows = []; this.savedRows.unshift(initialRowData); // Clean query parameters from URL to prevent duplicate inserts on reload window.history.replaceState({}, document.title, window.location.pathname + window.location.hash.split('?')[0]); } this.rowCount = 0; container.innerHTML = `
Apertura Massiva Ticket Usa il tasto Tab per spostarti e creare nuove righe in automatico.
# Stato * Titolo * Coda * Tipo Owner Responsabile Cliente * Oggetto Messaggio Azioni
`; // Add event listeners document.getElementById('bulk-add-row-btn').addEventListener('click', () => this.addRow()); document.getElementById('bulk-submit-btn').addEventListener('click', () => this.submitBulk()); // Add first row or restore saved rows if (this.savedRows && this.savedRows.length > 0) { this.savedRows.forEach(rowData => this.addRow(rowData)); } else { this.addRow(); } } catch (err) { console.error('Error rendering bulk view:', err); container.innerHTML = `

Errore caricamento: ${err.message}

`; } }, addRow(savedData = null) { this.rowCount++; const id = this.rowCount; const container = document.getElementById('bulk-rows-container'); const tr = document.createElement('tr'); tr.id = `bulk-row-${id}`; tr.className = 'bulk-row'; tr.innerHTML = ` ${id}
`; // Collapsible advanced row const trExp = document.createElement('tr'); trExp.id = `bulk-row-expanded-${id}`; trExp.className = 'bulk-row-expanded'; trExp.style.display = 'none'; trExp.innerHTML = `
`; container.appendChild(tr); container.appendChild(trExp); // Initialize attachments for this row this.rowAttachments[id] = savedData ? (savedData.attachments || []) : []; // Bind attachment click and input change const attachBtn = tr.querySelector(`#bulk-attach-btn-${id}`); const fileInput = tr.querySelector(`#bulk-file-input-${id}`); const attachBadge = tr.querySelector(`#bulk-attach-badge-${id}`); const updateBadge = () => { const count = (this.rowAttachments[id] || []).length; if (attachBadge) { attachBadge.textContent = count; attachBadge.style.display = count > 0 ? 'inline-block' : 'none'; } }; 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]; if (!this.rowAttachments[id]) { this.rowAttachments[id] = []; } this.rowAttachments[id].push({ filename: file.name, content: base64Data, content_type: file.type }); updateBadge(); this.saveState(); }; reader.readAsDataURL(file); } fileInput.value = ''; }); } // Populate with savedData if available, otherwise set default values if (savedData) { tr.querySelector(`#bulk-state-${id}`).value = savedData.state_id; tr.querySelector(`#bulk-title-${id}`).value = savedData.title; tr.querySelector(`#bulk-queue-search-${id}`).value = savedData.queue_name; tr.querySelector(`#bulk-queue-${id}`).value = savedData.queue_id; tr.querySelector(`#bulk-type-${id}`).value = savedData.type_id; tr.querySelector(`#bulk-owner-search-${id}`).value = savedData.owner_name; tr.querySelector(`#bulk-owner-${id}`).value = savedData.owner_id; tr.querySelector(`#bulk-responsible-search-${id}`).value = savedData.responsible_name; tr.querySelector(`#bulk-responsible-${id}`).value = savedData.responsible_id; tr.querySelector(`#bulk-customer-search-${id}`).value = savedData.customer_search; tr.querySelector(`#bulk-customer-${id}`).value = savedData.customer_id; tr.querySelector(`#bulk-customer-user-id-${id}`).value = savedData.customer_user_id; tr.querySelector(`#bulk-subject-${id}`).value = savedData.subject; tr.querySelector(`#bulk-body-${id}`).value = savedData.body; trExp.querySelector(`#bulk-priority-${id}`).value = savedData.priority_id; trExp.querySelector(`#bulk-company-display-${id}`).value = savedData.company_display; if (savedData.isExpanded) { trExp.style.display = 'table-row'; const btn = tr.querySelector(`#bulk-expand-btn-${id}`); btn.innerHTML = ``; } const dot = tr.querySelector(`#status-dot-${id}`); if (savedData.statusDotClass) dot.className = savedData.statusDotClass; if (savedData.statusDotTitle) dot.title = savedData.statusDotTitle; } else { // Default queue pre-population const defaultQueueInput = tr.querySelector(`#bulk-queue-search-${id}`); const defaultQueueHidden = tr.querySelector(`#bulk-queue-${id}`); if (App.lookups.queues && App.lookups.queues.length > 0) { defaultQueueInput.value = App.lookups.queues[0].name; defaultQueueHidden.value = App.lookups.queues[0].id; } // Default owner and responsible (Active Agent) const activeAgentId = localStorage.getItem('activeAgentId') || '1'; const activeAgent = (App.lookups.users || []).find(u => String(u.id) === String(activeAgentId)); if (activeAgent) { const agentName = `${activeAgent.first_name} ${activeAgent.last_name}`; tr.querySelector(`#bulk-owner-search-${id}`).value = agentName; tr.querySelector(`#bulk-owner-${id}`).value = activeAgent.id; tr.querySelector(`#bulk-responsible-search-${id}`).value = agentName; tr.querySelector(`#bulk-responsible-${id}`).value = activeAgent.id; } } // Toggle expand button tr.querySelector(`#bulk-expand-btn-${id}`).addEventListener('click', (e) => { e.preventDefault(); const isHidden = trExp.style.display === 'none'; trExp.style.display = isHidden ? 'table-row' : 'none'; const btn = tr.querySelector(`#bulk-expand-btn-${id}`); btn.innerHTML = isHidden ? `` : ``; this.saveState(); }); // Delete button tr.querySelector(`#bulk-delete-btn-${id}`).addEventListener('click', (e) => { e.preventDefault(); tr.remove(); trExp.remove(); this.reorderRows(); this.saveState(); }); // Setup autocompletes this.setupAutocomplete(id, 'queue', `bulk-queue-search-${id}`, `bulk-queue-${id}`, `bulk-queue-suggestions-${id}`); this.setupAutocomplete(id, 'owner', `bulk-owner-search-${id}`, `bulk-owner-${id}`, `bulk-owner-suggestions-${id}`); this.setupAutocomplete(id, 'responsible', `bulk-responsible-search-${id}`, `bulk-responsible-${id}`, `bulk-responsible-suggestions-${id}`); this.setupAutocomplete(id, 'customer', `bulk-customer-search-${id}`, `bulk-customer-${id}`, `bulk-customer-suggestions-${id}`); // Setup Tab key navigation on the last field (body textarea) const textarea = tr.querySelector(`#bulk-body-${id}`); textarea.addEventListener('keydown', (e) => { if (e.key === 'Tab' && !e.shiftKey) { // Check if this is the last visible bulk-row textarea const activeRows = container.querySelectorAll('.bulk-row'); const lastRow = activeRows[activeRows.length - 1]; if (tr === lastRow) { e.preventDefault(); this.addRow(); // Focus the next row's first input (Title) setTimeout(() => { const nextId = this.rowCount; document.getElementById(`bulk-title-${nextId}`)?.focus(); }, 50); } } }); // Scroll table to make inputs and textareas fully visible when focused tr.querySelectorAll('input, select, textarea').forEach(input => { input.addEventListener('focus', () => { const wrapper = document.querySelector('.bulk-grid-wrapper'); if (wrapper) { setTimeout(() => { const rect = input.getBoundingClientRect(); const wrapperRect = wrapper.getBoundingClientRect(); if (rect.right > wrapperRect.right) { wrapper.scrollBy({ left: rect.right - wrapperRect.right + 40, behavior: 'smooth' }); } else if (rect.left < wrapperRect.left) { wrapper.scrollBy({ left: rect.left - wrapperRect.left - 40, behavior: 'smooth' }); } if (rect.bottom > wrapperRect.bottom) { wrapper.scrollBy({ top: rect.bottom - wrapperRect.bottom + 40, behavior: 'smooth' }); } else if (rect.top < wrapperRect.top) { wrapper.scrollBy({ top: rect.top - wrapperRect.top - 40, behavior: 'smooth' }); } }, 150); } }); }); // Save state on any input change tr.querySelectorAll('input, select, textarea').forEach(input => { input.addEventListener('input', () => this.saveState()); input.addEventListener('change', () => this.saveState()); }); trExp.querySelectorAll('input, select, textarea').forEach(input => { input.addEventListener('input', () => this.saveState()); input.addEventListener('change', () => this.saveState()); }); updateBadge(); this.saveState(); }, reorderRows() { const container = document.getElementById('bulk-rows-container'); const rows = container.querySelectorAll('.bulk-row'); rows.forEach((row, index) => { const numSpan = row.querySelector('.row-num'); if (numSpan) numSpan.textContent = index + 1; }); }, saveState() { const container = document.getElementById('bulk-rows-container'); if (!container) return; const rows = container.querySelectorAll('.bulk-row'); const data = []; rows.forEach(row => { const id = row.id.replace('bulk-row-', ''); const state_id = document.getElementById(`bulk-state-${id}`).value; const title = document.getElementById(`bulk-title-${id}`).value; const queue_name = document.getElementById(`bulk-queue-search-${id}`).value; const queue_id = document.getElementById(`bulk-queue-${id}`).value; const type_id = document.getElementById(`bulk-type-${id}`).value; const owner_name = document.getElementById(`bulk-owner-search-${id}`).value; const owner_id = document.getElementById(`bulk-owner-${id}`).value; const responsible_name = document.getElementById(`bulk-responsible-search-${id}`).value; const responsible_id = document.getElementById(`bulk-responsible-${id}`).value; const customer_search = document.getElementById(`bulk-customer-search-${id}`).value; const customer_id = document.getElementById(`bulk-customer-${id}`).value; const customer_user_id = document.getElementById(`bulk-customer-user-id-${id}`).value; const subject = document.getElementById(`bulk-subject-${id}`).value; const body = document.getElementById(`bulk-body-${id}`).value; const priority_id = document.getElementById(`bulk-priority-${id}`).value; const company_display = document.getElementById(`bulk-company-display-${id}`).value; const trExp = document.getElementById(`bulk-row-expanded-${id}`); const isExpanded = trExp ? trExp.style.display !== 'none' : false; const statusDot = document.getElementById(`status-dot-${id}`); const statusDotClass = statusDot ? statusDot.className : ''; const statusDotTitle = statusDot ? statusDot.title : ''; data.push({ state_id, title, queue_name, queue_id, type_id, owner_name, owner_id, responsible_name, responsible_id, customer_search, customer_id, customer_user_id, subject, body, priority_id, company_display, isExpanded, statusDotClass, statusDotTitle, attachments: this.rowAttachments[id] || [] }); }); this.savedRows = data; }, setupAutocomplete(rowId, type, searchInputId, hiddenInputId, suggestionsDivId) { const input = document.getElementById(searchInputId); const hidden = document.getElementById(hiddenInputId); const suggestions = document.getElementById(suggestionsDivId); if (!input || !suggestions) return; let debounce; const adjustSuggestionsScroll = () => { setTimeout(() => { const wrapper = document.querySelector('.bulk-grid-wrapper'); if (wrapper) { const rect = suggestions.getBoundingClientRect(); const wrapperRect = wrapper.getBoundingClientRect(); if (rect.right > wrapperRect.right) { wrapper.scrollBy({ left: rect.right - wrapperRect.right + 40, behavior: 'smooth' }); } else if (rect.left < wrapperRect.left) { wrapper.scrollBy({ left: rect.left - wrapperRect.left - 40, behavior: 'smooth' }); } if (rect.bottom > wrapperRect.bottom) { wrapper.scrollBy({ top: rect.bottom - wrapperRect.bottom + 40, behavior: 'smooth' }); } else if (rect.top < wrapperRect.top) { wrapper.scrollBy({ top: rect.top - wrapperRect.top - 40, behavior: 'smooth' }); } } }, 50); }; const renderResults = (results) => { if (results.length === 0) { suggestions.innerHTML = '
Nessun risultato
'; suggestions.style.display = 'block'; adjustSuggestionsScroll(); return; } suggestions.innerHTML = results.map(r => { if (type === 'queue') { return `
${App.escapeHtml(r.name)}
`; } else if (type === 'owner' || type === 'responsible') { return `
${App.escapeHtml(r.first_name)} ${App.escapeHtml(r.last_name)} (${r.login})
`; } else if (type === 'customer') { return `
${App.escapeHtml(r.first_name)} ${App.escapeHtml(r.last_name)} (${r.login} | ${r.customer_id || '—'})
`; } }).join(''); suggestions.style.display = 'block'; adjustSuggestionsScroll(); // Bind clicks suggestions.querySelectorAll('.bulk-suggestions-item').forEach(item => { item.addEventListener('click', () => { if (type === 'queue' || type === 'owner' || type === 'responsible') { input.value = item.dataset.name; hidden.value = item.dataset.id; } else if (type === 'customer') { input.value = item.dataset.name; hidden.value = item.dataset.customerId || ''; document.getElementById(`bulk-customer-user-id-${rowId}`).value = item.dataset.login; document.getElementById(`bulk-company-display-${rowId}`).value = item.dataset.customerId || ''; } suggestions.style.display = 'none'; }); }); }; const showAllLocalOrQuery = async () => { if (type === 'queue') { renderResults(App.lookups.queues || []); } else if (type === 'owner' || type === 'responsible') { renderResults(App.lookups.users || []); } else if (type === 'customer') { try { const results = await App.api(`/api/customer-users/search?q=`); renderResults(results); } catch (err) { console.error(err); } } }; input.addEventListener('input', () => { clearTimeout(debounce); const q = input.value.trim(); // Clear hidden values if user deletes text if (q.length === 0) { hidden.value = ''; if (type === 'customer') { document.getElementById(`bulk-customer-user-id-${rowId}`).value = ''; document.getElementById(`bulk-company-display-${rowId}`).value = ''; } showAllLocalOrQuery(); return; } if (type !== 'queue' && type !== 'owner' && type !== 'responsible' && type !== 'customer' && q.length < 2) { suggestions.style.display = 'none'; return; } // Local filtering for fast performance on loaded lookups if (type === 'queue' || type === 'owner' || type === 'responsible') { const lowerQ = q.toLowerCase(); let filtered = []; if (type === 'queue') { filtered = (App.lookups.queues || []).filter(r => r.name.toLowerCase().includes(lowerQ)); } else { filtered = (App.lookups.users || []).filter(r => r.first_name.toLowerCase().includes(lowerQ) || r.last_name.toLowerCase().includes(lowerQ) || r.login.toLowerCase().includes(lowerQ) ); } renderResults(filtered); return; } // API search for customer users debounce = setTimeout(async () => { try { const results = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`); renderResults(results); } catch (err) { console.error(err); } }, 250); }); // Close suggestions clicking outside document.addEventListener('click', (e) => { if (e.target !== input && e.target !== suggestions) { suggestions.style.display = 'none'; } }); // Close suggestions on blur (tab navigation out of input) input.addEventListener('blur', () => { setTimeout(() => { suggestions.style.display = 'none'; }, 200); }); // Empty input and show list on focus/click input.addEventListener('focus', () => { if (type === 'queue' || type === 'owner' || type === 'responsible' || type === 'customer') { input.value = ''; hidden.value = ''; if (type === 'customer') { document.getElementById(`bulk-customer-user-id-${rowId}`).value = ''; document.getElementById(`bulk-company-display-${rowId}`).value = ''; } showAllLocalOrQuery(); } }); }, async submitBulk() { if (this.activeRequests) return; const container = document.getElementById('bulk-rows-container'); const rows = container.querySelectorAll('.bulk-row'); if (rows.length === 0) { Toast.warning('Nessun ticket da creare'); return; } // Validation pass const ticketsToSend = []; let hasValidationError = false; rows.forEach(row => { const id = row.id.replace('bulk-row-', ''); const state_id = document.getElementById(`bulk-state-${id}`).value; const title = document.getElementById(`bulk-title-${id}`).value.trim(); const queue_id = document.getElementById(`bulk-queue-${id}`).value; const queue_name = document.getElementById(`bulk-queue-search-${id}`).value; const type_id = document.getElementById(`bulk-type-${id}`).value; const ownerId = document.getElementById(`bulk-owner-${id}`).value; const responsibleId = document.getElementById(`bulk-responsible-${id}`).value; const customerId = document.getElementById(`bulk-customer-${id}`).value; const customerUserId = document.getElementById(`bulk-customer-user-id-${id}`).value; const customerSearch = document.getElementById(`bulk-customer-search-${id}`).value; const subject = document.getElementById(`bulk-subject-${id}`).value.trim(); const body = document.getElementById(`bulk-body-${id}`).value.trim(); const priority_id = document.getElementById(`bulk-priority-${id}`).value; // Reset status indicators const dot = document.getElementById(`status-dot-${id}`); dot.className = 'row-status-dot'; if (!title) { Toast.warning(`Titolo obbligatorio alla riga ${id}`); document.getElementById(`bulk-title-${id}`).focus(); hasValidationError = true; return; } if (!queue_id) { Toast.warning(`Seleziona una coda valida alla riga ${id}`); document.getElementById(`bulk-queue-search-${id}`).focus(); hasValidationError = true; return; } if (!customerUserId && !customerId) { Toast.warning(`Seleziona un Utente Cliente valido alla riga ${id}`); document.getElementById(`bulk-customer-search-${id}`).focus(); hasValidationError = true; return; } ticketsToSend.push({ id, 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: subject || undefined, body: body || undefined, attachments: (this.rowAttachments[id] || []).length > 0 ? this.rowAttachments[id] : undefined } }); }); if (hasValidationError) return; // Send tickets one by one this.activeRequests = true; const submitBtn = document.getElementById('bulk-submit-btn'); const origBtnHtml = submitBtn.innerHTML; submitBtn.disabled = true; Toast.info(`Inizio invio di ${ticketsToSend.length} ticket...`); let successCount = 0; let failCount = 0; for (const ticket of ticketsToSend) { const dot = document.getElementById(`status-dot-${ticket.id}`); dot.className = 'row-status-dot pending'; try { submitBtn.innerHTML = `
Invio riga ${ticket.id}...`; const result = await App.api('/api/tickets', { method: 'POST', body: JSON.stringify(ticket.payload) }); dot.className = 'row-status-dot success'; dot.title = `Successo: Ticket #${result.tn}`; successCount++; } catch (err) { console.error(`Error creating bulk ticket row ${ticket.id}:`, err); dot.className = 'row-status-dot error'; dot.title = `Errore: ${err.message}`; failCount++; } } this.activeRequests = false; submitBtn.disabled = false; submitBtn.innerHTML = origBtnHtml; if (failCount === 0) { Toast.success(`Tutti i ${successCount} ticket sono stati creati con successo!`); this.savedRows = []; this.rowAttachments = {}; setTimeout(() => { window.location.hash = '#/tickets'; }, 1500); } else { this.saveState(); Toast.warning(`Invio completato: ${successCount} creati, ${failCount} falliti. Controlla i pallini colorati per i dettagli.`); } } };