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
@@ -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>
|
||||
</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>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 || '')}">
|
||||
${App.escapeHtml(ticket.title || '(senza titolo)')}
|
||||
</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;">
|
||||
👤+ Assegna & Scheda
|
||||
</button>
|
||||
@@ -3562,6 +3562,10 @@ const TicketDetailView = {
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<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);"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
</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>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
<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-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;">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:15px; height:15px;">
|
||||
${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);">
|
||||
|
||||
<!-- 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 ? `
|
||||
<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;">
|
||||
@@ -201,7 +242,7 @@ const TimeAccountingView = {
|
||||
</svg>
|
||||
<p style="font-size:0.9rem; font-weight:500;">Nessuna nota o email trovata in questa data per l'agente selezionato.</p>
|
||||
</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>
|
||||
|
||||
<!-- 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) {
|
||||
const timeMins = parseFloat(a.time_unit || 0);
|
||||
|
||||
@@ -687,6 +762,13 @@ const TimeAccountingView = {
|
||||
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)
|
||||
document.querySelectorAll('.ta-day-pill[data-date]').forEach(pill => {
|
||||
pill.addEventListener('click', () => {
|
||||
|
||||
Reference in new issue
Block a user