/** * 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 = `

๐Ÿ—‚๏ธ Gruppi Ticket

๐Ÿ“‚

Nessun gruppo selezionato

Seleziona un gruppo dalla barra laterale o creane uno nuovo per iniziare a gestire le relazioni master/slave.

`; // 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(); 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.tn} โ€” ${App.escapeHtml(masterTicket.title)}
${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.tn} โ€” ${App.escapeHtml(t.title)}
${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}

Associa Nuovo Ticket Slave

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); } };