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:
+131
-7
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user