feat: gestione gruppi di ticket. feat: miglioramento filtri ticket a mio carico e ticket. feat: possiblità di copiare il numero del tocket con pulsante copia

This commit is contained in:
2026-07-09 08:36:03 +02:00
parent 5b914eb198
commit a89605b888
14 changed files with 1674 additions and 182 deletions
+80 -2
View File
@@ -41,6 +41,13 @@ const TicketDetailView = {
await App.ensureLookups();
const data = await App.api(`/api/tickets/${id}`);
const { ticket, articles, attachments } = data;
let groupsData = { asMaster: [], asMember: [] };
try {
groupsData = await App.api(`/api/groups/by-ticket/${id}`);
} catch (gErr) {
console.warn('Failed to load group associations for ticket', gErr);
}
const localISO = (dateStr) => {
if (!dateStr) return '';
const d = new Date(dateStr);
@@ -380,6 +387,38 @@ const TicketDetailView = {
</div>
</div>
` : ''}
<!-- Panel per i Gruppi Ticket -->
<div class="sidebar-panel" id="ticket-groups-panel">
<div class="sidebar-panel-title">Gruppi Ticket</div>
<div id="ticket-groups-list" style="margin-bottom: var(--space-sm);">
${groupsData.asMaster.length === 0 && groupsData.asMember.length === 0 ? `
<div style="font-size:0.78rem; color:var(--text-muted); padding:4px 0;">Nessun gruppo associato</div>
` : ''}
${groupsData.asMaster.map(g => `
<div style="font-size:0.8rem; margin-bottom:4px;">
<span style="color:var(--accent-primary); font-weight:bold;">👑 Master in:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</div>
`).join('')}
${groupsData.asMember.map(g => `
<div style="font-size:0.8rem; margin-bottom:4px;">
<span style="color:var(--text-secondary); font-weight:bold;">🔗 Membro di:</span>
<a href="#/tickets/groups" onclick="localStorage.setItem('otrs_selected_group_id', ${g.id})" style="color:var(--text-primary); text-decoration:none; border-bottom:1px dashed var(--text-muted);">${App.escapeHtml(g.nome)}</a>
</div>
`).join('')}
</div>
<div style="border-top:1px solid var(--border-subtle); padding-top:var(--space-xs); margin-top:var(--space-xs);">
<label class="quick-edit-label">Associa a Gruppo</label>
<div style="display:flex; gap:4px; margin-top:4px;">
<select class="form-select" id="group-association-select" style="padding:4px 20px 4px 8px; font-size:0.78rem; height:28px; margin:0; flex:1;">
<option value="">-- Seleziona --</option>
</select>
<button class="btn btn-primary btn-sm" id="btn-associate-to-group" style="height:28px; padding:0 8px; font-size:0.75rem;">+ Aggiungi</button>
</div>
</div>
</div>
</div>
</div>
`;
@@ -419,7 +458,7 @@ const TicketDetailView = {
this.noteQuill = null;
}
this.bindEvents(ticket, articles, container);
this.bindEvents(ticket, articles, container, groupsData);
} catch (err) {
container.innerHTML = `
@@ -433,7 +472,7 @@ const TicketDetailView = {
}
},
bindEvents(ticket, articles, container) {
bindEvents(ticket, articles, container, groupsData) {
// Quick-edit change detection
const fields = document.querySelectorAll('.quick-edit-select, #qe-customer-user-id, #qe-customer-id');
const saveBtn = document.getElementById('qe-save');
@@ -931,5 +970,44 @@ const TicketDetailView = {
this.render(this.ticketId);
});
}
// Populate Group Association dropdown
App.api('/api/groups').then(allGroups => {
const select = document.getElementById('group-association-select');
if (select) {
allGroups.forEach(g => {
const isMember = groupsData.asMember.some(m => m.id === g.id);
const isMaster = groupsData.asMaster.some(m => m.id === g.id);
if (!isMember && !isMaster) {
const opt = document.createElement('option');
opt.value = g.id;
opt.textContent = g.nome;
select.appendChild(opt);
}
});
}
}).catch(err => console.warn('Failed to load groups for association dropdown', err));
const btnAssociate = document.getElementById('btn-associate-to-group');
if (btnAssociate) {
btnAssociate.addEventListener('click', async () => {
const select = document.getElementById('group-association-select');
const groupId = select.value;
if (!groupId) return;
try {
btnAssociate.disabled = true;
await App.api(`/api/groups/${groupId}/tickets`, {
method: 'POST',
body: JSON.stringify({ ticket_identifier: this.ticketId })
});
Toast.success('Ticket associato al gruppo!');
this.render(this.ticketId);
} catch (err) {
Toast.error('Errore associazione: ' + err.message);
btnAssociate.disabled = false;
}
});
}
},
};