/**
* 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 π.
`;
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);
}
}
};