feat: contatore tempo allocato, pagina miei ticket, unione ticket (da db)
This commit is contained in:
@@ -28,6 +28,12 @@ const TicketDetailView = {
|
||||
return sum + (isNaN(val) ? 0 : val);
|
||||
}, 0);
|
||||
|
||||
// Display total time in the topbar
|
||||
const titleEl = document.getElementById('page-title');
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = `Ticket #${id} <span style="font-size:0.85rem; font-weight:normal; color:var(--text-secondary); margin-left:var(--space-md); background:var(--bg-tertiary); padding:4px 10px; border-radius:var(--radius-sm); border:1px solid var(--border-light); display:inline-flex; align-items:center; gap:4px;">⏱ Tempo Consultivato: <strong>${totalTime} min</strong></span>`;
|
||||
}
|
||||
|
||||
this.originalValues = {
|
||||
ticket_state_id: ticket.ticket_state_id,
|
||||
ticket_priority_id: ticket.ticket_priority_id,
|
||||
@@ -119,11 +125,13 @@ const TicketDetailView = {
|
||||
<div class="card-title">Aggiungi Nota</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;" />
|
||||
<input type="number" step="any" min="0" class="note-subject-input" id="note-time-units" placeholder="Tempo (minuti)" style="width:140px; margin-bottom:0;" />
|
||||
</div>
|
||||
<textarea class="note-textarea" id="note-body" placeholder="Scrivi una nota interna..."></textarea>
|
||||
<div style="display:flex;gap:var(--space-sm);justify-content:flex-end;">
|
||||
<button class="btn btn-primary btn-sm" id="note-send">
|
||||
<div id="note-body-container" style="background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); overflow: hidden; margin-bottom: var(--space-md);">
|
||||
<div id="note-body-editor" style="min-height: 180px; font-family: inherit; font-size: 0.95rem; border: none; color: var(--text-primary);"></div>
|
||||
</div>
|
||||
<div style="display:flex; gap:var(--space-md); justify-content:flex-end; align-items:center; margin-top:var(--space-md);">
|
||||
<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-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
|
||||
</button>
|
||||
@@ -141,14 +149,14 @@ 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 displayBody = this.htmlMode && hasHtml
|
||||
? `<iframe srcdoc="${a.a_body.replace(/"/g, '"')}" 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 articleAttachments = (attachments || []).filter(att => att.article_id === a.article_id);
|
||||
const hasHtml = (a.a_content_type || '').toLowerCase().includes('html') || a.a_body.includes('</') || a.a_body.includes('/>');
|
||||
const displayBody = this.htmlMode && hasHtml
|
||||
? `<iframe srcdoc="${a.a_body.replace(/"/g, '"')}" 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>`;
|
||||
|
||||
return `
|
||||
const articleAttachments = (attachments || []).filter(att => att.article_id === a.article_id);
|
||||
|
||||
return `
|
||||
<div class="article-card sender-${(a.sender_type || 'system').toLowerCase()}">
|
||||
<div class="article-header">
|
||||
<div class="article-sender">
|
||||
@@ -189,7 +197,7 @@ const TicketDetailView = {
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('') : `
|
||||
}).join('') : `
|
||||
<div class="empty-state" style="padding:var(--space-lg);">
|
||||
<div class="empty-state-icon">💬</div>
|
||||
<div class="empty-state-text">Nessun articolo</div>
|
||||
@@ -207,6 +215,10 @@ const TicketDetailView = {
|
||||
<span class="meta-label">Numero</span>
|
||||
<span class="meta-value" style="font-family:monospace;">${ticket.tn}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Stato</span>
|
||||
<span class="meta-value"><span class="badge badge-state" data-state-type="${(ticket.state_type || '').toLowerCase()}">${ticket.state_name}</span></span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Creato</span>
|
||||
<span class="meta-value">
|
||||
@@ -294,6 +306,24 @@ const TicketDetailView = {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Initialize Quill Editor for Note
|
||||
if (window.Quill) {
|
||||
this.noteQuill = new Quill('#note-body-editor', {
|
||||
theme: 'snow',
|
||||
placeholder: 'Scrivi una nota interna...',
|
||||
modules: {
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
['link', 'image'],
|
||||
['clean']
|
||||
]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.noteQuill = null;
|
||||
}
|
||||
|
||||
this.bindEvents();
|
||||
|
||||
} catch (err) {
|
||||
@@ -371,12 +401,33 @@ const TicketDetailView = {
|
||||
// Send note
|
||||
const noteSendBtn = document.getElementById('note-send');
|
||||
noteSendBtn.addEventListener('click', async () => {
|
||||
const body = document.getElementById('note-body').value.trim();
|
||||
const subject = document.getElementById('note-subject').value.trim();
|
||||
let body = this.noteQuill ? this.noteQuill.root.innerHTML.trim() : '';
|
||||
if (body === '<p><br></p>') {
|
||||
body = '';
|
||||
}
|
||||
let subject = document.getElementById('note-subject').value.trim();
|
||||
const time_unit = document.getElementById('note-time-units').value.trim();
|
||||
|
||||
if (!body) {
|
||||
Toast.warning('Scrivi qualcosa prima di inviare');
|
||||
// If body is empty but subject is provided, fill body with subject text
|
||||
if (!body && subject) {
|
||||
body = `<p>${App.escapeHtml(subject)}</p>`;
|
||||
if (this.noteQuill) {
|
||||
this.noteQuill.root.innerHTML = body;
|
||||
}
|
||||
}
|
||||
|
||||
// If subject is empty but body is provided, fill subject with truncated plain text of body
|
||||
if (body && !subject) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = body;
|
||||
const plainText = tempDiv.textContent || tempDiv.innerText || '';
|
||||
subject = plainText.trim().substring(0, 50) || 'Nota interna';
|
||||
document.getElementById('note-subject').value = subject;
|
||||
}
|
||||
|
||||
// If both are still empty, show warning
|
||||
if (!body && !subject) {
|
||||
Toast.warning('Inserisci un oggetto o il corpo della nota prima di inviare');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -390,6 +441,7 @@ const TicketDetailView = {
|
||||
});
|
||||
|
||||
Toast.success(res.message || 'Nota aggiunta!');
|
||||
App.updateDailyTimer();
|
||||
this.render(this.ticketId);
|
||||
} catch (err) {
|
||||
Toast.error('Errore: ' + err.message);
|
||||
|
||||
Reference in New Issue
Block a user