style: aggiunte iconcine per copia numero ticket e titolo nella pagina di riepilogo del ticket e copia titolo nella pagina ticket/a mio carico. style: rimosso link apri in otrs sul titolo dell'header di un ticket

This commit is contained in:
2026-07-14 08:24:44 +02:00
parent 964241d8ec
commit 47d11c311e
2 changed files with 42 additions and 27 deletions
+30 -15
View File
@@ -13,7 +13,7 @@ const TicketDetailView = {
const body = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : ''; const body = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : '';
const subject = document.getElementById('note-subject') ? document.getElementById('note-subject').value.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 time_unit = document.getElementById('note-time-units') ? document.getElementById('note-time-units').value.trim() : '';
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) {
App.saveDraft(this.ticketId, { App.saveDraft(this.ticketId, {
type: 'note', type: 'note',
@@ -96,7 +96,7 @@ const TicketDetailView = {
const data = await App.api(`/api/tickets/${id}`); const data = await App.api(`/api/tickets/${id}`);
const { ticket, articles, attachments } = data; const { ticket, articles, attachments } = data;
// No automatic tab creation here anymore // No automatic tab creation here anymore
let groupsData = { asMaster: [], asMember: [] }; let groupsData = { asMaster: [], asMember: [] };
try { try {
groupsData = await App.api(`/api/groups/by-ticket/${id}`); groupsData = await App.api(`/api/groups/by-ticket/${id}`);
@@ -153,13 +153,14 @@ const TicketDetailView = {
<div class="card"> <div class="card">
<div class="ticket-header"> <div class="ticket-header">
<div class="ticket-header-info"> <div class="ticket-header-info">
<div class="ticket-number">#${ticket.tn}</div> <div class="ticket-number" style="display:inline-flex; align-items:center; gap:6px;">
<h2 class="ticket-detail-title"> <span class="copy-ticket-btn" data-tn="${ticket.tn}" style="cursor: pointer; font-size: 0.85rem;" title="Copia numero ticket">📋</span>
${data.otrsWebUrl ? ` #${ticket.tn}
<a href="${data.otrsWebUrl}" target="_blank" style="color:inherit; text-decoration:none; border-bottom:1px dashed transparent; transition:border-bottom 0.1s ease;" onmouseover="this.style.borderBottom='1px dashed var(--text-primary)'" onmouseout="this.style.borderBottom='transparent'" title="Apri in OTRS"> -
${App.escapeHtml(ticket.title || '(senza titolo)')} </div>
</a> <h2 class="ticket-detail-title" style="display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap;">
` : App.escapeHtml(ticket.title || '(senza titolo)')} <span class="copy-ticket-btn" data-tn="${App.escapeHtml(ticket.title || '')}" style="cursor: pointer; font-size: 0.85rem;" title="Copia titolo ticket">📋</span>
${App.escapeHtml(ticket.title || '(senza titolo)')}
</h2> </h2>
<div class="ticket-meta-badges"> <div class="ticket-meta-badges">
<span class="badge badge-state" data-state-type="${(ticket.state_type || '').toLowerCase()}">${ticket.state_name}</span> <span class="badge badge-state" data-state-type="${(ticket.state_type || '').toLowerCase()}">${ticket.state_name}</span>
@@ -287,7 +288,7 @@ const TicketDetailView = {
<div class="articles-timeline"> <div class="articles-timeline">
${articles.length > 0 ? articles.map(a => { ${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.a_content_type || '').toLowerCase().includes('html') || a.a_body.includes('</') || a.a_body.includes('/>');
let processedBody = a.a_body || ''; let processedBody = a.a_body || '';
const articleAttachments = (attachments || []).filter(att => att.article_id === a.article_id); const articleAttachments = (attachments || []).filter(att => att.article_id === a.article_id);
@@ -521,7 +522,7 @@ const TicketDetailView = {
modules: { modules: {
toolbar: [ toolbar: [
['bold', 'italic', 'underline', 'strike'], ['bold', 'italic', 'underline', 'strike'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'list': 'ordered' }, { 'list': 'bullet' }],
['link', 'image'], ['link', 'image'],
['clean'] ['clean']
], ],
@@ -529,7 +530,7 @@ const TicketDetailView = {
bindings: { bindings: {
tab: { tab: {
key: 'Tab', key: 'Tab',
handler: function() { handler: function () {
return true; return true;
} }
} }
@@ -939,7 +940,7 @@ const TicketDetailView = {
if (window.EmailCompose) { if (window.EmailCompose) {
const hasHtml = (article.a_content_type || '').toLowerCase().includes('html') || article.a_body.includes('</') || article.a_body.includes('/>'); const hasHtml = (article.a_content_type || '').toLowerCase().includes('html') || article.a_body.includes('</') || article.a_body.includes('/>');
let processedQuoted = article.a_body || ''; let processedQuoted = article.a_body || '';
if (hasHtml) { if (hasHtml) {
const articleAttachments = (attachments || []).filter(att => att.article_id === article.article_id); const articleAttachments = (attachments || []).filter(att => att.article_id === article.article_id);
@@ -961,7 +962,7 @@ const TicketDetailView = {
} }
const quotedBody = hasHtml ? processedQuoted : App.escapeHtml(article.a_body || '').replace(/\n/g, '<br>'); const quotedBody = hasHtml ? processedQuoted : App.escapeHtml(article.a_body || '').replace(/\n/g, '<br>');
const initialBodyHtml = ` const initialBodyHtml = `
<p><br></p> <p><br></p>
<p>Il ${App.formatDateTime(article.create_time)}, <strong>${App.escapeHtml(article.a_from || 'Sistema')}</strong> ha scritto:</p> <p>Il ${App.formatDateTime(article.create_time)}, <strong>${App.escapeHtml(article.a_from || 'Sistema')}</strong> ha scritto:</p>
@@ -1292,7 +1293,7 @@ const TicketDetailView = {
if (doc && doc.readyState === 'complete') { if (doc && doc.readyState === 'complete') {
attachImageClick(); attachImageClick();
} }
} catch (_) {} } catch (_) { }
}); });
document.querySelectorAll('.attachment-badge[data-is-image="true"]').forEach(badge => { document.querySelectorAll('.attachment-badge[data-is-image="true"]').forEach(badge => {
@@ -1302,5 +1303,19 @@ const TicketDetailView = {
this.openImageLightbox(badge.href); this.openImageLightbox(badge.href);
}); });
}); });
document.querySelectorAll('.copy-ticket-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const tn = btn.dataset.tn;
if (tn) {
navigator.clipboard.writeText(tn).then(() => {
Toast.success('Copiato negli appunti!');
}).catch(err => {
Toast.error('Errore durante la copia: ' + err.message);
});
}
});
});
}, },
}; };
+12 -12
View File
@@ -39,18 +39,18 @@ const TicketListView = {
Filters.state.user_id = activeAgentId; Filters.state.user_id = activeAgentId;
Filters.save(); Filters.save();
} }
const params = Filters.toQueryParams(); const params = Filters.toQueryParams();
params.set('page', this.currentPage); params.set('page', this.currentPage);
params.set('per_page', this.perPage); params.set('per_page', this.perPage);
params.set('sort_by', this.sortBy); params.set('sort_by', this.sortBy);
params.set('sort_dir', this.sortDir); params.set('sort_dir', this.sortDir);
const searchInput = document.getElementById('global-search'); const searchInput = document.getElementById('global-search');
if (searchInput && searchInput.value.trim()) { if (searchInput && searchInput.value.trim()) {
params.set('search', searchInput.value.trim()); params.set('search', searchInput.value.trim());
} }
const data = await App.api(`/api/tickets?${params.toString()}`); const data = await App.api(`/api/tickets?${params.toString()}`);
this.renderContent(container, data); this.renderContent(container, data);
@@ -135,9 +135,9 @@ const TicketListView = {
</thead> </thead>
<tbody> <tbody>
${tickets.length > 0 ? tickets.map(t => { ${tickets.length > 0 ? tickets.map(t => {
const displayQueue = t.queue_name.includes('::') ? t.queue_name.split('::').pop() : t.queue_name; const displayQueue = t.queue_name.includes('::') ? t.queue_name.split('::').pop() : t.queue_name;
const shortQueue = displayQueue.length > 15 ? displayQueue.substring(0, 12) + '...' : displayQueue; const shortQueue = displayQueue.length > 15 ? displayQueue.substring(0, 12) + '...' : displayQueue;
return ` return `
<tr data-ticket-id="${t.id}" class="${this.selectedIds.has(String(t.id)) ? 'selected' : ''} ${this.selectedOrder[0] === String(t.id) ? 'first-selected' : ''}" style="cursor:pointer;"> <tr data-ticket-id="${t.id}" class="${this.selectedIds.has(String(t.id)) ? 'selected' : ''} ${this.selectedOrder[0] === String(t.id) ? 'first-selected' : ''}" style="cursor:pointer;">
<td> <td>
<span class="ticket-tn" style="display:inline-flex; align-items:center;"> <span class="ticket-tn" style="display:inline-flex; align-items:center;">
@@ -153,14 +153,14 @@ const TicketListView = {
</td> </td>
<td style="color:var(--text-tertiary);font-size:0.78rem;">${App.formatDateTime(t.create_time)}</td> <td style="color:var(--text-tertiary);font-size:0.78rem;">${App.formatDateTime(t.create_time)}</td>
<td><span class="badge badge-state" data-state-type="${(t.state_type || '').toLowerCase()}">${t.state_name}</span></td> <td><span class="badge badge-state" data-state-type="${(t.state_type || '').toLowerCase()}">${t.state_name}</span></td>
<td class="ticket-title-cell"><a href="#/tickets/${t.id}" class="ticket-title-link" onclick="event.stopPropagation()">${App.escapeHtml(t.title || '(senza titolo)')}</a></td> <td class="ticket-title-cell"><span class="copy-ticket-btn" data-tn="${t.title}" style="cursor: pointer; font-size: 0.82rem; display: inline-flex; align-items: center; margin-right: 4px;" onclick="event.stopPropagation();" title="Copia titolo ticket">📋</span><a href="#/tickets/${t.id}" class="ticket-title-link" onclick="event.stopPropagation()">${App.escapeHtml(t.title || '(senza titolo)')}</a></td>
<td class="queue-cell"><span class="badge badge-queue">${App.escapeHtml(shortQueue)}</span><div class="queue-tooltip">${App.escapeHtml(t.queue_name)}</div></td> <td class="queue-cell"><span class="badge badge-queue">${App.escapeHtml(shortQueue)}</span><div class="queue-tooltip">${App.escapeHtml(t.queue_name)}</div></td>
<td style="color:var(--text-secondary);font-size:0.82rem;">${t.owner_first || ''} ${t.owner_last || ''}</td> <td style="color:var(--text-secondary);font-size:0.82rem;">${t.owner_first || ''} ${t.owner_last || ''}</td>
<td style="color:var(--text-secondary);font-size:0.82rem;">${t.customer_first ? `${t.customer_first} ${t.customer_last}` : (t.customer_user_id || '—')}</td> <td style="color:var(--text-secondary);font-size:0.82rem;">${t.customer_first ? `${t.customer_first} ${t.customer_last}` : (t.customer_user_id || '—')}</td>
<td><span class="badge badge-priority" data-priority="${App.priorityIndex(t.priority_name)}">${App.priorityIndex(t.priority_name)}</span></td> <td><span class="badge badge-priority" data-priority="${App.priorityIndex(t.priority_name)}">${App.priorityIndex(t.priority_name)}</span></td>
</tr> </tr>
`; `;
}).join('') : ` }).join('') : `
<tr> <tr>
<td colspan="8"> <td colspan="8">
<div class="empty-state"> <div class="empty-state">
@@ -265,7 +265,7 @@ const TicketListView = {
if (e.target.closest('.ticket-tn-link') || e.target.closest('.ticket-title-link')) { if (e.target.closest('.ticket-tn-link') || e.target.closest('.ticket-title-link')) {
return; return;
} }
const id = String(row.dataset.ticketId); const id = String(row.dataset.ticketId);
if (this.selectedIds.has(id)) { if (this.selectedIds.has(id)) {
this.selectedIds.delete(id); this.selectedIds.delete(id);
@@ -289,7 +289,7 @@ const TicketListView = {
tr.classList.remove('first-selected'); tr.classList.remove('first-selected');
} }
}); });
this.updateBatchBar(); this.updateBatchBar();
}); });
}); });
@@ -312,7 +312,7 @@ const TicketListView = {
batchOpenTabsBtn.addEventListener('click', (e) => { batchOpenTabsBtn.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
if (this.selectedIds.size === 0) return; if (this.selectedIds.size === 0) return;
let count = 0; let count = 0;
this.selectedIds.forEach(id => { this.selectedIds.forEach(id => {
const row = document.querySelector(`.ticket-table tbody tr[data-ticket-id="${id}"]`); const row = document.querySelector(`.ticket-table tbody tr[data-ticket-id="${id}"]`);
@@ -325,7 +325,7 @@ const TicketListView = {
count++; count++;
} }
}); });
if (count > 0) { if (count > 0) {
Toast.success(`${count} ticket aperti in nuove schede!`); Toast.success(`${count} ticket aperti in nuove schede!`);
this.selectedIds.clear(); this.selectedIds.clear();