/** * emailCompose.js * Modal per la composizione e invio email dal contesto di un ticket. * Utilizza Quill.js per l'editor HTML, supporta allegati e immagini inline. */ const EmailCompose = (() => { let quillEditor = null; let attachmentsList = []; let currentOptions = {}; // ── CSS ────────────────────────────────────────────────────────────────────── function injectStyles() { if (document.getElementById('email-compose-styles')) return; const style = document.createElement('style'); style.id = 'email-compose-styles'; style.textContent = ` #email-compose-overlay { position: fixed; inset: 0; z-index: 9000; background: rgba(0,0,0,0.55); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; animation: fadeIn 0.15s ease; } @keyframes fadeIn { from { opacity:0 } to { opacity:1 } } #email-compose-modal { background: var(--bg-card); border: 1px solid var(--border-light); border-radius: var(--radius-xl, 14px); box-shadow: 0 24px 80px rgba(0,0,0,0.35); width: min(860px, 95vw); max-height: 92vh; display: flex; flex-direction: column; animation: slideUp 0.18s ease; } @keyframes slideUp { from { transform: translateY(20px); opacity:0 } to { transform: translateY(0); opacity:1 } } #email-compose-modal .ec-header { padding: 16px 20px 12px; border-bottom: 1px solid var(--border-subtle); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; } #email-compose-modal .ec-title { font-size: 1rem; font-weight: 600; color: var(--text-primary); display: flex; align-items: center; gap: 8px; } #email-compose-modal .ec-body { padding: 16px 20px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 12px; } #email-compose-modal .ec-field { display: flex; flex-direction: column; gap: 4px; } #email-compose-modal .ec-label { font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; } #email-compose-modal .ec-tags-input { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 6px 10px; min-height: 36px; cursor: text; } #email-compose-modal .ec-tags-input:focus-within { border-color: var(--accent-primary); box-shadow: 0 0 0 3px rgba(var(--accent-rgb,99,102,241),0.12); } #email-compose-modal .ec-tag { display: inline-flex; align-items: center; gap: 4px; background: var(--accent-primary); color: #fff; border-radius: 4px; padding: 2px 6px; font-size: 0.78rem; } #email-compose-modal .ec-tag button { background: none; border: none; color: rgba(255,255,255,0.8); cursor: pointer; padding: 0; line-height: 1; font-size: 0.9rem; } #email-compose-modal .ec-tag button:hover { color: #fff; } #email-compose-modal .ec-tag-input { border: none; outline: none; background: transparent; font-size: 0.88rem; color: var(--text-primary); min-width: 160px; flex: 1; } #email-compose-modal .ec-editor-wrapper { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); overflow: hidden; background: var(--bg-tertiary); } #email-compose-modal .ec-editor-wrapper .ql-toolbar { border: none; border-bottom: 1px solid var(--border-subtle); background: var(--bg-secondary); } #email-compose-modal .ec-editor-wrapper .ql-container { border: none; min-height: 220px; font-size: 0.9rem; } #email-compose-modal .ec-attachments-zone { border: 2px dashed var(--border-subtle); border-radius: var(--radius-md); padding: 12px 14px; text-align: center; cursor: pointer; transition: border-color 0.15s, background 0.15s; font-size: 0.82rem; color: var(--text-muted); } #email-compose-modal .ec-attachments-zone:hover, #email-compose-modal .ec-attachments-zone.drag-over { border-color: var(--accent-primary); background: rgba(var(--accent-rgb,99,102,241),0.06); color: var(--text-secondary); } #email-compose-modal .ec-file-list { display: flex; flex-direction: column; gap: 4px; } #email-compose-modal .ec-file-item { display: flex; align-items: center; gap: 8px; font-size: 0.82rem; padding: 4px 8px; background: var(--bg-secondary); border-radius: var(--radius-sm); color: var(--text-secondary); } #email-compose-modal .ec-file-item button { margin-left: auto; background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 0.85rem; padding: 0 2px; } #email-compose-modal .ec-file-item button:hover { color: var(--error); } #email-compose-modal .ec-footer { padding: 12px 20px; border-top: 1px solid var(--border-subtle); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; gap: 10px; } #email-compose-modal select.ec-select { background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 6px 12px; font-size: 0.85rem; color: var(--text-primary); min-width: 180px; } `; document.head.appendChild(style); } // ── Tag Input Helper ────────────────────────────────────────────────────────── function makeTagInput(containerId, initialEmails = []) { const container = document.getElementById(containerId); const tags = [...initialEmails]; function render() { const inputEl = container.querySelector('.ec-tag-input'); const currentVal = inputEl ? inputEl.value : ''; container.innerHTML = ''; tags.forEach((email, idx) => { const tagEl = document.createElement('span'); tagEl.className = 'ec-tag'; tagEl.innerHTML = `${App.escapeHtml(email)}`; tagEl.querySelector('button').addEventListener('click', () => { tags.splice(idx, 1); render(); }); container.appendChild(tagEl); }); const input = document.createElement('input'); input.className = 'ec-tag-input'; input.type = 'text'; input.placeholder = tags.length ? '' : 'email@esempio.com, premi Invio'; input.value = currentVal; input.addEventListener('keydown', (e) => { if ((e.key === 'Enter' || e.key === ',') && input.value.trim()) { e.preventDefault(); const val = input.value.trim().replace(/,$/, ''); if (val && !tags.includes(val)) tags.push(val); input.value = ''; render(); } else if (e.key === 'Backspace' && !input.value && tags.length) { tags.pop(); render(); } }); input.addEventListener('blur', () => { if (input.value.trim()) { const val = input.value.trim().replace(/,$/, ''); if (val && !tags.includes(val)) tags.push(val); input.value = ''; render(); } }); container.appendChild(input); container.addEventListener('click', () => input.focus()); } render(); return { getTags: () => [...tags], addTag: (email) => { if (!tags.includes(email)) { tags.push(email); render(); } } }; } // ── Build Modal HTML ────────────────────────────────────────────────────────── function buildModal() { const overlay = document.createElement('div'); overlay.id = 'email-compose-overlay'; overlay.innerHTML = `
Invia Email
📎 Trascina file qui o clicca per selezionare
`; return overlay; } // ── File List Renderer ──────────────────────────────────────────────────────── function renderFileList() { const list = document.getElementById('ec-file-list'); if (!list) return; list.innerHTML = attachmentsList.map((f, idx) => `
📎 ${App.escapeHtml(f.filename)} (${Math.round(f.content.length * 0.75 / 1024)} KB)
`).join(''); list.querySelectorAll('button[data-idx]').forEach(btn => { btn.addEventListener('click', () => { attachmentsList.splice(parseInt(btn.dataset.idx, 10), 1); renderFileList(); }); }); } // ── Load Signatures ─────────────────────────────────────────────────────────── async function loadSignatures(agentId, selectEl) { try { const sigs = await App.api(`/api/email/signatures?agent_id=${agentId}`); selectEl.innerHTML = ''; sigs.forEach(sig => { const opt = document.createElement('option'); opt.value = sig.id; opt.textContent = sig.name + (sig.is_default ? ' ★' : ''); opt.dataset.html = sig.body_html; selectEl.appendChild(opt); }); // Pre-select default const defSig = sigs.find(s => s.is_default); if (defSig) { selectEl.value = defSig.id; return defSig.body_html; } } catch (e) { console.warn('[EmailCompose] Signatures load error:', e); } return ''; } // ── Open ───────────────────────────────────────────────────────────────────── async function open(options = {}) { injectStyles(); attachmentsList = []; currentOptions = options; // Remove existing const existing = document.getElementById('email-compose-overlay'); if (existing) existing.remove(); const overlay = buildModal(); document.body.appendChild(overlay); // Init tag inputs const initialTo = options.customerEmail ? [options.customerEmail] : []; const toTagsCtrl = makeTagInput('ec-to-container', initialTo); const ccTagsCtrl = makeTagInput('ec-cc-container', []); // Subject const subjectEl = document.getElementById('ec-subject'); const tn = options.ticketTn || ''; const title = options.ticketTitle || ''; subjectEl.value = tn ? `Re: [Ticket#${tn}] ${title}` : title; // Signature select const sigSelect = document.getElementById('ec-signature-select'); const agentId = App.currentAgentId || 0; const defaultSigHtml = await loadSignatures(agentId, sigSelect); // Quill editor quillEditor = new Quill('#ec-quill-editor', { theme: 'snow', placeholder: 'Scrivi il testo della email...', modules: { toolbar: [ ['bold', 'italic', 'underline', 'strike'], [{ 'header': [1, 2, 3, false] }], [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['link', 'image'], [{ 'color': [] }, { 'background': [] }], ['clean'] ], } }); // Insert initial body and signature let initialHtml = ''; if (options.initialBodyHtml) { initialHtml += options.initialBodyHtml; } else { initialHtml += '


'; } if (defaultSigHtml) { initialHtml += '' + defaultSigHtml; } quillEditor.clipboard.dangerouslyPasteHTML(initialHtml); quillEditor.setSelection(0, 0); // Signature change sigSelect.addEventListener('change', () => { const selectedOpt = sigSelect.options[sigSelect.selectedIndex]; const sigHtml = selectedOpt ? (selectedOpt.dataset.html || '') : ''; // Replace signature: get current body, strip old signature (after first
), append new const currentHtml = quillEditor.root.innerHTML; const sigMarker = ''; const baseHtml = currentHtml.includes(sigMarker) ? currentHtml.split(sigMarker)[0] : currentHtml; const newHtml = baseHtml + (sigHtml ? sigMarker + sigHtml : ''); quillEditor.clipboard.dangerouslyPasteHTML(newHtml); }); // File drag & drop const dropZone = document.getElementById('ec-drop-zone'); const fileInput = document.getElementById('ec-file-input'); dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over')); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); processFiles(Array.from(e.dataTransfer.files)); }); fileInput.addEventListener('change', (e) => { processFiles(Array.from(e.target.files)); fileInput.value = ''; }); // Close handlers document.getElementById('ec-close').addEventListener('click', close); document.getElementById('ec-cancel').addEventListener('click', close); overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); }); // Send document.getElementById('ec-send').addEventListener('click', () => sendEmail(toTagsCtrl, ccTagsCtrl)); } function processFiles(files) { for (const file of files) { const reader = new FileReader(); reader.onload = () => { const base64 = reader.result.split(',')[1]; attachmentsList.push({ filename: file.name, content: base64, contentType: file.type }); renderFileList(); }; reader.readAsDataURL(file); } } // ── Send ───────────────────────────────────────────────────────────────────── async function sendEmail(toCtrl, ccCtrl) { const to = toCtrl.getTags(); const cc = ccCtrl.getTags(); const subject = document.getElementById('ec-subject').value.trim(); const bodyHtml = quillEditor ? quillEditor.root.innerHTML : ''; if (!to.length) { Toast.warning('Inserisci almeno un destinatario (campo A)'); return; } if (!subject) { Toast.warning('Inserisci l\'oggetto della email'); return; } const sendBtn = document.getElementById('ec-send'); sendBtn.disabled = true; sendBtn.innerHTML = '
Invio...'; try { const agentId = App.currentAgentId || 0; const payload = { ticketId: currentOptions.ticketId, to, cc, subject, bodyHtml, attachments: attachmentsList, agentId, keepHelpdeskCopy: document.getElementById('ec-helpdesk-cc-select').value === '1', }; const res = await App.api('/api/email/send', { method: 'POST', body: JSON.stringify(payload), }); Toast.success(`Email inviata a ${to.join(', ')}`); close(); } catch (err) { Toast.error('Errore invio email: ' + err.message); sendBtn.disabled = false; sendBtn.innerHTML = ' Invia'; } } // ── Close ───────────────────────────────────────────────────────────────────── function close() { const overlay = document.getElementById('email-compose-overlay'); if (overlay) overlay.remove(); if (quillEditor) { quillEditor = null; } attachmentsList = []; } return { open, close }; })(); window.EmailCompose = EmailCompose;