feat: funzione per l'invio della posta elettronica da otrs-turbo. fix: corretta consuntivazione automatica. feat: aggiunto link apri in otrs anche nel numero del ticket in cima

This commit is contained in:
Gabriele Cimaschi
2026-07-08 21:46:11 +02:00
parent 9a91c3d9f5
commit 5b914eb198
15 changed files with 1358 additions and 9 deletions
+71 -4
View File
@@ -52,10 +52,14 @@ const TicketDetailView = {
return sum + (isNaN(val) ? 0 : val);
}, 0);
// Display ticket number in the topbar
// Display ticket number in the topbar with OTRS link if available
const titleEl = document.getElementById('page-title');
if (titleEl) {
titleEl.innerHTML = `Ticket #${id}`;
if (data.otrsWebUrl) {
titleEl.innerHTML = `<a href="${data.otrsWebUrl}" target="_blank" style="color:inherit; text-decoration:none; display:inline-flex; align-items:center; gap:6px;" title="Apri in OTRS">Ticket #${ticket.tn || id} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;opacity:0.75;"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3"/></svg></a>`;
} else {
titleEl.innerHTML = `Ticket #${ticket.tn || id}`;
}
}
this.originalValues = {
@@ -184,6 +188,11 @@ const TicketDetailView = {
</div>
<div style="display:flex; gap:var(--space-md); align-items:center;">
<input type="number" step="any" min="0" class="note-subject-input" id="note-time-units" placeholder="Tempo (minuti)" style="width:140px; margin-bottom:0; height:32px; padding:4px 10px; font-size:0.85rem;" />
<button class="btn btn-ghost btn-sm" id="btn-open-email-compose" style="height:32px; display:flex; align-items:center; gap:var(--space-xs); border-color:var(--accent-secondary); color:var(--accent-secondary);"
data-ticket-id="${ticket.id}" data-ticket-tn="${ticket.tn}" data-ticket-title="${App.escapeHtml(ticket.title)}" data-customer-email="${App.escapeHtml(ticket.customer_email || '')}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
Invia Email
</button>
<button class="btn btn-primary btn-sm" id="note-send" style="height:32px; display:flex; align-items:center; gap:var(--space-xs);">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>
Invia Nota
@@ -219,6 +228,7 @@ const TicketDetailView = {
<span class="article-from">${App.escapeHtml(a.a_from || a.creator_first + ' ' + a.creator_last || 'Sistema')}</span>
${a.channel_name ? `<span style="font-size:0.72rem;color:var(--text-muted);">via ${a.channel_name}</span>` : ''}
<button class="btn-delete-article" data-article-id="${a.article_id}" style="background:none; border:none; cursor:pointer; font-size:0.85rem; padding: 2px; margin-left: var(--space-xs); display:inline-flex; align-items:center; opacity: 0.6; transition: opacity 0.2s;" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.6" title="Elimina Articolo">🗑️</button>
<button class="btn-email-article" data-article-id="${a.article_id}" style="background:none; border:none; cursor:pointer; font-size:0.85rem; padding: 2px; margin-left: var(--space-xs); display:inline-flex; align-items:center; opacity: 0.6; transition: opacity 0.2s;" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=0.6" title="Rispondi via email (Quota questo articolo)">✉️</button>
</div>
<div style="display:flex; gap: var(--space-sm); align-items:center;">
<div class="time-edit-container" data-article-id="${a.article_id}" style="display:inline-flex; align-items:center; gap:4px; position:relative;">
@@ -409,7 +419,7 @@ const TicketDetailView = {
this.noteQuill = null;
}
this.bindEvents();
this.bindEvents(ticket, articles, container);
} catch (err) {
container.innerHTML = `
@@ -423,7 +433,7 @@ const TicketDetailView = {
}
},
bindEvents() {
bindEvents(ticket, articles, container) {
// Quick-edit change detection
const fields = document.querySelectorAll('.quick-edit-select, #qe-customer-user-id, #qe-customer-id');
const saveBtn = document.getElementById('qe-save');
@@ -670,6 +680,63 @@ const TicketDetailView = {
});
}
// Email Compose Button
const btnEmailCompose = document.getElementById('btn-open-email-compose');
if (btnEmailCompose) {
btnEmailCompose.addEventListener('click', () => {
if (window.EmailCompose) {
EmailCompose.open({
ticketId: parseInt(btnEmailCompose.dataset.ticketId, 10),
ticketTn: btnEmailCompose.dataset.ticketTn,
ticketTitle: btnEmailCompose.dataset.ticketTitle,
customerEmail: btnEmailCompose.dataset.customerEmail,
});
} else {
Toast.error('Modulo email non disponibile');
}
});
}
// Article Email Reply Buttons
document.querySelectorAll('.btn-email-article').forEach(btn => {
btn.addEventListener('click', () => {
const articleId = parseInt(btn.dataset.articleId, 10);
const article = articles.find(art => art.article_id == articleId);
if (!article) return;
if (window.EmailCompose) {
const hasHtml = (article.a_content_type || '').toLowerCase().includes('html') || article.a_body.includes('</') || article.a_body.includes('/>');
const quotedBody = hasHtml ? article.a_body : App.escapeHtml(article.a_body || '').replace(/\n/g, '<br>');
const initialBodyHtml = `
<p><br></p>
<p>Il ${App.formatDateTime(article.create_time)}, <strong>${App.escapeHtml(article.a_from || 'Sistema')}</strong> ha scritto:</p>
<blockquote style="border-left: 2px solid var(--border-subtle, #444); padding-left: 12px; margin-left: 8px; color: var(--text-secondary);">
${quotedBody}
</blockquote>
<p><br></p>
`;
// Extract sender email if possible for CC/To
let customerEmail = ticket.customer_email || '';
const matchEmail = (article.a_from || '').match(/<([^>]+)>/) || (article.a_from || '').match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/);
if (matchEmail) {
customerEmail = matchEmail[1];
}
EmailCompose.open({
ticketId: ticket.id,
ticketTn: ticket.tn,
ticketTitle: ticket.title,
customerEmail: customerEmail,
initialBodyHtml: initialBodyHtml,
});
} else {
Toast.error('Modulo email non disponibile');
}
});
});
// Retrodate Ticket Event Listeners
const btnEditTicketDate = document.getElementById('btn-edit-ticket-date');
const ticketDateEditor = document.getElementById('ticket-date-editor');