/**
* ticketGroups.js
* SPA View for managing Ticket Groups (mimics master/slave relationships).
*/
const TicketGroupsView = {
groups: [],
selectedGroupId: null,
selectedGroupData: null,
selectedTicketIds: new Set(), // For bulk actions (complying with no-checkboxes rule)
async render() {
const container = document.getElementById('view-container');
container.innerHTML = `
๐
Nessun gruppo selezionato
Seleziona un gruppo dalla barra laterale o creane uno nuovo per iniziare a gestire le relazioni master/slave.
Nuovo Gruppo
`;
// Bind event listeners
document.getElementById('btn-create-group').addEventListener('click', () => this.openGroupModal());
document.getElementById('group-modal-close').addEventListener('click', () => this.closeGroupModal());
document.getElementById('btn-group-modal-cancel').addEventListener('click', () => this.closeGroupModal());
document.getElementById('btn-group-modal-save').addEventListener('click', () => this.saveGroup());
// Restore state from localStorage if available
const savedGroupId = localStorage.getItem('otrs_selected_group_id');
if (savedGroupId) {
this.selectedGroupId = parseInt(savedGroupId, 10);
}
await this.loadGroups();
const prefillMasterTn = sessionStorage.getItem('otrs_create_group_with_master_tn');
if (prefillMasterTn) {
sessionStorage.removeItem('otrs_create_group_with_master_tn');
this.openGroupModal();
const masterInput = document.getElementById('group-master-input');
if (masterInput) {
masterInput.value = prefillMasterTn;
}
} else if (this.selectedGroupId) {
this.selectGroup(this.selectedGroupId);
}
},
async loadGroups() {
try {
this.groups = await App.api('/api/groups');
this.renderGroupsList();
} catch (err) {
Toast.error('Errore nel caricamento dei gruppi: ' + err.message);
}
},
renderGroupsList() {
const listContainer = document.getElementById('groups-list-container');
if (!listContainer) return;
if (this.groups.length === 0) {
listContainer.innerHTML = `
Nessun gruppo presente
`;
return;
}
listContainer.innerHTML = this.groups.map(group => {
const isActive = group.id === this.selectedGroupId;
return `
${App.escapeHtml(group.nome)}
${App.escapeHtml(group.descrizione || 'Nessuna descrizione')}
Master: ${group.master_ticket_id ? '#' + group.master_ticket_id : 'Nessuno'}
${group.member_count} ticket
`;
}).join('');
// Bind click events to items
listContainer.querySelectorAll('.group-item').forEach(item => {
item.addEventListener('click', () => {
const id = parseInt(item.dataset.id, 10);
this.selectGroup(id);
});
});
},
async selectGroup(id) {
this.selectedGroupId = id;
localStorage.setItem('otrs_selected_group_id', id);
this.selectedTicketIds.clear();
this.renderGroupsList();
const detailPane = document.getElementById('group-details-pane');
detailPane.innerHTML = `
`;
try {
this.selectedGroupData = await App.api(`/api/groups/${id}`);
this.renderGroupDetails();
} catch (err) {
Toast.error('Errore nel caricamento del dettaglio gruppo: ' + err.message);
detailPane.innerHTML = `
Errore nel caricamento dei dati: ${err.message}
`;
}
},
renderGroupDetails() {
const detailPane = document.getElementById('group-details-pane');
if (!detailPane || !this.selectedGroupData) return;
const { group, masterTicket, memberTickets } = this.selectedGroupData;
let masterTicketHtml = '';
if (masterTicket) {
masterTicketHtml = `
${masterTicket.state_name}
Coda: ${masterTicket.queue_name}
Proprietario: ${masterTicket.owner_first ? `${masterTicket.owner_first} ${masterTicket.owner_last}` : masterTicket.owner_login}
๐ Master Ticket
`;
} else {
masterTicketHtml = `
Nessun master ticket assegnato. Imposta un ticket come master usando il tasto Modifica Gruppo o associa un ID/Numero valido.
`;
}
let membersListHtml = '';
if (memberTickets.length === 0) {
membersListHtml = `
Nessun ticket slave/membro associato a questo gruppo.
`;
} else {
membersListHtml = `
${memberTickets.map(t => {
const isSelected = this.selectedTicketIds.has(t.id);
return `
${t.state_name}
Coda: ${t.queue_name}
Proprietario: ${t.owner_first ? `${t.owner_first} ${t.owner_last}` : t.owner_login}
`;
}).join('')}
`;
}
detailPane.innerHTML = `
${App.escapeHtml(group.nome)}
${App.escapeHtml(group.descrizione || 'Nessuna descrizione per questo gruppo.')}
Ticket Master
${masterTicketHtml}
Ticket Membri (${memberTickets.length})
${this.selectedTicketIds.size > 0 ? `
` : `
Clicca sulle schede per selezionare in blocco
`}
${membersListHtml}
`;
// Bind actions
document.getElementById('btn-edit-group').addEventListener('click', () => this.openGroupModal(group));
document.getElementById('btn-delete-group').addEventListener('click', () => this.deleteGroup(group.id));
if (masterTicket) {
document.getElementById('btn-unlink-master').addEventListener('click', () => this.unlinkMaster());
}
document.getElementById('btn-quick-add-ticket').addEventListener('click', () => this.addTicketToGroup());
document.getElementById('quick-add-ticket-input').addEventListener('keydown', (e) => {
if (e.key === 'Enter') this.addTicketToGroup();
});
// Individual removal buttons
detailPane.querySelectorAll('.btn-remove-member').forEach(btn => {
btn.addEventListener('click', () => {
const ticketId = parseInt(btn.dataset.ticketId, 10);
this.removeTicketFromGroup(ticketId);
});
});
// Multi-select cards logic (No-checkboxes rule)
detailPane.querySelectorAll('.member-ticket-card').forEach(card => {
card.addEventListener('click', () => {
const ticketId = parseInt(card.dataset.ticketId, 10);
if (this.selectedTicketIds.has(ticketId)) {
this.selectedTicketIds.delete(ticketId);
} else {
this.selectedTicketIds.add(ticketId);
}
this.renderGroupDetails();
});
});
// Bulk actions
const btnBulkRemove = document.getElementById('btn-bulk-remove');
if (btnBulkRemove) {
btnBulkRemove.addEventListener('click', () => this.bulkRemoveTickets());
}
const btnBulkCopy = document.getElementById('btn-bulk-copy');
if (btnBulkCopy) {
btnBulkCopy.addEventListener('click', (e) => {
e.stopPropagation();
const selectedTns = [];
this.selectedTicketIds.forEach(id => {
const ticket = memberTickets.find(t => t.id === id);
if (ticket && ticket.tn) {
selectedTns.push(ticket.tn);
}
});
if (selectedTns.length > 0) {
const textToCopy = selectedTns.join('\n');
navigator.clipboard.writeText(textToCopy).then(() => {
Toast.success(`${selectedTns.length} numeri ticket copiati!`);
}).catch(err => {
Toast.error('Errore durante la copia: ' + err.message);
});
}
});
}
// Individual copy buttons
detailPane.querySelectorAll('.copy-ticket-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const tn = btn.dataset.tn;
if (tn) {
navigator.clipboard.writeText(tn).then(() => {
Toast.success(`Numero ticket ${tn} copiato!`);
}).catch(err => {
Toast.error('Errore durante la copia: ' + err.message);
});
}
});
});
},
openGroupModal(group = null) {
const modal = document.getElementById('group-modal');
const title = document.getElementById('group-modal-title');
const nameInput = document.getElementById('group-name-input');
const descInput = document.getElementById('group-desc-input');
const masterInput = document.getElementById('group-master-input');
if (group) {
title.textContent = 'Modifica Gruppo';
nameInput.value = group.nome || '';
descInput.value = group.descrizione || '';
masterInput.value = group.master_ticket_id || '';
modal.dataset.editId = group.id;
} else {
title.textContent = 'Nuovo Gruppo';
nameInput.value = '';
descInput.value = '';
masterInput.value = '';
delete modal.dataset.editId;
}
modal.style.display = 'flex';
nameInput.focus();
},
closeGroupModal() {
document.getElementById('group-modal').style.display = 'none';
},
async saveGroup() {
const modal = document.getElementById('group-modal');
const editId = modal.dataset.editId;
const nome = document.getElementById('group-name-input').value.trim();
const descrizione = document.getElementById('group-desc-input').value.trim();
const masterVal = document.getElementById('group-master-input').value.trim();
if (!nome) {
Toast.error('Il nome del gruppo รจ richiesto.');
return;
}
let masterTicketId = null;
if (masterVal) {
// Validate master ticket input (ID or Number)
try {
const res = await App.api(`/api/tickets?search=${masterVal}&per_page=1`);
if (res && res.tickets && res.tickets.length > 0) {
masterTicketId = res.tickets[0].id;
} else {
// Check if it's a direct database ID by querying search directly
if (/^\d+$/.test(masterVal)) {
masterTicketId = parseInt(masterVal, 10);
} else {
Toast.error('Ticket master non trovato.');
return;
}
}
} catch (err) {
if (/^\d+$/.test(masterVal)) {
masterTicketId = parseInt(masterVal, 10);
} else {
Toast.error('Errore nella verifica del ticket master: ' + err.message);
return;
}
}
}
const payload = { nome, descrizione, master_ticket_id: masterTicketId };
try {
if (editId) {
await App.api(`/api/groups/${editId}`, {
method: 'PUT',
body: JSON.stringify(payload)
});
Toast.success('Gruppo aggiornato con successo');
} else {
const newGroup = await App.api('/api/groups', {
method: 'POST',
body: JSON.stringify(payload)
});
this.selectedGroupId = newGroup.id;
Toast.success('Gruppo creato con successo');
}
this.closeGroupModal();
await this.loadGroups();
if (this.selectedGroupId) {
await this.selectGroup(this.selectedGroupId);
}
} catch (err) {
Toast.error('Errore nel salvataggio: ' + err.message);
}
},
async deleteGroup(id) {
const ok = await App.confirm('Elimina Gruppo', 'Sei sicuro di voler eliminare questo gruppo? Le associazioni dei ticket verranno rimosse, ma i ticket non saranno modificati.');
if (!ok) {
return;
}
try {
await App.api(`/api/groups/${id}`, { method: 'DELETE' });
Toast.success('Gruppo eliminato con successo');
this.selectedGroupId = null;
this.selectedGroupData = null;
localStorage.removeItem('otrs_selected_group_id');
await this.loadGroups();
document.getElementById('group-details-pane').innerHTML = `
๐
Nessun gruppo selezionato
Seleziona un gruppo dalla barra laterale o creane uno nuovo per iniziare a gestire le relazioni.
`;
} catch (err) {
Toast.error('Errore nella cancellazione: ' + err.message);
}
},
async unlinkMaster() {
if (!this.selectedGroupData) return;
const { group } = this.selectedGroupData;
try {
await App.api(`/api/groups/${group.id}`, {
method: 'PUT',
body: JSON.stringify({
nome: group.nome,
descrizione: group.descrizione,
master_ticket_id: null
})
});
Toast.success('Ticket master scollegato con successo');
await this.selectGroup(group.id);
} catch (err) {
Toast.error('Errore nello scollegamento: ' + err.message);
}
},
async addTicketToGroup() {
const input = document.getElementById('quick-add-ticket-input');
const val = input.value.trim();
if (!val) return;
try {
await App.api(`/api/groups/${this.selectedGroupId}/tickets`, {
method: 'POST',
body: JSON.stringify({ ticket_identifier: val })
});
Toast.success('Ticket associato correttamente');
input.value = '';
await this.selectGroup(this.selectedGroupId);
} catch (err) {
Toast.error('Errore nell\'associazione: ' + err.message);
}
},
async removeTicketFromGroup(ticketId) {
try {
await App.api(`/api/groups/${this.selectedGroupId}/tickets/${ticketId}`, {
method: 'DELETE'
});
Toast.success('Ticket rimosso dal gruppo');
await this.selectGroup(this.selectedGroupId);
} catch (err) {
Toast.error('Errore nella rimozione: ' + err.message);
}
},
async bulkRemoveTickets() {
if (this.selectedTicketIds.size === 0) return;
const ok = await App.confirm('Rimuovi Ticket', `Vuoi rimuovere i ${this.selectedTicketIds.size} ticket selezionati da questo gruppo?`);
if (!ok) return;
let successCount = 0;
let failCount = 0;
for (const ticketId of this.selectedTicketIds) {
try {
await App.api(`/api/groups/${this.selectedGroupId}/tickets/${ticketId}`, {
method: 'DELETE'
});
successCount++;
} catch (err) {
failCount++;
}
}
if (successCount > 0) {
Toast.success(`${successCount} ticket rimossi con successo.`);
}
if (failCount > 0) {
Toast.error(`Impossibile rimuovere ${failCount} ticket.`);
}
this.selectedTicketIds.clear();
await this.selectGroup(this.selectedGroupId);
}
};