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