fix: aggiunta nota ticket unito. feat: possiblità di visualizzare ticket in schede. feat: possibilità di creare un nuovo gruppo dall'interfaccia del ticket.

This commit is contained in:
2026-07-12 19:17:07 +02:00
parent e764c37d46
commit 748f777a31
10 changed files with 585 additions and 82 deletions
+131 -7
View File
@@ -13,6 +13,108 @@ const App = {
lookupsLoaded: false,
demotivationalPhrases: [],
motivationalPhrases: [],
drafts: {},
tabs: [],
loadTabs() {
try {
const saved = localStorage.getItem('otrs_turbo_tabs');
if (saved) this.tabs = JSON.parse(saved);
const savedDrafts = localStorage.getItem('otrs_turbo_drafts');
if (savedDrafts) this.drafts = JSON.parse(savedDrafts);
} catch (e) {}
this.renderTabs();
},
saveTabs() {
localStorage.setItem('otrs_turbo_tabs', JSON.stringify(this.tabs));
this.renderTabs();
},
saveDraft(ticketId, draft) {
if (!this.drafts[ticketId]) this.drafts[ticketId] = {};
this.drafts[ticketId][draft.type] = draft;
localStorage.setItem('otrs_turbo_drafts', JSON.stringify(this.drafts));
},
getDraft(ticketId, type) {
return this.drafts[ticketId] ? this.drafts[ticketId][type] : null;
},
clearDraft(ticketId, type) {
if (this.drafts[ticketId] && this.drafts[ticketId][type]) {
delete this.drafts[ticketId][type];
if (Object.keys(this.drafts[ticketId]).length === 0) {
delete this.drafts[ticketId];
}
localStorage.setItem('otrs_turbo_drafts', JSON.stringify(this.drafts));
}
},
openTab(id, tn, title) {
const exists = this.tabs.find(t => String(t.id) === String(id));
if (!exists) {
this.tabs.push({ id, tn, title });
this.saveTabs();
}
},
addTabWithoutRedirect(id, tn, title) {
const exists = this.tabs.find(t => String(t.id) === String(id));
if (!exists) {
this.tabs.push({ id, tn, title });
this.saveTabs();
} else {
if (title && exists.title !== title) {
exists.title = title;
this.saveTabs();
} else {
this.renderTabs();
}
}
},
closeTab(id, e) {
if (e) e.stopPropagation();
this.tabs = this.tabs.filter(t => String(t.id) !== String(id));
this.saveTabs();
// Clear drafts for closed tab
delete this.drafts[id];
localStorage.setItem('otrs_turbo_drafts', JSON.stringify(this.drafts));
const hash = window.location.hash;
if (hash === `#/tickets/${id}`) {
if (this.tabs.length > 0) {
window.location.hash = `#/tickets/${this.tabs[this.tabs.length - 1].id}`;
} else {
window.location.hash = '#/tickets';
}
}
},
renderTabs() {
const bar = document.getElementById('tabs-bar');
if (!bar) return;
if (this.tabs.length === 0) {
bar.style.display = 'none';
return;
}
bar.style.display = 'flex';
const currentHash = window.location.hash;
bar.innerHTML = this.tabs.map(t => {
const isActive = currentHash === `#/tickets/${t.id}`;
const displayTitle = t.title ? (t.title.length > 25 ? t.title.substring(0, 22) + '...' : t.title) : `#${t.tn}`;
return `
<div class="tab-item ${isActive ? 'active' : ''}" onclick="window.location.hash = '#/tickets/${t.id}'" title="${App.escapeHtml(t.title || '')}">
<span>${App.escapeHtml(displayTitle)}</span>
<button class="tab-close" onclick="App.closeTab(${t.id}, event)">✕</button>
</div>
`;
}).join('');
},
get currentAgentId() {
return parseInt(localStorage.getItem('activeAgentId') || '1', 10);
@@ -23,6 +125,7 @@ const App = {
this.initTheme();
this.loadDemotivationalPhrases();
this.loadMotivationalPhrases();
this.loadTabs();
Toast.init();
// Hash-based SPA router
@@ -112,6 +215,27 @@ const App = {
const hash = fullHash.split('?')[0];
const titleEl = document.getElementById('page-title');
// Save draft for previous ticket before routing
if (typeof TicketDetailView !== 'undefined' && TicketDetailView.ticketId) {
TicketDetailView.saveDraft();
if (typeof EmailCompose !== 'undefined') {
EmailCompose.saveDraft();
const overlay = document.getElementById('email-compose-overlay');
if (overlay) overlay.remove();
}
}
this.renderTabs();
const container = document.getElementById('view-container');
if (container) {
if (hash.match(/^#\/tickets\/(\d+)$/)) {
container.classList.add('ticket-view-active');
} else {
container.classList.remove('ticket-view-active');
}
}
// Update active nav link
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
@@ -243,11 +367,11 @@ const App = {
try {
// Sync LDAP customer users to local DB cache
await this.api('/api/customer-users/sync', { method: 'POST' });
await this.ensureLookups(true);
await this.initAgentSelector();
Toast.success('Dati locali (code, utenti, ecc.) aggiornati con successo!');
// If we are on a view that needs lookups, we can re-render it
const hash = window.location.hash;
if (hash === '#/tickets/bulk') {
@@ -325,7 +449,7 @@ const App = {
await this.ensureLookups();
// Populate select dropdown
select.innerHTML = (this.lookups.users || []).map(u =>
select.innerHTML = (this.lookups.users || []).map(u =>
`<option value="${u.id}">${u.first_name} ${u.last_name} (${u.login})</option>`
).join('');
@@ -505,7 +629,7 @@ const App = {
if (timerEl) {
timerEl.classList.add('clickable-auto-time');
timerEl.setAttribute('title', `Consuntivazione Automatica fine giornata (${remaining} m). Clicca per eseguire.`);
//timerEl.setAttribute('title', `Consuntivazione Automatica fine giornata (${remaining} m). Clicca per eseguire.`);
timerEl.onclick = triggerAction;
}
} else {
@@ -580,7 +704,7 @@ const App = {
async updateSidebarBadges() {
try {
const stats = await this.api('/api/dashboard/stats');
const badge = document.getElementById('open-ticket-count');
if (badge) {
badge.textContent = stats.total_open > 0 ? stats.total_open : '';
@@ -657,7 +781,7 @@ const App = {
btnCancel.addEventListener('click', () => cleanUp(false));
btnOk.addEventListener('click', () => cleanUp(true));
// Close on backdrop click
overlay.addEventListener('click', (e) => {
if (e.target === overlay) cleanUp(false);
@@ -807,7 +931,7 @@ const App = {
}
});
}
overlay.addEventListener('click', (e) => {
if (e.target === overlay) cleanUp(null);
});