tutto di troppo: fix visualizzazione email. style: riviste icone invio ed elimina, postato aggiungi ticket al grppo e informazioni laterali da a cc.

This commit is contained in:
Gabriele Cimaschi committed 2026-09-09 11:04:14 +02:00
1 parent 9ae0cec0ca
commit cf500aabb3
7 files changed
+743 -85

No files matched your search

+229 -64
View File
@@ -4,7 +4,7 @@
*/
const TicketDetailView = {
ticketId: null,
htmlMode: false,
htmlMode: true,
originalValues: {},
noteAttachments: [],
@@ -13,13 +13,15 @@ const TicketDetailView = {
const body = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : '';
const subject = document.getElementById('note-subject') ? document.getElementById('note-subject').value.trim() : '';
const time_unit = document.getElementById('note-time-units') ? document.getElementById('note-time-units').value.trim() : '';
const ticket_state_id = document.getElementById('note-ticket-state') ? document.getElementById('note-ticket-state').value : '';
if ((body !== '<p><br></p>' && body !== '') || subject !== '' || time_unit !== '' || this.noteAttachments.length > 0) {
if ((body !== '<p><br></p>' && body !== '') || subject !== '' || time_unit !== '' || this.noteAttachments.length > 0 || ticket_state_id !== '') {
App.saveDraft(this.ticketId, {
type: 'note',
body,
subject,
time_unit,
ticket_state_id,
attachments: [...this.noteAttachments]
});
} else {
@@ -27,6 +29,42 @@ const TicketDetailView = {
}
},
extractLinks(htmlOrText) {
if (!htmlOrText) return [];
const links = [];
const seen = new Set();
// 1. Extract <a href="..."> tags
const anchorRegex = /<a\s+(?:[^>]*?\s+)?href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi;
let match;
while ((match = anchorRegex.exec(htmlOrText)) !== null) {
let url = match[1].trim().replace(/&amp;/g, '&');
let text = match[2].replace(/<[^>]+>/g, '').trim();
if (!text) text = url;
if (/^(javascript|mailto|tel|cid|#|\/api\/tickets\/attachments)/i.test(url)) {
continue;
}
if (!seen.has(url)) {
seen.add(url);
links.push({ url, text });
}
}
// 2. Extract plain HTTP/HTTPS URLs from text
const plainUrlRegex = /(https?:\/\/[^\s<"'>]+)/gi;
while ((match = plainUrlRegex.exec(htmlOrText)) !== null) {
let url = match[1].trim().replace(/[.,;)]+$/, '');
if (/^\/api\/tickets\/attachments/i.test(url)) continue;
if (!seen.has(url)) {
seen.add(url);
links.push({ url, text: url });
}
}
return links;
},
updateNoteAttachmentList() {
const listEl = document.getElementById('note-file-list');
if (!listEl) return;
@@ -158,8 +196,8 @@ const TicketDetailView = {
<div class="ticket-detail-main">
<!-- Header -->
<div class="card">
<div class="ticket-header">
<div class="ticket-header-info">
<div class="ticket-header" style="display:flex; justify-content:space-between; align-items:flex-start; gap:16px; flex-wrap:wrap;">
<div class="ticket-header-info" style="flex:1; min-width:280px;">
<div class="ticket-number" style="display:inline-flex; align-items:center; gap:6px;">
<span class="copy-ticket-btn" data-tn="${ticket.tn}" style="cursor: pointer; font-size: 0.85rem;" title="Copia numero ticket">📋</span>
#${ticket.tn}
@@ -177,6 +215,34 @@ const TicketDetailView = {
${ticket.lock_name === 'lock' ? '<span class="badge" style="background:var(--warning-bg);color:var(--warning);">🔒 Bloccato</span>' : ''}
</div>
</div>
<!-- Gruppi Ticket sul lato destro dell'intestazione in due righe -->
<div id="ticket-groups-panel" style="display:flex; flex-direction:column; align-items:flex-end; gap:6px; font-size:0.8rem;">
<div id="ticket-groups-list" style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; justify-content:flex-end;">
${groupsData.asMaster.length === 0 && groupsData.asMember.length === 0 ? `
<span style="font-size:0.78rem; color:var(--text-muted);">Nessun gruppo associato</span>
` : ''}
${groupsData.asMaster.map(g => `
<span style="font-size:0.8rem;">
<span style="color:var(--accent-primary); font-weight:bold;">👑 Master in:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</span>
`).join('')}
${groupsData.asMember.map(g => `
<span style="font-size:0.8rem;">
<span style="color:var(--text-secondary); font-weight:bold;">🔗 Membro di:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</span>
`).join('')}
</div>
<div style="display:flex; align-items:center; gap:4px; margin-top:2px;">
<select class="form-select" id="group-association-select" style="padding:4px 20px 4px 8px; font-size:0.78rem; height:28px; margin:0;">
<option value="">-- Seleziona --</option>
</select>
<button class="btn btn-primary btn-sm" id="btn-associate-to-group" style="height:28px; padding:0 8px; font-size:0.75rem;">+ Aggiungi</button>
</div>
</div>
</div>
</div>
@@ -241,10 +307,6 @@ const TicketDetailView = {
<input type="hidden" id="qe-customer-id" data-field="customer_id" value="${ticket.customer_id || ''}" />
<div id="qe-customer-suggestions" class="autocomplete-suggestions" style="display:none; top: 100%;"></div>
</div>
<div class="quick-edit-field">
<label class="quick-edit-label">Tempo (minuti)</label>
<input type="number" step="any" min="0" class="form-input" id="qe-time-unit" placeholder="minuti" style="padding: 6px 12px; height: 32px; font-size: 0.85rem;" />
</div>
<div class="quick-edit-field full-width">
<label class="quick-edit-label">Oggetto Nota (opzionale)</label>
<input type="text" class="form-input quick-edit-input" id="qe-note-subject" placeholder="Oggetto della nota..." style="padding: 6px 12px; height: 32px; font-size: 0.85rem;" />
@@ -253,7 +315,11 @@ const TicketDetailView = {
<label class="quick-edit-label">Nota / Commento</label>
<textarea class="quick-edit-textarea" id="qe-note" placeholder="Inserisci una nota..."></textarea>
</div>
<div style="margin-top:var(--space-md);display:flex;gap:var(--space-sm);justify-content:flex-end;grid-column:1 / -1;">
<div style="margin-top:var(--space-md);display:flex;gap:var(--space-sm);align-items:center;justify-content:flex-end;grid-column:1 / -1;flex-wrap:wrap;">
<div style="display:flex;align-items:center;gap:var(--space-xs);margin-right:auto;">
<label for="qe-time-unit" style="font-size:0.85rem;font-weight:600;color:var(--text-secondary);white-space:nowrap;">Tempo (minuti):</label>
<input type="number" step="any" min="0" class="form-input" id="qe-time-unit" placeholder="minuti" style="padding:4px 8px; height:32px; font-size:0.85rem; width:110px;" />
</div>
<button class="btn btn-ghost btn-sm" id="qe-reset">Reset</button>
<button class="btn btn-primary btn-sm" id="qe-save" disabled>Salva Modifiche</button>
</div>
@@ -262,7 +328,17 @@ const TicketDetailView = {
<!-- Add Note Form -->
<div class="add-note-form" style="margin-bottom: var(--space-lg);">
<div class="card-title">Aggiungi Nota</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: var(--space-md); flex-wrap:wrap; gap:var(--space-sm);">
<div class="card-title" style="margin-bottom:0;">Aggiungi Nota</div>
<div style="display:flex; align-items:center; gap:var(--space-xs);">
<label for="note-ticket-state" style="font-size:0.85rem; font-weight:600; color:var(--text-secondary);">Stato ticket:</label>
<select id="note-ticket-state" class="quick-edit-select" style="height:32px; padding:4px 8px; font-size:0.85rem; margin-bottom:0; width:auto;">
${(App.lookups.states || []).map(s =>
`<option value="${s.id}" ${s.id === ticket.ticket_state_id ? 'selected' : ''}>${App.escapeHtml(s.name)}</option>`
).join('')}
</select>
</div>
</div>
<div style="display:flex; gap:var(--space-md); margin-bottom:var(--space-md);">
<input type="text" class="note-subject-input" id="note-subject" placeholder="Oggetto (opzionale)" style="flex:1; margin-bottom:0;" />
</div>
@@ -304,34 +380,82 @@ const TicketDetailView = {
</div>
<div class="articles-timeline">
${articles.length > 0 ? articles.map(a => {
const hasHtml = (a.a_content_type || '').toLowerCase().includes('html') || a.a_body.includes('</') || a.a_body.includes('/>');
const hasHtml = a.has_html || (a.a_content_type || '').toLowerCase().includes('html') || /<(div|p|br|table|html|body|span|img|a|style)\b/i.test(a.html_body || a.a_body || '');
let processedBody = a.a_body || '';
let processedBody = (hasHtml && a.html_body) ? a.html_body : (a.a_body || '');
const articleAttachments = (attachments || []).filter(att => att.article_id === a.article_id);
const usedAttachmentIds = new Set();
if (hasHtml) {
if (hasHtml && this.htmlMode) {
articleAttachments.forEach(att => {
let replaced = false;
if (att.content_id) {
const cleanCid = att.content_id.replace(/[<>]/g, '').trim();
if (cleanCid) {
const escapedCid = cleanCid.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
const regex = new RegExp(`cid:<?${escapedCid}>?`, 'gi');
processedBody = processedBody.replace(regex, `/api/tickets/attachments/${att.id}`);
if (regex.test(processedBody)) {
processedBody = processedBody.replace(regex, `/api/tickets/attachments/${att.id}`);
replaced = true;
}
}
}
if (att.filename) {
const escapedFilename = att.filename.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
const regexFilename = new RegExp(`cid:<?${escapedFilename}>?`, 'gi');
processedBody = processedBody.replace(regexFilename, `/api/tickets/attachments/${att.id}`);
if (regexFilename.test(processedBody)) {
processedBody = processedBody.replace(regexFilename, `/api/tickets/attachments/${att.id}`);
replaced = true;
}
const regexSrc = new RegExp(`src=["']${escapedFilename}["']`, 'gi');
if (regexSrc.test(processedBody)) {
processedBody = processedBody.replace(regexSrc, `src="/api/tickets/attachments/${att.id}"`);
replaced = true;
}
const nameWithoutExt = att.filename.replace(/\.[^/.]+$/, '');
if (nameWithoutExt && nameWithoutExt.length > 3) {
const escapedBase = nameWithoutExt.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
const regexBase = new RegExp(`cid:<?${escapedBase}[^"'>\\s]*>?`, 'gi');
if (regexBase.test(processedBody)) {
processedBody = processedBody.replace(regexBase, `/api/tickets/attachments/${att.id}`);
replaced = true;
}
}
}
if (att.disposition === 'inline' || replaced) {
usedAttachmentIds.add(att.id);
}
});
if (!processedBody.includes('<base')) {
if (processedBody.includes('<head>')) {
processedBody = processedBody.replace('<head>', '<head><base target="_blank">');
} else {
processedBody = `<base target="_blank">${processedBody}`;
}
}
}
const displayBody = this.htmlMode && hasHtml
? `<iframe srcdoc="${processedBody.replace(/"/g, '&quot;')}" style="width:100%; border:none; background:var(--bg-card); border-radius:var(--radius-md); min-height:220px; font-family:inherit; color-scheme: dark;"></iframe>`
: `<div class="article-body">${App.escapeHtml(a.a_body || '')}</div>`;
const displayBody = (this.htmlMode && hasHtml)
? `<iframe id="article-iframe-${a.article_id}" srcdoc="${processedBody.replace(/"/g, '&quot;')}"
onload="try{this.style.height=(this.contentWindow.document.body.scrollHeight+30)+'px';}catch(e){}"
style="width:100%; border:none; background:#ffffff; color:#222222; border-radius:var(--radius-md); min-height:160px; padding:0; display:block;">
</iframe>`
: `<div class="article-body" style="white-space:pre-wrap; font-family:inherit;">${App.linkifyText(App.escapeHtml(a.a_body || ''))}</div>`;
const visibleAttachments = articleAttachments.filter(att => att.filename !== 'file-1' && att.filename !== 'file-2');
const visibleAttachments = articleAttachments.filter(att => {
const fname = (att.filename || '').toLowerCase();
if (fname.startsWith('file-1') || fname.startsWith('file-2') || fname.startsWith('file-3')) {
return false;
}
if (usedAttachmentIds.has(att.id)) {
return false;
}
return true;
});
const extractedLinks = this.extractLinks(processedBody || a.a_body || '');
return `
<div class="article-card sender-${(a.sender_type || 'system').toLowerCase()}">
@@ -341,8 +465,12 @@ const TicketDetailView = {
<span style="font-size: 0.72rem; color: var(--text-muted); font-family: monospace; margin-right: var(--space-xs);">ID: ${a.article_id}</span>
<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>
<button class="btn-delete-article" data-article-id="${a.article_id}" style="background:var(--bg-tertiary); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); cursor:pointer; padding:3px 6px; margin-left:var(--space-xs); display:inline-flex; align-items:center; justify-content:center; color:var(--danger); transition:all 0.15s ease; opacity:1;" title="Elimina Articolo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px; height:14px;"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
</button>
<button class="btn-email-article" data-article-id="${a.article_id}" style="background:var(--bg-tertiary); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); cursor:pointer; padding:3px 6px; margin-left:var(--space-xs); display:inline-flex; align-items:center; justify-content:center; color:var(--accent-secondary); transition:all 0.15s ease; opacity:1;" title="Rispondi via email (Quota questo articolo)">
<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>
</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;">
@@ -373,20 +501,68 @@ const TicketDetailView = {
</div>
</div>
</div>
${a.a_subject ? `<div class="article-subject">${App.escapeHtml(a.a_subject)}</div>` : ''}
${displayBody}
${a.a_subject ? `<div class="article-subject" style="font-weight:700; font-size:0.95rem; color:var(--text-primary); margin-bottom:var(--space-xs);">${App.escapeHtml(a.a_subject)}</div>` : ''}
${visibleAttachments.length > 0 ? `
<div class="article-attachments">
${visibleAttachments.map(att => `
<a href="/api/tickets/attachments/${att.id}" class="attachment-badge" target="_blank" download="${att.filename}" data-is-image="${(att.content_type || '').startsWith('image/')}">
<span>📎</span>
<strong>${App.escapeHtml(att.filename)}</strong>
<span class="attachment-size">(${Math.round(att.content_size / 1024)} KB)</span>
</a>
`).join('')}
<div class="article-content-layout" style="display: flex; gap: var(--space-md); align-items: flex-start; flex-wrap: wrap;">
<div class="article-body-main" style="flex: 1; min-width: 280px; width: 0;">
${displayBody}
${visibleAttachments.length > 0 ? `
<div class="article-attachments">
${visibleAttachments.map(att => `
<a href="/api/tickets/attachments/${att.id}" class="attachment-badge" target="_blank" download="${att.filename}" data-is-image="${(att.content_type || '').startsWith('image/')}">
<span>📎</span>
<strong>${App.escapeHtml(att.filename)}</strong>
<span class="attachment-size">(${Math.round(att.content_size / 1024)} KB)</span>
</a>
`).join('')}
</div>
` : ''}
</div>
` : ''}
<!-- Scheda a lato per ogni mail con Da: A: Cc: e lista Link -->
<div class="article-email-side-card" style="width: 260px; min-width: 220px; background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 12px; font-size: 0.78rem; align-self: flex-start; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-secondary); margin-bottom: 8px; border-bottom: 1px solid var(--border-subtle); padding-bottom: 4px; display: flex; align-items: center; gap: 4px; font-size: 0.8rem;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;color:var(--accent-primary);"><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>
Dettagli Mail
</div>
<div style="margin-bottom: 6px; word-break: break-all; line-height: 1.35;">
<strong style="color: var(--text-muted);">Da:</strong>
<span style="color: var(--text-primary); font-family: inherit;">${App.escapeHtml(a.a_from || '—')}</span>
</div>
<div style="margin-bottom: 6px; word-break: break-all; line-height: 1.35;">
<strong style="color: var(--text-muted);">A:</strong>
<span style="color: var(--text-primary); font-family: inherit;">${App.escapeHtml(a.a_to || '—')}</span>
</div>
${a.a_cc ? `
<div style="margin-bottom: 6px; word-break: break-all; line-height: 1.35;">
<strong style="color: var(--text-muted);">Cc:</strong>
<span style="color: var(--text-primary); font-family: inherit;">${App.escapeHtml(a.a_cc)}</span>
</div>
` : ''}
<div style="font-weight: 700; color: var(--text-secondary); margin-top: 12px; margin-bottom: 8px; border-bottom: 1px solid var(--border-subtle); padding-bottom: 4px; display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem;">
<span style="display: flex; align-items: center; gap: 4px;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;color:var(--accent-primary);"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
Link contenuti
</span>
<span class="badge" style="font-size: 0.7rem; padding: 1px 6px; background: var(--bg-card);">${extractedLinks.length}</span>
</div>
${extractedLinks.length > 0 ? `
<div class="article-links-minilist" style="display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto; padding-right: 2px;">
${extractedLinks.map(link => `
<a href="${App.escapeHtml(link.url)}" target="_blank" rel="noopener noreferrer" title="${App.escapeHtml(link.url)}" style="color: var(--accent-primary); text-decoration: none; word-break: break-all; font-size: 0.75rem; display: flex; align-items: flex-start; gap: 4px; line-height: 1.3; background: var(--bg-card); padding: 4px 6px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); transition: border-color 0.15s ease;">
<span style="flex-shrink:0; font-size: 0.7rem;">🌐</span>
<span style="overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-word;">${App.escapeHtml(link.text || link.url)}</span>
</a>
`).join('')}
</div>
` : `
<div style="color: var(--text-muted); font-size: 0.75rem; font-style: italic;">Nessun link presente</div>
`}
</div>
</div>
</div>
`;
}).join('') : `
@@ -497,37 +673,6 @@ const TicketDetailView = {
</div>
` : ''}
<!-- Panel per i Gruppi Ticket -->
<div class="sidebar-panel" id="ticket-groups-panel">
<div class="sidebar-panel-title">Gruppi Ticket</div>
<div id="ticket-groups-list" style="margin-bottom: var(--space-sm);">
${groupsData.asMaster.length === 0 && groupsData.asMember.length === 0 ? `
<div style="font-size:0.78rem; color:var(--text-muted); padding:4px 0;">Nessun gruppo associato</div>
` : ''}
${groupsData.asMaster.map(g => `
<div style="font-size:0.8rem; margin-bottom:4px;">
<span style="color:var(--accent-primary); font-weight:bold;">👑 Master in:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</div>
`).join('')}
${groupsData.asMember.map(g => `
<div style="font-size:0.8rem; margin-bottom:4px;">
<span style="color:var(--text-secondary); font-weight:bold;">🔗 Membro di:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</div>
`).join('')}
</div>
<div style="border-top:1px solid var(--border-subtle); padding-top:var(--space-xs); margin-top:var(--space-xs);">
<label class="quick-edit-label">Associa a Gruppo</label>
<div style="display:flex; gap:4px; margin-top:4px;">
<select class="form-select" id="group-association-select" style="padding:4px 20px 4px 8px; font-size:0.78rem; height:28px; margin:0; flex:1;">
<option value="">-- Seleziona --</option>
</select>
<button class="btn btn-primary btn-sm" id="btn-associate-to-group" style="height:28px; padding:0 8px; font-size:0.75rem;">+ Aggiungi</button>
</div>
</div>
</div>
</div>
</div>
`;
@@ -579,6 +724,9 @@ const TicketDetailView = {
if (document.getElementById('note-time-units')) {
document.getElementById('note-time-units').value = noteDraft.time_unit || '';
}
if (document.getElementById('note-ticket-state') && noteDraft.ticket_state_id) {
document.getElementById('note-ticket-state').value = noteDraft.ticket_state_id;
}
this.noteAttachments = noteDraft.attachments || [];
this.updateNoteAttachmentList();
}
@@ -891,6 +1039,19 @@ const TicketDetailView = {
}
});
// Shortcut for Quick Edit save
const quickEditContainer = container.querySelector('.quick-edit');
if (quickEditContainer) {
quickEditContainer.addEventListener('keydown', (e) => {
if (App.isShortcutTriggered(e, 'quick_edit_save')) {
e.preventDefault();
if (saveBtn && !saveBtn.disabled) {
saveBtn.click();
}
}
});
}
// Send note
const noteSendBtn = document.getElementById('note-send');
noteSendBtn.addEventListener('click', async () => {
@@ -925,6 +1086,9 @@ const TicketDetailView = {
return;
}
const noteStateSelect = document.getElementById('note-ticket-state');
const ticket_state_id = noteStateSelect ? noteStateSelect.value : null;
try {
noteSendBtn.disabled = true;
noteSendBtn.innerHTML = '<div class="spinner" style="width:14px;height:14px;border-width:2px;"></div> Invio...';
@@ -935,6 +1099,7 @@ const TicketDetailView = {
subject,
body,
time_unit,
ticket_state_id: ticket_state_id ? parseInt(ticket_state_id) : undefined,
attachments: this.noteAttachments.length > 0 ? this.noteAttachments : undefined
}),
});