feat: aggiunta possibilità di salvare delle note interne da caricare successivamente su OTRS.
This commit is contained in:
1 parent
146d4ed661
commit
3f3885130a
6 files changed
+837
No files matched your search
@@ -0,0 +1,571 @@
|
||||
/**
|
||||
* 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 = `
|
||||
<div class="note-temporanee-page" style="display: grid; grid-template-columns: 1fr 340px; gap: var(--space-lg); align-items: start;">
|
||||
|
||||
<!-- Left / Main Column: Note Editor -->
|
||||
<div class="main-editor-area">
|
||||
<div class="card" style="margin-bottom: 0;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: var(--space-md); flex-wrap:wrap; gap:10px;">
|
||||
<div style="display:flex; align-items:center; gap: 10px;">
|
||||
<div style="display:inline-flex; align-items:center; justify-content:center; width:36px; height:36px; background: linear-gradient(135deg, rgba(79, 70, 229, 0.2), rgba(16, 185, 129, 0.2)); border-radius: 50%; color: var(--accent-primary); font-size:1.2rem; font-weight:bold;">📝</div>
|
||||
<div>
|
||||
<h2 style="margin:0; font-size:1.2rem; font-weight:700; color:var(--text-primary);" id="editor-title-text">Nuova Nota Temporanea</h2>
|
||||
<p style="margin:2px 0 0 0; font-size:0.82rem; color:var(--text-secondary);">
|
||||
Le note vengono mantenute in locale finché non le elimini con il cestino 🗑️ o le invii a OTRS 🚀.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm" id="note-clear-form-btn" style="display:flex; align-items:center; gap:4px; font-size:0.82rem;">
|
||||
✨ Nuova Nota
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<form id="note-form" onsubmit="return false;" style="display:flex; flex-direction:column; gap: var(--space-md);">
|
||||
|
||||
<!-- Ticket Link / Selector -->
|
||||
<div class="form-group" style="position:relative;">
|
||||
<label class="form-label" style="font-weight:600; font-size:0.85rem; display:flex; justify-content:space-between;">
|
||||
<span>Ticket di riferimento (opzionale):</span>
|
||||
<span id="ticket-resolved-badge" style="font-weight:normal; color:var(--accent-primary); font-size:0.8rem; display:none;"></span>
|
||||
</label>
|
||||
<div style="position:relative; display:flex; align-items:center;">
|
||||
<input type="text" id="note-ticket-input" class="form-input" placeholder="Cerca o inserisci numero ticket (TN) o ID (es. 2024082610000123)..." autocomplete="off" style="padding-right:30px;" />
|
||||
<button type="button" id="note-ticket-clear-btn" style="position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; color:var(--text-muted); cursor:pointer; display:none;" title="Rimuovi ticket">✕</button>
|
||||
</div>
|
||||
<div id="note-ticket-suggestions" class="autocomplete-suggestions" style="display:none; position:absolute; top:100%; left:0; right:0; z-index:1000;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Subject and Type Row -->
|
||||
<div style="display:grid; grid-template-columns: 2fr 1fr; gap: var(--space-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="font-weight:600; font-size:0.85rem;">Oggetto / Titolo Nota:</label>
|
||||
<input type="text" id="note-subject" class="form-input" placeholder="Es. Appunti chiamata cliente / Verifica server..." />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="font-weight:600; font-size:0.85rem;">Tipo Nota:</label>
|
||||
<select id="note-type" class="form-select">
|
||||
<option value="note-internal">🔒 Nota Interna</option>
|
||||
<option value="note-external">🌐 Nota Esterna</option>
|
||||
<option value="email-external">✉️ Email Cliente</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Quill Rich Text Body Editor -->
|
||||
<div class="form-group">
|
||||
<label class="form-label" style="font-weight:600; font-size:0.85rem;">Corpo della Nota:</label>
|
||||
<div id="note-body-editor-wrapper" style="background:var(--bg-card); border-radius:var(--radius-md); border:1px solid var(--border-light); overflow:hidden;">
|
||||
<div id="note-body-editor" style="min-height:220px; font-size:0.9rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time Accounting Units -->
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; background:var(--bg-secondary); padding:var(--space-sm) var(--space-md); border-radius:var(--radius-md); border:1px solid var(--border-subtle); flex-wrap:wrap; gap:10px;">
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<span style="font-size:1.1rem;">⏱</span>
|
||||
<label for="note-time-units" style="font-size:0.85rem; font-weight:600; color:var(--text-primary); margin:0;">Tempo di consuntivazione (minuti):</label>
|
||||
</div>
|
||||
<input type="number" id="note-time-units" class="form-input" min="0" step="5" value="0" style="width:100px; text-align:right; font-weight:600; height:34px;" />
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div style="display:flex; align-items:center; justify-content:flex-end; gap:var(--space-sm); margin-top:var(--space-xs); border-top:1px solid var(--border-subtle); padding-top:var(--space-md);">
|
||||
<button type="button" class="btn btn-ghost" id="note-delete-current-btn" style="display:none; color:var(--error); margin-right:auto;">
|
||||
🗑️ Elimina Bozza
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" id="note-save-local-btn" style="min-width:140px; display:flex; align-items:center; justify-content:center; gap:6px;">
|
||||
💾 Salva in Locale
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="note-submit-otrs-btn" style="min-width:150px; display:flex; align-items:center; justify-content:center; gap:6px; background:var(--accent-primary);">
|
||||
🚀 Invia a OTRS
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side Column: Summary Menu of Saved Notes -->
|
||||
<div class="saved-notes-sidebar">
|
||||
<div class="card" style="margin-bottom:0; padding:var(--space-md);">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-sm);">
|
||||
<h3 style="margin:0; font-size:0.95rem; font-weight:700; color:var(--text-primary); display:flex; align-items:center; gap:6px;">
|
||||
<span>📋</span> Note Salvate
|
||||
<span class="badge" id="notes-sidebar-count" style="background:var(--accent-primary); color:#fff; font-size:0.75rem; padding:2px 8px; border-radius:10px;">0</span>
|
||||
</h3>
|
||||
<button class="btn btn-ghost btn-xs" id="notes-refresh-btn" title="Aggiorna lista note">🔄</button>
|
||||
</div>
|
||||
|
||||
<!-- Search filter in saved notes -->
|
||||
<div style="margin-bottom:var(--space-md); position:relative;">
|
||||
<input type="text" id="notes-sidebar-search" class="form-input" placeholder="Filtra note..." style="width:100%; height:32px; font-size:0.8rem; padding-left:28px;" />
|
||||
<span style="position:absolute; left:8px; top:50%; transform:translateY(-50%); font-size:0.8rem; color:var(--text-muted);">🔍</span>
|
||||
</div>
|
||||
|
||||
<!-- Saved Notes List Container -->
|
||||
<div id="saved-notes-list-container" style="max-height: calc(100vh - 270px); overflow-y:auto; display:flex; flex-direction:column; gap:var(--space-sm); padding-right:2px;">
|
||||
<div style="text-align:center; padding:20px; color:var(--text-muted); font-size:0.85rem;">
|
||||
Caricamento note...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 === '<p><br></p>') 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 = '<div style="text-align:center; padding:15px;"><div class="spinner" style="width:20px;height:20px;margin:0 auto;"></div></div>';
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div style="text-align:center; padding:25px 10px; color:var(--text-tertiary); font-size:0.85rem; border:1px dashed var(--border-subtle); border-radius:var(--radius-md);">
|
||||
${searchVal ? 'Nessuna nota corrisponde alla ricerca' : 'Nessuna nota temporanea salvata'}
|
||||
</div>
|
||||
`;
|
||||
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 `
|
||||
<div class="saved-note-card ${isSelected ? 'selected' : ''}" data-note-id="${n.id}" style="background: ${isSelected ? 'rgba(79,70,229,0.08)' : 'var(--bg-card)'}; border: 1px solid ${isSelected ? 'var(--accent-primary)' : 'var(--border-subtle)'}; border-radius: var(--radius-md); padding: 10px; transition: all 0.2s ease;">
|
||||
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:4px; font-size:0.75rem;">
|
||||
${n.ticket_tn ? `
|
||||
<span class="badge" style="background:var(--accent-primary); color:#fff; font-weight:700; font-size:0.72rem; padding:1px 6px;">#${n.ticket_tn}</span>
|
||||
` : `
|
||||
<span class="badge" style="background:var(--bg-secondary); color:var(--text-tertiary); font-size:0.72rem; padding:1px 6px;">Senza Ticket</span>
|
||||
`}
|
||||
<span style="color:var(--text-tertiary);">${App.formatDateTime(n.updated_at)}</span>
|
||||
</div>
|
||||
|
||||
<div style="font-size:0.85rem; font-weight:700; color:var(--text-primary); margin-bottom:4px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
|
||||
${App.escapeHtml(n.subject || '(Senza titolo)')}
|
||||
</div>
|
||||
|
||||
<div style="font-size:0.8rem; color:var(--text-secondary); line-height:1.3; margin-bottom:8px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;">
|
||||
${App.escapeHtml(textPreview)}
|
||||
</div>
|
||||
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; font-size:0.75rem; border-top:1px solid var(--border-subtle); padding-top:6px; margin-top:4px;">
|
||||
<div style="display:flex; align-items:center; gap:6px;">
|
||||
<span style="color:var(--text-tertiary); font-size:0.72rem;">${typeLabel}</span>
|
||||
${timeStr ? `<span style="font-weight:600; color:var(--accent-primary); font-size:0.72rem;">${timeStr}</span>` : ''}
|
||||
</div>
|
||||
|
||||
<div style="display:flex; align-items:center; gap:4px;">
|
||||
<button class="btn btn-ghost btn-xs btn-edit-note" data-id="${n.id}" title="Carica ed modifica nota" style="padding:2px 5px; font-size:0.75rem;">✏️</button>
|
||||
<button class="btn btn-ghost btn-xs btn-submit-note" data-id="${n.id}" title="Invia direttamente a OTRS" style="padding:2px 5px; font-size:0.75rem; color:var(--accent-primary);">🚀</button>
|
||||
<button class="btn btn-ghost btn-xs btn-delete-note" data-id="${n.id}" title="Elimina definitivamente dal DB locale" style="padding:2px 5px; font-size:0.75rem; color:var(--error);">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
}).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);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user