feat: aggiunto shortcut per aggiungere il ticket in una nuova scheda. style: spostata consuntivazione accanto ad utente cliente.
This commit is contained in:
1 parent
107ba10689
commit
ce904756dd
6 files changed
+151
-9
No files matched your search
+53
-1
@@ -69,6 +69,7 @@ const App = {
|
|||||||
ricerca_spudorata_open: { ctrl: true, alt: false, shift: true, key: 'f', enabled: true },
|
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 },
|
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 },
|
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 };
|
return defaults[id] || { ctrl: true, alt: false, shift: false, key: 'Enter', enabled: true };
|
||||||
},
|
},
|
||||||
@@ -123,11 +124,18 @@ const App = {
|
|||||||
return text.replace(urlRegex, url => `<a href="${url}" target="_blank" rel="noopener noreferrer" style="color:var(--accent-primary); text-decoration:underline;">${url}</a>`);
|
return text.replace(urlRegex, url => `<a href="${url}" target="_blank" rel="noopener noreferrer" style="color:var(--accent-primary); text-decoration:underline;">${url}</a>`);
|
||||||
},
|
},
|
||||||
|
|
||||||
openTab(id, tn, title) {
|
openTab(id, tn, title, notify = false) {
|
||||||
const exists = this.tabs.find(t => String(t.id) === String(id));
|
const exists = this.tabs.find(t => String(t.id) === String(id));
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
this.tabs.push({ id, tn, title });
|
this.tabs.push({ id, tn, title });
|
||||||
this.saveTabs();
|
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) {
|
if (consoleObj) {
|
||||||
consoleObj.toggle();
|
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);
|
}, true);
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ const RicercaSpudorataView = {
|
|||||||
<span style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:#1070ca; color:#fff; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; vertical-align:middle; line-height:16px;">O</span>
|
<span style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:#1070ca; color:#fff; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; vertical-align:middle; line-height:16px;">O</span>
|
||||||
</a>
|
</a>
|
||||||
` : ''}
|
` : ''}
|
||||||
<button class="open-tab-btn" data-id="${t.id}" data-tn="${t.tn}" data-title="${App.escapeHtml(t.title || '')}" onclick="App.openTab(${t.id}, '${t.tn}', this.dataset.title); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-right:6px; cursor:pointer; line-height:16px;">+</button>
|
<button class="open-tab-btn" data-id="${t.id}" data-tn="${t.tn}" data-title="${App.escapeHtml(t.title || '')}" onclick="App.openTab(${t.id}, '${t.tn}', this.dataset.title, true); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-right:6px; cursor:pointer; line-height:16px;">+</button>
|
||||||
<a href="#/tickets/${t.id}" class="ticket-tn-link" onclick="event.stopPropagation()">${t.tn}</a>
|
<a href="#/tickets/${t.id}" class="ticket-tn-link" onclick="event.stopPropagation()">${t.tn}</a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ const ShortcutsView = {
|
|||||||
context: 'Globale',
|
context: 'Globale',
|
||||||
defaultCombo: { ctrl: true, alt: true, shift: false, key: 't', enabled: true }
|
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',
|
id: 'create_ticket_submit',
|
||||||
name: 'Invia Nuovo Ticket',
|
name: 'Invia Nuovo Ticket',
|
||||||
|
|||||||
@@ -3453,7 +3453,7 @@ const TicketDetailView = {
|
|||||||
<span class="copy-full-ticket-title" data-full="${ticket.tn} - ${App.escapeHtml(ticket.title || '(senza titolo)')}" style="cursor: pointer;" title="Clicca per copiare: ${ticket.tn} - ${App.escapeHtml(ticket.title || '')}">
|
<span class="copy-full-ticket-title" data-full="${ticket.tn} - ${App.escapeHtml(ticket.title || '(senza titolo)')}" style="cursor: pointer;" title="Clicca per copiare: ${ticket.tn} - ${App.escapeHtml(ticket.title || '')}">
|
||||||
${App.escapeHtml(ticket.title || '(senza titolo)')}
|
${App.escapeHtml(ticket.title || '(senza titolo)')}
|
||||||
</span>
|
</span>
|
||||||
<button class="open-tab-btn" data-id="${ticket.id}" data-tn="${ticket.tn}" data-title="${App.escapeHtml(ticket.title || '(senza titolo)')}" onclick="App.openTab(${ticket.id}, '${ticket.tn}', this.dataset.title); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-left:6px; cursor:pointer; line-height:16px;">+</button>
|
<button class="open-tab-btn" data-id="${ticket.id}" data-tn="${ticket.tn}" data-title="${App.escapeHtml(ticket.title || '(senza titolo)')}" onclick="App.openTab(${ticket.id}, '${ticket.tn}', this.dataset.title, true); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-left:6px; cursor:pointer; line-height:16px;">+</button>
|
||||||
<button class="assign-open-tab-btn" data-id="${ticket.id}" data-tn="${ticket.tn}" data-title="${App.escapeHtml(ticket.title || '(senza titolo)')}" onclick="App.assignAndOpenTab(${ticket.id}, '${ticket.tn}', this.dataset.title); event.stopPropagation();" title="Assegna a me ed apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; height:20px; padding:0 8px; background:var(--accent-primary); color:#fff; border:none; border-radius:var(--radius-full); font-size:11px; font-weight:600; font-family:inherit; margin-left:6px; cursor:pointer; gap:3px;">
|
<button class="assign-open-tab-btn" data-id="${ticket.id}" data-tn="${ticket.tn}" data-title="${App.escapeHtml(ticket.title || '(senza titolo)')}" onclick="App.assignAndOpenTab(${ticket.id}, '${ticket.tn}', this.dataset.title); event.stopPropagation();" title="Assegna a me ed apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; height:20px; padding:0 8px; background:var(--accent-primary); color:#fff; border:none; border-radius:var(--radius-full); font-size:11px; font-weight:600; font-family:inherit; margin-left:6px; cursor:pointer; gap:3px;">
|
||||||
👤+ Assegna & Scheda
|
👤+ Assegna & Scheda
|
||||||
</button>
|
</button>
|
||||||
@@ -3562,6 +3562,10 @@ const TicketDetailView = {
|
|||||||
<input type="hidden" id="qe-customer-id" data-field="customer_id" value="${ticket.customer_id || ''}" />
|
<input type="hidden" id="qe-customer-id" data-field="customer_id" value="${ticket.customer_id || ''}" />
|
||||||
<div id="qe-customer-suggestions" class="autocomplete-suggestions" style="display:none; top: 100%;"></div>
|
<div id="qe-customer-suggestions" class="autocomplete-suggestions" style="display:none; top: 100%;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="quick-edit-field">
|
||||||
|
<label class="quick-edit-label">Consuntivazione Tempo (min)</label>
|
||||||
|
<input type="number" step="any" min="0" class="form-input" id="note-time-units" placeholder="minuti" style="padding: 6px 12px; height: 32px; font-size: 0.85rem;" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Integrated Note & Article Editor inside Modifica Rapida -->
|
<!-- Integrated Note & Article Editor inside Modifica Rapida -->
|
||||||
@@ -3591,9 +3595,6 @@ const TicketDetailView = {
|
|||||||
<label for="note-create-time" style="font-size:0.8rem; font-weight:600; color:var(--text-secondary);">Data/Ora:</label>
|
<label for="note-create-time" style="font-size:0.8rem; font-weight:600; color:var(--text-secondary);">Data/Ora:</label>
|
||||||
<input type="datetime-local" class="note-subject-input" id="note-create-time" title="Data/ora nota (opzionale per retrodatazione)" style="width:185px; margin-bottom:0; height:32px; padding:4px 8px; font-size:0.82rem;" />
|
<input type="datetime-local" class="note-subject-input" id="note-create-time" title="Data/ora nota (opzionale per retrodatazione)" style="width:185px; margin-bottom:0; height:32px; padding:4px 8px; font-size:0.82rem;" />
|
||||||
|
|
||||||
<label for="note-time-units" style="font-size:0.8rem; font-weight:600; color:var(--text-secondary); margin-left:4px;">Tempo (min):</label>
|
|
||||||
<input type="number" step="any" min="0" class="note-subject-input" id="note-time-units" placeholder="minuti" style="width:90px; margin-bottom:0; height:32px; padding:4px 8px; font-size:0.85rem;" />
|
|
||||||
|
|
||||||
<button class="btn btn-ghost btn-sm" id="btn-open-email-compose" style="height:32px; display:flex; align-items:center; gap:var(--space-xs); border-color:var(--accent-secondary); color:var(--accent-secondary);"
|
<button class="btn btn-ghost btn-sm" id="btn-open-email-compose" style="height:32px; display:flex; align-items:center; gap:var(--space-xs); border-color:var(--accent-secondary); color:var(--accent-secondary);"
|
||||||
data-ticket-id="${ticket.id}" data-ticket-tn="${ticket.tn}" data-ticket-title="${App.escapeHtml(ticket.title)}" data-customer-email="${App.escapeHtml(ticket.customer_email || '')}">
|
data-ticket-id="${ticket.id}" data-ticket-tn="${ticket.tn}" data-ticket-title="${App.escapeHtml(ticket.title)}" data-customer-email="${App.escapeHtml(ticket.customer_email || '')}">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ const TicketListView = {
|
|||||||
<span style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:#1070ca; color:#fff; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; vertical-align:middle; line-height:16px;">O</span>
|
<span style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:#1070ca; color:#fff; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; vertical-align:middle; line-height:16px;">O</span>
|
||||||
</a>
|
</a>
|
||||||
` : ''}
|
` : ''}
|
||||||
<button class="open-tab-btn" data-id="${t.id}" data-tn="${t.tn}" data-title="${App.escapeHtml(t.title || '')}" onclick="App.openTab(${t.id}, '${t.tn}', this.dataset.title); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-right:6px; cursor:pointer; line-height:16px;">+</button>
|
<button class="open-tab-btn" data-id="${t.id}" data-tn="${t.tn}" data-title="${App.escapeHtml(t.title || '')}" onclick="App.openTab(${t.id}, '${t.tn}', this.dataset.title, true); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-right:6px; cursor:pointer; line-height:16px;">+</button>
|
||||||
<a href="#/tickets/${t.id}" class="ticket-tn-link" onclick="event.stopPropagation()">${t.tn}</a>
|
<a href="#/tickets/${t.id}" class="ticket-tn-link" onclick="event.stopPropagation()">${t.tn}</a>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -12,6 +12,39 @@ const TimeAccountingView = {
|
|||||||
viewMode: 'list', // 'list' | 'calendar'
|
viewMode: 'list', // 'list' | 'calendar'
|
||||||
showAllTime: false,
|
showAllTime: false,
|
||||||
showNonWorkingDays: localStorage.getItem('otrs_ta_show_non_working_days') !== '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) {
|
formatLocalISO(d) {
|
||||||
if (!d) return '';
|
if (!d) return '';
|
||||||
@@ -174,6 +207,14 @@ const TimeAccountingView = {
|
|||||||
|
|
||||||
<div style="display:flex; gap:var(--space-xs); align-self:flex-end;">
|
<div style="display:flex; gap:var(--space-xs); align-self:flex-end;">
|
||||||
<button id="ta-btn-today" class="btn btn-ghost btn-sm" style="height:36px;">Oggi</button>
|
<button id="ta-btn-today" class="btn btn-ghost btn-sm" style="height:36px;">Oggi</button>
|
||||||
|
<button id="ta-btn-toggle-groupby" class="btn btn-ghost btn-sm" style="height:36px; display:flex; align-items:center; gap:6px; background:${this.groupByTicket ? 'var(--accent-primary)' : 'var(--bg-tertiary)'}; color:${this.groupByTicket ? '#ffffff' : 'var(--text-secondary)'}; border:1px solid var(--border-subtle);" title="Raggruppa le note adiacenti appartenenti allo stesso ticket">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:15px; height:15px;">
|
||||||
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||||
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
||||||
|
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
||||||
|
</svg>
|
||||||
|
<span>${this.groupByTicket ? 'Raggruppa per Ticket (Attivo)' : 'Raggruppa per Ticket'}</span>
|
||||||
|
</button>
|
||||||
<button id="ta-btn-toggle-nonwork" class="btn btn-ghost btn-sm" style="height:36px; display:flex; align-items:center; gap:6px;">
|
<button id="ta-btn-toggle-nonwork" class="btn btn-ghost btn-sm" style="height:36px; display:flex; align-items:center; gap:6px;">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:15px; height:15px;">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:15px; height:15px;">
|
||||||
${this.showNonWorkingDays
|
${this.showNonWorkingDays
|
||||||
@@ -193,7 +234,7 @@ const TimeAccountingView = {
|
|||||||
<div style="display:flex; gap:var(--space-md); padding:var(--space-md) var(--space-xl); align-items:flex-start; min-height:calc(100vh - 280px);">
|
<div style="display:flex; gap:var(--space-md); padding:var(--space-md) var(--space-xl); align-items:flex-start; min-height:calc(100vh - 280px);">
|
||||||
|
|
||||||
<!-- Left Side: List of Articles -->
|
<!-- Left Side: List of Articles -->
|
||||||
<div style="flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--space-sm);">
|
<div style="flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--space-md);">
|
||||||
${articles.length === 0 ? `
|
${articles.length === 0 ? `
|
||||||
<div class="empty-state" style="padding:var(--space-2xl); text-align:center; color:var(--text-muted); background:var(--bg-card); border-radius:var(--radius-md); border:1px solid var(--border-subtle);">
|
<div class="empty-state" style="padding:var(--space-2xl); text-align:center; color:var(--text-muted); background:var(--bg-card); border-radius:var(--radius-md); border:1px solid var(--border-subtle);">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px; height:48px; margin:0 auto var(--space-md); display:block; opacity:0.4;">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px; height:48px; margin:0 auto var(--space-md); display:block; opacity:0.4;">
|
||||||
@@ -201,7 +242,7 @@ const TimeAccountingView = {
|
|||||||
</svg>
|
</svg>
|
||||||
<p style="font-size:0.9rem; font-weight:500;">Nessuna nota o email trovata in questa data per l'agente selezionato.</p>
|
<p style="font-size:0.9rem; font-weight:500;">Nessuna nota o email trovata in questa data per l'agente selezionato.</p>
|
||||||
</div>
|
</div>
|
||||||
` : articles.map(a => this.buildArticleCard(a)).join('')}
|
` : this.groupArticles(articles).map(g => g.type === 'single' ? this.buildArticleCard(g.article) : this.buildGroupCard(g)).join('')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Side: Details & Actions Panel -->
|
<!-- Right Side: Details & Actions Panel -->
|
||||||
@@ -574,6 +615,40 @@ const TimeAccountingView = {
|
|||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
buildGroupCard(group) {
|
||||||
|
const totalGroupMins = Math.round(group.articles.reduce((acc, a) => acc + (parseFloat(a.time_unit) || 0), 0));
|
||||||
|
const formattedGroupTime = this.formatPillTime(totalGroupMins);
|
||||||
|
const hasSelectedArticle = group.articles.some(a => String(a.article_id) === String(this.selectedArticleId));
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="card ta-group-card" style="border:1px solid ${hasSelectedArticle ? 'var(--accent-primary)' : 'var(--border-subtle)'}; background:var(--bg-card); border-radius:var(--radius-md); overflow:hidden;">
|
||||||
|
<!-- Header del Gruppo Ticket -->
|
||||||
|
<div style="padding:10px 14px; background:var(--bg-secondary); border-bottom:1px solid var(--border-subtle); display:flex; align-items:center; justify-content:space-between; gap:10px;">
|
||||||
|
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
|
||||||
|
<span style="font-size:0.9rem;">🎫</span>
|
||||||
|
<a href="#/tickets/${group.ticket_id}" class="ticket-tn-link" onclick="event.stopPropagation();" style="font-weight:700; font-size:0.9rem;">#${App.escapeHtml(group.ticket_tn || group.ticket_id)}</a>
|
||||||
|
<span style="font-size:0.85rem; font-weight:600; color:var(--text-primary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" title="${App.escapeHtml(group.ticket_title || '')}">
|
||||||
|
${App.escapeHtml(group.ticket_title || '(senza titolo)')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:8px; flex-shrink:0;">
|
||||||
|
<span class="badge" style="background:var(--bg-tertiary); color:var(--text-secondary); font-size:0.75rem; font-weight:600;">
|
||||||
|
${group.articles.length} ${group.articles.length === 1 ? 'nota' : 'note'}
|
||||||
|
</span>
|
||||||
|
<span class="badge" style="background:${totalGroupMins > 0 ? 'rgba(34,197,94,0.18)' : 'var(--bg-tertiary)'}; color:${totalGroupMins > 0 ? '#15803d' : 'var(--text-muted)'}; font-weight:700; font-size:0.78rem;">
|
||||||
|
⏱️ Totale: ${formattedGroupTime} (${totalGroupMins} m)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Note del Gruppo -->
|
||||||
|
<div style="display:flex; flex-direction:column; gap:6px; padding:10px;">
|
||||||
|
${group.articles.map(a => this.buildArticleCard(a)).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
buildDetailPanel(a) {
|
buildDetailPanel(a) {
|
||||||
const timeMins = parseFloat(a.time_unit || 0);
|
const timeMins = parseFloat(a.time_unit || 0);
|
||||||
|
|
||||||
@@ -687,6 +762,13 @@ const TimeAccountingView = {
|
|||||||
this.loadData();
|
this.loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle group by ticket button
|
||||||
|
document.getElementById('ta-btn-toggle-groupby')?.addEventListener('click', () => {
|
||||||
|
this.groupByTicket = !this.groupByTicket;
|
||||||
|
localStorage.setItem('otrs_ta_group_by_ticket', this.groupByTicket);
|
||||||
|
this.loadData();
|
||||||
|
});
|
||||||
|
|
||||||
// Day pill clicks (also handles calendar cell clicks)
|
// Day pill clicks (also handles calendar cell clicks)
|
||||||
document.querySelectorAll('.ta-day-pill[data-date]').forEach(pill => {
|
document.querySelectorAll('.ta-day-pill[data-date]').forEach(pill => {
|
||||||
pill.addEventListener('click', () => {
|
pill.addEventListener('click', () => {
|
||||||
|
|||||||
Reference in new issue
Block a user