From ce904756dd09f1b4240d83d7182a5c18c3636c00 Mon Sep 17 00:00:00 2001 From: Gabriele Cimaschi Date: Wed, 23 Sep 2026 14:15:24 +0200 Subject: [PATCH] feat: aggiunto shortcut per aggiungere il ticket in una nuova scheda. style: spostata consuntivazione accanto ad utente cliente. --- public/js/app.js | 54 +++++++++++++++++- public/js/views/ricercaSpudorata.js | 2 +- public/js/views/shortcuts.js | 7 +++ public/js/views/ticketDetail.js | 9 +-- public/js/views/ticketList.js | 2 +- public/js/views/timeAccounting.js | 86 ++++++++++++++++++++++++++++- 6 files changed, 151 insertions(+), 9 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index d6f5e4b..731e099 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -69,6 +69,7 @@ const App = { ricerca_spudorata_open: { ctrl: true, alt: false, shift: true, key: 'f', enabled: true }, ticket_list_open: { ctrl: true, alt: false, shift: true, key: 'Backspace', enabled: true }, open_terminal_console: { ctrl: true, alt: true, shift: false, key: 't', enabled: true }, + add_ticket_tab: { ctrl: true, alt: true, shift: false, key: 's', enabled: true }, }; return defaults[id] || { ctrl: true, alt: false, shift: false, key: 'Enter', enabled: true }; }, @@ -123,11 +124,18 @@ const App = { return text.replace(urlRegex, url => `${url}`); }, - openTab(id, tn, title) { + openTab(id, tn, title, notify = false) { const exists = this.tabs.find(t => String(t.id) === String(id)); if (!exists) { this.tabs.push({ id, tn, title }); this.saveTabs(); + if (notify && typeof Toast !== 'undefined') { + Toast.success(`Ticket #${tn || id} aggiunto alle schede!`); + } + } else { + if (notify && typeof Toast !== 'undefined') { + Toast.info(`Ticket #${tn || id} è già nelle schede`); + } } }, @@ -311,6 +319,50 @@ const App = { if (consoleObj) { consoleObj.toggle(); } + } else if (this.isShortcutTriggered(e, 'add_ticket_tab')) { + e.preventDefault(); + // 1. Check if in ticket detail + const detailTabBtn = document.querySelector('.ticket-header .open-tab-btn, .ticket-detail .open-tab-btn'); + if (detailTabBtn) { + detailTabBtn.click(); + return; + } + + // 2. Check if multiple tickets selected in list + const selectedRows = document.querySelectorAll('.ticket-table tbody tr.selected, .results-table tbody tr.selected'); + if (selectedRows.length > 0) { + let count = 0; + selectedRows.forEach(row => { + const tabBtn = row.querySelector('.open-tab-btn'); + if (tabBtn) { + const id = tabBtn.dataset.id; + const tn = tabBtn.dataset.tn; + const title = tabBtn.dataset.title || ''; + if (id) { + this.openTab(parseInt(id, 10), tn, title); + count++; + } + } + }); + if (count > 0 && typeof Toast !== 'undefined') { + Toast.success(`${count} ticket aggiunti alle schede!`); + } + return; + } + + // 3. Check hovered or focused row in any ticket table/list + const hoveredRow = document.querySelector('.ticket-table tbody tr:hover, .results-table tbody tr:hover, .ticket-table tbody tr:focus-within, .results-table tbody tr:focus-within'); + if (hoveredRow) { + const tabBtn = hoveredRow.querySelector('.open-tab-btn'); + if (tabBtn) { + tabBtn.click(); + return; + } + } + + if (typeof Toast !== 'undefined') { + Toast.info('Seleziona o apri un ticket per aggiungerlo in scheda'); + } } }, true); diff --git a/public/js/views/ricercaSpudorata.js b/public/js/views/ricercaSpudorata.js index 828b399..81d81b2 100644 --- a/public/js/views/ricercaSpudorata.js +++ b/public/js/views/ricercaSpudorata.js @@ -280,7 +280,7 @@ const RicercaSpudorataView = { O ` : ''} - + ${t.tn} diff --git a/public/js/views/shortcuts.js b/public/js/views/shortcuts.js index 9679bb3..10f4ca8 100644 --- a/public/js/views/shortcuts.js +++ b/public/js/views/shortcuts.js @@ -86,6 +86,13 @@ const ShortcutsView = { context: 'Globale', defaultCombo: { ctrl: true, alt: true, shift: false, key: 't', enabled: true } }, + { + id: 'add_ticket_tab', + name: 'Aggiungi Ticket in Scheda', + description: 'Aggiunge il ticket visualizzato (o selezionato nell\'elenco) alle schede aperte in alto', + context: 'Globale / Dettaglio / Elenco', + defaultCombo: { ctrl: true, alt: true, shift: false, key: 's', enabled: true } + }, { id: 'create_ticket_submit', name: 'Invia Nuovo Ticket', diff --git a/public/js/views/ticketDetail.js b/public/js/views/ticketDetail.js index 0643111..42524c6 100644 --- a/public/js/views/ticketDetail.js +++ b/public/js/views/ticketDetail.js @@ -3453,7 +3453,7 @@ const TicketDetailView = { ${App.escapeHtml(ticket.title || '(senza titolo)')} - + @@ -3562,6 +3562,10 @@ const TicketDetailView = { +
+ + +
@@ -3591,9 +3595,6 @@ const TicketDetailView = { - - - + ${t.tn} diff --git a/public/js/views/timeAccounting.js b/public/js/views/timeAccounting.js index 212500b..98cf3e2 100644 --- a/public/js/views/timeAccounting.js +++ b/public/js/views/timeAccounting.js @@ -12,6 +12,39 @@ const TimeAccountingView = { viewMode: 'list', // 'list' | 'calendar' showAllTime: false, showNonWorkingDays: localStorage.getItem('otrs_ta_show_non_working_days') !== 'false', + groupByTicket: localStorage.getItem('otrs_ta_group_by_ticket') !== 'false', + + groupArticles(articles) { + if (!articles || articles.length === 0) return []; + if (!this.groupByTicket) { + return articles.map(a => ({ type: 'single', article: a })); + } + + const groups = []; + let currentGroup = null; + + for (const a of articles) { + const ticketId = a.ticket_id; + if (currentGroup && currentGroup.ticket_id === ticketId) { + currentGroup.articles.push(a); + } else { + if (currentGroup) { + groups.push(currentGroup); + } + currentGroup = { + type: 'group', + ticket_id: ticketId, + ticket_tn: a.ticket_tn, + ticket_title: a.ticket_title, + articles: [a] + }; + } + } + if (currentGroup) { + groups.push(currentGroup); + } + return groups; + }, formatLocalISO(d) { if (!d) return ''; @@ -174,6 +207,14 @@ const TimeAccountingView = {
+