Files

484 lines
21 KiB
JavaScript

/**
* mailManagement.js
* Pagina di gestione mail: include la gestione dei Gruppi di Indirizzi
* e la gestione delle firme email per l'agente attivo.
*/
const MailManagementView = {
agentId: null,
signatures: [],
groups: [],
editingSigId: null,
editingGroupId: null,
signatureQuill: null,
async render() {
this.agentId = App.currentAgentId || 0;
const container = document.getElementById('view-container');
// Inject styles for tooltip and layout
this.injectStyles();
container.innerHTML = `
<div style="max-width: 820px; margin: 0 auto; padding: var(--space-xl) var(--space-lg); display: flex; flex-direction: column; gap: var(--space-xl);">
<!-- SECTION 1: Address Groups -->
<div class="card" style="padding: var(--space-lg);">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-md);">
<div>
<h2 style="margin:0; font-size:1.15rem; font-weight:700; color:var(--text-primary); display:flex; align-items:center; gap:8px;">👥 Gruppi di Indirizzi</h2>
<p style="margin:4px 0 0; font-size:0.82rem; color:var(--text-muted);">Crea gruppi di contatti da inserire rapidamente nei campi A, CC o BCC.</p>
</div>
<button class="btn btn-primary btn-sm" id="btn-new-group">
+ Nuovo Gruppo
</button>
</div>
<div id="groups-list" style="display:grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--space-md);"></div>
</div>
<!-- SECTION 2: Signatures -->
<div class="card" style="padding: var(--space-lg);">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-md);">
<div>
<h2 style="margin:0; font-size:1.15rem; font-weight:700; color:var(--text-primary); display:flex; align-items:center; gap:8px;">✉️ Le Mie Firme Email</h2>
<p style="margin:4px 0 0; font-size:0.82rem; color:var(--text-muted);">Gestisci le firme da allegare automaticamente alle tue email.</p>
</div>
<button class="btn btn-primary btn-sm" id="btn-new-signature">
+ Nuova Firma
</button>
</div>
<div id="signatures-list" style="display:flex; flex-direction:column; gap:var(--space-md);"></div>
</div>
<!-- MODAL: Signature Editor -->
<div id="signature-editor-modal" style="display:none; position:fixed; inset:0; z-index:8000; background:rgba(0,0,0,0.5); backdrop-filter:blur(4px); align-items:center; justify-content:center;">
<div style="background:var(--bg-card); border:1px solid var(--border-light); border-radius:var(--radius-xl); box-shadow:0 24px 80px rgba(0,0,0,0.3); width:min(720px,94vw); max-height:90vh; display:flex; flex-direction:column;">
<div style="padding:16px 20px 12px; border-bottom:1px solid var(--border-subtle); display:flex; align-items:center; justify-content:space-between; flex-shrink:0;">
<div style="font-size:1rem; font-weight:600; color:var(--text-primary);" id="sig-modal-title">Nuova Firma</div>
<button id="sig-modal-close" style="background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:1.2rem;">✕</button>
</div>
<div style="padding:16px 20px; flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:12px;">
<div>
<label style="font-size:0.75rem; font-weight:600; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.04em;">Nome Firma</label>
<input type="text" id="sig-name" class="form-input" placeholder="es. Firma Professionale" style="margin-bottom:0; margin-top:4px;" />
</div>
<div>
<label style="font-size:0.75rem; font-weight:600; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.04em;">Contenuto</label>
<div style="margin-top:4px; border:1px solid var(--border-subtle); border-radius:var(--radius-md); overflow:hidden; background:var(--bg-tertiary);">
<div id="sig-quill-editor" style="min-height:200px;"></div>
</div>
</div>
<label style="display:flex; align-items:center; gap:8px; font-size:0.88rem; color:var(--text-secondary); cursor:pointer;">
<input type="checkbox" id="sig-is-default" />
Imposta come firma predefinita
</label>
</div>
<div style="padding:12px 20px; border-top:1px solid var(--border-subtle); display:flex; justify-content:flex-end; gap:10px; flex-shrink:0;">
<button class="btn btn-ghost btn-sm" id="sig-cancel">Annulla</button>
<button class="btn btn-primary btn-sm" id="sig-save">Salva Firma</button>
</div>
</div>
</div>
<!-- MODAL: Address Group Editor -->
<div id="group-editor-modal" style="display:none; position:fixed; inset:0; z-index:8000; background:rgba(0,0,0,0.5); backdrop-filter:blur(4px); align-items:center; justify-content:center;">
<div style="background:var(--bg-card); border:1px solid var(--border-light); border-radius:var(--radius-xl); box-shadow:0 24px 80px rgba(0,0,0,0.3); width:min(520px,94vw); max-height:90vh; display:flex; flex-direction:column;">
<div style="padding:16px 20px 12px; border-bottom:1px solid var(--border-subtle); display:flex; align-items:center; justify-content:space-between; flex-shrink:0;">
<div style="font-size:1rem; font-weight:600; color:var(--text-primary);" id="group-modal-title">Nuovo Gruppo</div>
<button id="group-modal-close" style="background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:1.2rem;">✕</button>
</div>
<div style="padding:16px 20px; flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:12px;">
<div style="display: flex; flex-direction: column; gap: 4px;">
<label style="font-size:0.75rem; font-weight:600; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.04em;">Nome Gruppo</label>
<input type="text" id="group-name" class="form-input" placeholder="es. Sviluppo Interno" style="width: 100%; box-sizing: border-box; display: block; margin-bottom:0;" />
</div>
<div style="display: flex; flex-direction: column; gap: 4px;">
<label style="font-size:0.75rem; font-weight:600; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.04em;">Indirizzi Email (separati da virgola)</label>
<textarea id="group-emails" class="form-input" rows="4" placeholder="es. user1@test.com, user2@test.com" style="width: 100%; box-sizing: border-box; display: block; margin-bottom:0; resize:vertical; font-family:monospace; font-size:0.85rem; padding:8px 12px;"></textarea>
</div>
</div>
<div style="padding:12px 20px; border-top:1px solid var(--border-subtle); display:flex; justify-content:flex-end; gap:10px; flex-shrink:0;">
<button class="btn btn-ghost btn-sm" id="group-cancel">Annulla</button>
<button class="btn btn-primary btn-sm" id="group-save">Salva Gruppo</button>
</div>
</div>
</div>
</div>
`;
// Signatures events
document.getElementById('btn-new-signature').addEventListener('click', () => this.openSigEditor(null));
// Address Groups events
document.getElementById('btn-new-group').addEventListener('click', () => this.openGroupEditor(null));
// Load data
await Promise.all([
this.loadSignatures(),
this.loadGroups()
]);
},
injectStyles() {
if (document.getElementById('mail-management-styles')) return;
const style = document.createElement('style');
style.id = 'mail-management-styles';
style.textContent = `
.g-card {
background: var(--bg-secondary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: var(--space-md);
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 12px;
transition: transform 0.2s, box-shadow 0.2s;
}
.g-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
border-color: var(--accent-primary);
}
.g-tooltip {
visibility: hidden;
opacity: 0;
position: absolute;
bottom: 105%;
left: 50%;
transform: translateX(-50%);
background: var(--bg-tertiary, #2c2c3e);
border: 1px solid var(--border-light, #444);
color: var(--text-primary, #fff);
padding: 8px 12px;
border-radius: 6px;
font-size: 0.78rem;
z-index: 100;
white-space: pre-line;
box-shadow: var(--shadow-lg);
pointer-events: none;
transition: opacity 0.2s, visibility 0.2s;
max-width: 280px;
width: max-content;
}
.g-card:hover .g-tooltip {
visibility: visible;
opacity: 1;
}
`;
document.head.appendChild(style);
},
// ─── SIGNATURES LOGIC ────────────────────────────────────────────────────────
async loadSignatures() {
try {
this.signatures = await App.api(`/api/email/signatures?agent_id=${this.agentId}`);
this.renderSignatures();
} catch (err) {
Toast.error('Errore caricamento firme: ' + err.message);
}
},
renderSignatures() {
const list = document.getElementById('signatures-list');
if (!list) return;
if (!this.signatures.length) {
list.innerHTML = `
<div class="empty-state" style="padding:var(--space-md); border:1px dashed var(--border-subtle); border-radius:var(--radius-md);">
<div class="empty-state-icon" style="font-size:1.5rem;">✉️</div>
<div class="empty-state-text" style="font-size:0.9rem;">Nessuna firma configurata</div>
</div>
`;
return;
}
list.innerHTML = this.signatures.map(sig => `
<div class="card" style="padding: var(--space-md); background: var(--bg-secondary); border-color: var(--border-subtle);">
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:${sig.body_html ? 'var(--space-sm)' : '0'};">
<div>
<div style="font-weight:600; font-size:0.9rem; color:var(--text-primary); display:flex; align-items:center; gap:8px;">
${App.escapeHtml(sig.name)}
${sig.is_default ? '<span style="font-size:0.68rem; background:var(--accent-primary); color:#fff; padding:2px 7px; border-radius:20px;">Predefinita</span>' : ''}
</div>
</div>
<div style="display:flex; gap:6px; flex-shrink:0;">
${!sig.is_default ? `<button class="btn btn-ghost btn-sm sig-btn-default" data-id="${sig.id}" style="height:26px; font-size:0.75rem; padding:0 8px;">★ Predefinita</button>` : ''}
<button class="btn btn-ghost btn-sm sig-btn-edit" data-id="${sig.id}" style="height:26px; font-size:0.75rem; padding:0 8px;">✏️ Modifica</button>
<button class="btn btn-ghost btn-sm sig-btn-delete" data-id="${sig.id}" style="height:26px; font-size:0.75rem; padding:0 8px; color:var(--error);">🗑️</button>
</div>
</div>
${sig.body_html ? `
<div style="border:1px solid var(--border-subtle); border-radius:var(--radius-md); padding:8px 12px; background:var(--bg-tertiary); max-height:80px; overflow:hidden; position:relative;">
<div style="font-size:0.8rem; color:var(--text-secondary);">${sig.body_html}</div>
<div style="position:absolute;bottom:0;left:0;right:0;height:24px;background:linear-gradient(transparent,var(--bg-tertiary));"></div>
</div>
` : ''}
</div>
`).join('');
// Bind buttons
list.querySelectorAll('.sig-btn-edit').forEach(btn => {
btn.addEventListener('click', () => this.openSigEditor(parseInt(btn.dataset.id, 10)));
});
list.querySelectorAll('.sig-btn-delete').forEach(btn => {
btn.addEventListener('click', () => this.deleteSig(parseInt(btn.dataset.id, 10)));
});
list.querySelectorAll('.sig-btn-default').forEach(btn => {
btn.addEventListener('click', () => this.setDefaultSig(parseInt(btn.dataset.id, 10)));
});
},
openSigEditor(id) {
this.editingSigId = id;
const sig = id ? this.signatures.find(s => s.id === id) : null;
const modal = document.getElementById('signature-editor-modal');
modal.style.display = 'flex';
document.getElementById('sig-modal-title').textContent = id ? 'Modifica Firma' : 'Nuova Firma';
document.getElementById('sig-name').value = sig ? sig.name : '';
document.getElementById('sig-is-default').checked = sig ? !!sig.is_default : false;
// Init or reset Quill
if (this.signatureQuill) {
this.signatureQuill.root.innerHTML = sig ? (sig.body_html || '') : '';
} else {
this.signatureQuill = new Quill('#sig-quill-editor', {
theme: 'snow',
placeholder: 'Inserisci la tua firma...',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ 'color': [] }],
['link', 'image'],
['clean']
]
}
});
if (sig && sig.body_html) {
this.signatureQuill.clipboard.dangerouslyPasteHTML(sig.body_html);
}
}
document.getElementById('sig-modal-close').onclick = () => this.closeSigEditor();
document.getElementById('sig-cancel').onclick = () => this.closeSigEditor();
document.getElementById('sig-save').onclick = () => this.saveSig();
modal.onclick = (e) => { if (e.target === modal) this.closeSigEditor(); };
},
closeSigEditor() {
const modal = document.getElementById('signature-editor-modal');
if (modal) modal.style.display = 'none';
this.editingSigId = null;
},
async saveSig() {
const name = document.getElementById('sig-name').value.trim();
const body_html = this.signatureQuill ? this.signatureQuill.root.innerHTML : '';
const is_default = document.getElementById('sig-is-default').checked ? 1 : 0;
if (!name) { Toast.warning('Inserisci un nome per la firma'); return; }
const saveBtn = document.getElementById('sig-save');
saveBtn.disabled = true;
saveBtn.textContent = 'Salvataggio...';
try {
const payload = { agent_id: this.agentId, name, body_html, is_default };
if (this.editingSigId) {
await App.api(`/api/email/signatures/${this.editingSigId}`, {
method: 'PUT',
body: JSON.stringify(payload),
});
Toast.success('Firma aggiornata');
} else {
await App.api('/api/email/signatures', {
method: 'POST',
body: JSON.stringify(payload),
});
Toast.success('Firma creata');
}
this.closeSigEditor();
await this.loadSignatures();
} catch (err) {
Toast.error('Errore salvataggio: ' + err.message);
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Salva Firma';
}
},
async deleteSig(id) {
const ok = await App.confirm('Elimina Firma', 'Sei sicuro di voler eliminare questa firma?');
if (!ok) return;
try {
await App.api(`/api/email/signatures/${id}`, { method: 'DELETE' });
Toast.success('Firma eliminata');
await this.loadSignatures();
} catch (err) {
Toast.error('Errore eliminazione: ' + err.message);
}
},
async setDefaultSig(id) {
try {
await App.api(`/api/email/signatures/${id}/default`, {
method: 'PATCH',
body: JSON.stringify({ agent_id: this.agentId }),
});
Toast.success('Firma impostata come predefinita');
await this.loadSignatures();
} catch (err) {
Toast.error('Errore: ' + err.message);
}
},
// ─── ADDRESS GROUPS LOGIC ──────────────────────────────────────────────────
async loadGroups() {
try {
this.groups = await App.api(`/api/email/address-groups?agent_id=${this.agentId}`);
this.renderGroups();
} catch (err) {
Toast.error('Errore caricamento gruppi: ' + err.message);
}
},
renderGroups() {
const list = document.getElementById('groups-list');
if (!list) return;
if (!this.groups.length) {
list.innerHTML = `
<div class="empty-state" style="padding:var(--space-md); border:1px dashed var(--border-subtle); border-radius:var(--radius-md); grid-column: 1 / -1;">
<div class="empty-state-icon" style="font-size:1.5rem;">👥</div>
<div class="empty-state-text" style="font-size:0.9rem;">Nessun gruppo configurato</div>
</div>
`;
return;
}
list.innerHTML = this.groups.map(g => {
const emailList = g.emails.split(',').map(e => e.trim()).filter(Boolean);
const tooltipText = emailList.length ? emailList.join('\n') : '(nessun indirizzo)';
const count = emailList.length;
return `
<div class="g-card">
<div class="g-tooltip"><strong>Contatti (${count}):</strong>\n${App.escapeHtml(tooltipText)}</div>
<div>
<div style="font-weight:600; font-size:0.9rem; color:var(--text-primary);">${App.escapeHtml(g.name)}</div>
<div style="font-size:0.75rem; color:var(--text-muted); margin-top:2px;">
${count} indirizz${count === 1 ? 'o' : 'i'} email
</div>
</div>
<div style="display:flex; gap:6px; justify-content: flex-end; margin-top:4px;">
<button class="btn btn-ghost btn-sm group-btn-edit" data-id="${g.id}" style="height:24px; font-size:0.72rem; padding:0 6px;">✏️</button>
<button class="btn btn-ghost btn-sm group-btn-delete" data-id="${g.id}" style="height:24px; font-size:0.72rem; padding:0 6px; color:var(--error);">🗑️</button>
</div>
</div>
`;
}).join('');
// Bind group buttons
list.querySelectorAll('.group-btn-edit').forEach(btn => {
btn.addEventListener('click', () => this.openGroupEditor(parseInt(btn.dataset.id, 10)));
});
list.querySelectorAll('.group-btn-delete').forEach(btn => {
btn.addEventListener('click', () => this.deleteGroup(parseInt(btn.dataset.id, 10)));
});
},
openGroupEditor(id) {
this.editingGroupId = id;
const group = id ? this.groups.find(g => g.id === id) : null;
const modal = document.getElementById('group-editor-modal');
modal.style.display = 'flex';
document.getElementById('group-modal-title').textContent = id ? 'Modifica Gruppo' : 'Nuovo Gruppo';
document.getElementById('group-name').value = group ? group.name : '';
document.getElementById('group-emails').value = group ? group.emails : '';
document.getElementById('group-modal-close').onclick = () => this.closeGroupEditor();
document.getElementById('group-cancel').onclick = () => this.closeGroupEditor();
document.getElementById('group-save').onclick = () => this.saveGroup();
modal.onclick = (e) => { if (e.target === modal) this.closeGroupEditor(); };
},
closeGroupEditor() {
const modal = document.getElementById('group-editor-modal');
if (modal) modal.style.display = 'none';
this.editingGroupId = null;
},
async saveGroup() {
const name = document.getElementById('group-name').value.trim();
const emails = document.getElementById('group-emails').value.trim();
if (!name) { Toast.warning('Inserisci un nome per il gruppo'); return; }
if (!emails) { Toast.warning('Inserisci almeno un indirizzo email'); return; }
const parsedEmails = emails.split(',').map(e => e.trim()).filter(Boolean);
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const invalid = parsedEmails.filter(e => !emailRegex.test(e));
if (invalid.length > 0) {
Toast.warning('I seguenti indirizzi non sono validi: ' + invalid.join(', '));
return;
}
const saveBtn = document.getElementById('group-save');
saveBtn.disabled = true;
saveBtn.textContent = 'Salvataggio...';
try {
const payload = { agent_id: this.agentId, name, emails: parsedEmails.join(', ') };
if (this.editingGroupId) {
await App.api(`/api/email/address-groups/${this.editingGroupId}`, {
method: 'PUT',
body: JSON.stringify(payload),
});
Toast.success('Gruppo aggiornato');
} else {
await App.api('/api/email/address-groups', {
method: 'POST',
body: JSON.stringify(payload),
});
Toast.success('Gruppo creato');
}
this.closeGroupEditor();
await this.loadGroups();
} catch (err) {
Toast.error('Errore salvataggio: ' + err.message);
} finally {
saveBtn.disabled = false;
saveBtn.textContent = 'Salva Gruppo';
}
},
async deleteGroup(id) {
const ok = await App.confirm('Elimina Gruppo', 'Sei sicuro di voler eliminare questo gruppo di indirizzi?');
if (!ok) return;
try {
await App.api(`/api/email/address-groups/${id}`, { method: 'DELETE' });
Toast.success('Gruppo eliminato');
await this.loadGroups();
} catch (err) {
Toast.error('Errore eliminazione: ' + err.message);
}
}
};
window.MailManagementView = MailManagementView;