/** * Note Temporanee View (public/js/views/noteTemporanee.js) * Temporary draft notes manager saved locally in SQLite until manually deleted or uploaded to OTRS. * Uses Quill Rich Text Editor for advanced formatting matching Ticket Detail notes. */ const NoteTemporaneeView = { currentNoteId: null, savedNotes: [], selectedTicket: null, quill: null, async render() { const container = document.getElementById('view-container'); if (!container) return; container.innerHTML = `
πŸ“

Nuova Nota Temporanea

Le note vengono mantenute in locale finchΓ© non le elimini con il cestino πŸ—‘οΈ o le invii a OTRS πŸš€.

⏱

πŸ“‹ Note Salvate 0

πŸ”
Caricamento note...
`; this.initQuill(); this.bindEvents(); await this.loadSavedNotes(); // Check if URL hash has parameters like ?ticket_id=... or ?ticket_tn=... const urlParams = new URLSearchParams((window.location.hash.split('?')[1]) || ''); const ticketTnParam = urlParams.get('ticket_tn') || urlParams.get('tn'); if (ticketTnParam) { const input = document.getElementById('note-ticket-input'); if (input) { input.value = ticketTnParam; this.resolveTicketInput(ticketTnParam); } } }, initQuill() { const editorEl = document.getElementById('note-body-editor'); if (!editorEl) return; if (window.Quill) { this.quill = new Quill('#note-body-editor', { theme: 'snow', placeholder: 'Scrivi il contenuto dettagliato della nota temporanea...', modules: { toolbar: [ ['bold', 'italic', 'underline', 'strike'], [{ 'list': 'ordered' }, { 'list': 'bullet' }], [{ 'color': [] }, { 'background': [] }], ['link', 'image'], ['clean'] ], keyboard: { bindings: { tab: { key: 'Tab', handler: function () { return true; } } } } } }); } }, getNoteBodyHtml() { if (this.quill) { const html = this.quill.root.innerHTML; if (html === '


') return ''; return html; } return ''; }, setNoteBodyHtml(html) { if (this.quill) { this.quill.root.innerHTML = html || ''; } }, bindEvents() { const saveBtn = document.getElementById('note-save-local-btn'); const submitBtn = document.getElementById('note-submit-otrs-btn'); const clearBtn = document.getElementById('note-clear-form-btn'); const deleteBtn = document.getElementById('note-delete-current-btn'); const refreshBtn = document.getElementById('notes-refresh-btn'); const searchInput = document.getElementById('notes-sidebar-search'); const ticketInput = document.getElementById('note-ticket-input'); const ticketClearBtn = document.getElementById('note-ticket-clear-btn'); if (saveBtn) saveBtn.addEventListener('click', () => this.saveLocalNote()); if (submitBtn) submitBtn.addEventListener('click', () => this.submitToOTRS()); if (clearBtn) clearBtn.addEventListener('click', () => this.clearForm()); if (deleteBtn) deleteBtn.addEventListener('click', () => { if (this.currentNoteId) this.deleteNote(this.currentNoteId); }); if (refreshBtn) refreshBtn.addEventListener('click', () => this.loadSavedNotes()); if (searchInput) { searchInput.addEventListener('input', () => this.renderSavedNotesList()); } if (ticketInput) { let debounceTimer = null; ticketInput.addEventListener('input', () => { const val = ticketInput.value.trim(); if (ticketClearBtn) ticketClearBtn.style.display = val ? 'block' : 'none'; clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { this.resolveTicketInput(val); }, 400); }); } if (ticketClearBtn) { ticketClearBtn.addEventListener('click', () => { if (ticketInput) ticketInput.value = ''; ticketClearBtn.style.display = 'none'; this.selectedTicket = null; this.updateTicketBadge(null); }); } }, async resolveTicketInput(val) { if (!val) { this.selectedTicket = null; this.updateTicketBadge(null); return; } try { const res = await App.api(`/api/tickets?search=${encodeURIComponent(val)}&per_page=5`); const tickets = res.tickets || []; if (tickets.length > 0) { const exact = tickets.find(t => String(t.tn) === String(val) || String(t.id) === String(val)) || tickets[0]; this.selectedTicket = exact; this.updateTicketBadge(exact); } else { this.selectedTicket = null; this.updateTicketBadge(null, `Nessun ticket trovato per "${val}"`); } } catch (_) { this.selectedTicket = null; this.updateTicketBadge(null); } }, updateTicketBadge(ticket, errorMsg = '') { const badge = document.getElementById('ticket-resolved-badge'); if (!badge) return; if (ticket) { badge.style.display = 'inline-block'; badge.style.color = 'var(--accent-primary)'; badge.innerHTML = `βœ“ Ticket #${ticket.tn} β€” ${App.escapeHtml(ticket.title ? (ticket.title.length > 40 ? ticket.title.substring(0, 37) + '...' : ticket.title) : '')}`; } else if (errorMsg) { badge.style.display = 'inline-block'; badge.style.color = 'var(--error)'; badge.innerHTML = App.escapeHtml(errorMsg); } else { badge.style.display = 'none'; badge.innerHTML = ''; } }, async loadSavedNotes() { try { const listContainer = document.getElementById('saved-notes-list-container'); if (listContainer) { listContainer.innerHTML = '
'; } this.savedNotes = await App.api('/api/notes'); this.renderSavedNotesList(); const countBadge = document.getElementById('notes-sidebar-count'); if (countBadge) countBadge.textContent = this.savedNotes.length; // Update sidebar nav badge const navBadge = document.getElementById('temp-notes-count'); if (navBadge) navBadge.textContent = this.savedNotes.length || ''; } catch (err) { console.error('Failed to load saved notes:', err); Toast.error('Errore nel caricamento delle note salvate'); } }, renderSavedNotesList() { const listContainer = document.getElementById('saved-notes-list-container'); if (!listContainer) return; const searchInput = document.getElementById('notes-sidebar-search'); const searchVal = searchInput ? searchInput.value.trim().toLowerCase() : ''; let list = this.savedNotes || []; if (searchVal) { list = list.filter(n => (n.ticket_tn && n.ticket_tn.toLowerCase().includes(searchVal)) || (n.subject && n.subject.toLowerCase().includes(searchVal)) || (n.body && n.body.toLowerCase().includes(searchVal)) || (n.ticket_title && n.ticket_title.toLowerCase().includes(searchVal)) ); } if (list.length === 0) { listContainer.innerHTML = `
${searchVal ? 'Nessuna nota corrisponde alla ricerca' : 'Nessuna nota temporanea salvata'}
`; return; } listContainer.innerHTML = list.map(n => { const isSelected = String(n.id) === String(this.currentNoteId); const isInternal = n.note_type === 'note-internal'; const isEmail = n.note_type === 'email-external'; const typeLabel = isInternal ? 'πŸ”’ Interna' : (isEmail ? 'βœ‰οΈ Email' : '🌐 Esterna'); const timeStr = n.time_units > 0 ? `⏱ ${n.time_units}m` : ''; // Strip HTML tags for clean text preview const tmpDiv = document.createElement('div'); tmpDiv.innerHTML = n.body || ''; const textPreview = tmpDiv.textContent || tmpDiv.innerText || ''; return `
${n.ticket_tn ? ` #${n.ticket_tn} ` : ` Senza Ticket `} ${App.formatDateTime(n.updated_at)}
${App.escapeHtml(n.subject || '(Senza titolo)')}
${App.escapeHtml(textPreview)}
${typeLabel} ${timeStr ? `${timeStr}` : ''}
`; }).join(''); // Bind item actions listContainer.querySelectorAll('.btn-edit-note').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); this.loadNoteIntoForm(parseInt(btn.dataset.id, 10)); }); }); listContainer.querySelectorAll('.btn-submit-note').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); this.submitNoteById(parseInt(btn.dataset.id, 10)); }); }); listContainer.querySelectorAll('.btn-delete-note').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); this.deleteNote(parseInt(btn.dataset.id, 10)); }); }); listContainer.querySelectorAll('.saved-note-card').forEach(card => { card.addEventListener('click', () => { this.loadNoteIntoForm(parseInt(card.dataset.noteId, 10)); }); }); }, loadNoteIntoForm(noteId) { const note = this.savedNotes.find(n => n.id === noteId); if (!note) return; this.currentNoteId = note.id; const editorTitle = document.getElementById('editor-title-text'); const ticketInput = document.getElementById('note-ticket-input'); const subjectInput = document.getElementById('note-subject'); const typeSelect = document.getElementById('note-type'); const timeInput = document.getElementById('note-time-units'); const deleteBtn = document.getElementById('note-delete-current-btn'); const ticketClearBtn = document.getElementById('note-ticket-clear-btn'); if (editorTitle) editorTitle.textContent = `Modifica Nota #${note.id}`; if (ticketInput) ticketInput.value = note.ticket_tn || (note.ticket_id ? String(note.ticket_id) : ''); if (ticketClearBtn) ticketClearBtn.style.display = ticketInput && ticketInput.value ? 'block' : 'none'; if (subjectInput) subjectInput.value = note.subject || ''; if (typeSelect) typeSelect.value = note.note_type || 'note-internal'; this.setNoteBodyHtml(note.body || ''); if (timeInput) timeInput.value = note.time_units || 0; if (deleteBtn) deleteBtn.style.display = 'inline-flex'; if (note.ticket_tn || note.ticket_id) { this.resolveTicketInput(note.ticket_tn || String(note.ticket_id)); } else { this.selectedTicket = null; this.updateTicketBadge(null); } this.renderSavedNotesList(); }, clearForm() { this.currentNoteId = null; const editorTitle = document.getElementById('editor-title-text'); const ticketInput = document.getElementById('note-ticket-input'); const subjectInput = document.getElementById('note-subject'); const typeSelect = document.getElementById('note-type'); const timeInput = document.getElementById('note-time-units'); const deleteBtn = document.getElementById('note-delete-current-btn'); const ticketClearBtn = document.getElementById('note-ticket-clear-btn'); if (editorTitle) editorTitle.textContent = 'Nuova Nota Temporanea'; if (ticketInput) ticketInput.value = ''; if (ticketClearBtn) ticketClearBtn.style.display = 'none'; if (subjectInput) subjectInput.value = ''; if (typeSelect) typeSelect.value = 'note-internal'; this.setNoteBodyHtml(''); if (timeInput) timeInput.value = '0'; if (deleteBtn) deleteBtn.style.display = 'none'; this.selectedTicket = null; this.updateTicketBadge(null); this.renderSavedNotesList(); }, async saveLocalNote() { const ticketInput = document.getElementById('note-ticket-input'); const subjectInput = document.getElementById('note-subject'); const typeSelect = document.getElementById('note-type'); const timeInput = document.getElementById('note-time-units'); const bodyVal = this.getNoteBodyHtml(); if (!bodyVal) { Toast.error('Inserisci il corpo della nota prima di salvare'); return; } const payload = { id: this.currentNoteId, ticket_id: this.selectedTicket ? this.selectedTicket.id : null, ticket_tn: ticketInput ? ticketInput.value.trim() : null, ticket_title: this.selectedTicket ? this.selectedTicket.title : null, subject: subjectInput ? subjectInput.value.trim() : '', body: bodyVal, time_units: timeInput ? parseFloat(timeInput.value) || 0 : 0, note_type: typeSelect ? typeSelect.value : 'note-internal' }; try { const res = await App.api('/api/notes', { method: 'POST', body: payload }); Toast.success('Nota temporanea salvata nel database locale!'); this.currentNoteId = res.id; await this.loadSavedNotes(); } catch (err) { console.error(err); Toast.error('Errore nel salvataggio della nota: ' + err.message); } }, async submitToOTRS() { const bodyVal = this.getNoteBodyHtml(); if (!bodyVal) { Toast.error('Inserisci il corpo della nota prima di inviare a OTRS'); return; } // First ensure the note is saved locally await this.saveLocalNote(); if (!this.currentNoteId) return; await this.submitNoteById(this.currentNoteId); }, async submitNoteById(noteId) { const note = this.savedNotes.find(n => n.id === noteId); if (!note) return; if (!note.ticket_tn && !note.ticket_id) { Toast.error('Inserisci un ticket di riferimento (TN o ID) prima di inviare a OTRS'); return; } if (!confirm(`Sei sicuro di voler inviare questa nota a OTRS per il ticket #${note.ticket_tn || note.ticket_id}? La nota verrΓ  poi rimossa dal database locale.`)) { return; } try { const res = await App.api(`/api/notes/${noteId}/submit`, { method: 'POST' }); Toast.success(res.message || 'Nota inviata con successo a OTRS!'); if (String(this.currentNoteId) === String(noteId)) { this.clearForm(); } await this.loadSavedNotes(); // Trigger daily timer refresh if time units were added if (note.time_units > 0 && App.updateDailyTimer) { App.updateDailyTimer(); } } catch (err) { console.error(err); Toast.error('Errore invio OTRS: ' + err.message); } }, async deleteNote(noteId) { if (!confirm('Sei sicuro di voler eliminare questa nota temporanea dal database locale?')) { return; } try { await App.api(`/api/notes/${noteId}`, { method: 'DELETE' }); Toast.success('Nota temporanea eliminata dal DB locale'); if (String(this.currentNoteId) === String(noteId)) { this.clearForm(); } await this.loadSavedNotes(); } catch (err) { console.error(err); Toast.error('Errore eliminazione nota: ' + err.message); } } };