Paginointi asiakaspalveluun + Zammad-ryhmät näkyvyysasetuksiin

- Paginointi: 100 tikettiä/sivu, navigointipalkki sivujen välillä
- Filtterit resetoivat sivunumeron 1:ksi
- Select All valitsee vain nykyisen sivun tiketit
- Zammad-synkronointi tallentaa source=zammad ja zammad_group
- Postilaatikoiden näkyvyysasetuksissa Zammad-ryhmät (Zammad)-merkinnällä
- Zammad-ryhmien piilotus filtteröi tiketit samalla tavalla kuin postilaatikot

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 23:20:25 +02:00
parent cc6e5c2653
commit 08bce71b5b
2 changed files with 105 additions and 17 deletions

110
script.js
View File

@@ -1350,11 +1350,15 @@ let ticketTypeLabels = {
muu: 'Muu',
};
let ticketPage = 1;
const TICKETS_PER_PAGE = 100;
async function loadTickets() {
try {
// Hae kaikkien yritysten tiketit jos useampi yritys
const allParam = availableCompanies.length > 1 ? '&all=1' : '';
tickets = await apiCall('tickets' + allParam);
ticketPage = 1;
renderTickets();
} catch (e) { console.error(e); }
}
@@ -1367,9 +1371,15 @@ function renderTickets() {
const showMine = document.getElementById('ticket-show-mine').checked;
let filtered = tickets;
// Piilota piilotettujen postilaatikoiden tiketit
// Piilota piilotettujen postilaatikoiden ja Zammad-ryhmien tiketit
if (currentHiddenMailboxes.length > 0) {
filtered = filtered.filter(t => !currentHiddenMailboxes.includes(String(t.mailbox_id)) && !currentHiddenMailboxes.includes(t.mailbox_id));
filtered = filtered.filter(t => {
// Piilota mailbox-perusteisesti
if (t.mailbox_id && (currentHiddenMailboxes.includes(String(t.mailbox_id)) || currentHiddenMailboxes.includes(t.mailbox_id))) return false;
// Piilota Zammad-ryhmä-perusteisesti
if (t.source === 'zammad' && t.zammad_group && currentHiddenMailboxes.includes('zammad_group:' + t.zammad_group)) return false;
return true;
});
}
// Suljetut näkyvät vain kun täppä on päällä
@@ -1431,6 +1441,14 @@ function renderTickets() {
const ttbody = document.getElementById('tickets-tbody');
const noTickets = document.getElementById('no-tickets');
// Paginointi
const totalFiltered = filtered.length;
const totalPages = Math.max(1, Math.ceil(totalFiltered / TICKETS_PER_PAGE));
if (ticketPage > totalPages) ticketPage = totalPages;
const startIdx = (ticketPage - 1) * TICKETS_PER_PAGE;
const pageTickets = filtered.slice(startIdx, startIdx + TICKETS_PER_PAGE);
if (filtered.length === 0) {
ttbody.innerHTML = '';
noTickets.style.display = 'block';
@@ -1439,7 +1457,7 @@ function renderTickets() {
noTickets.style.display = 'none';
document.getElementById('tickets-table').style.display = 'table';
const multiCompany = availableCompanies.length > 1;
ttbody.innerHTML = filtered.map(t => {
ttbody.innerHTML = pageTickets.map(t => {
const lastType = t.last_message_type === 'reply_out' ? '&#8594;' : (t.last_message_type === 'note' ? '&#128221;' : '&#8592;');
const typeLabel = ticketTypeLabels[t.type] || 'Muu';
const rowClass = t.priority === 'urgent' ? 'ticket-row-urgent' : (t.priority === 'tärkeä' ? 'ticket-row-important' : (t.status === 'kasittelyssa' ? 'ticket-row-active' : ''));
@@ -1479,15 +1497,63 @@ function renderTickets() {
if (counts.kasittelyssa) parts.push(`${counts.kasittelyssa} käsittelyssä`);
if (counts.odottaa) parts.push(`${counts.odottaa} odottaa`);
document.getElementById('ticket-status-summary').textContent = parts.join(' · ');
// Paginointipalkki
renderTicketPagination(totalFiltered, totalPages);
}
document.getElementById('ticket-search-input').addEventListener('input', () => renderTickets());
document.getElementById('ticket-status-filter').addEventListener('change', () => renderTickets());
document.getElementById('ticket-type-filter').addEventListener('change', () => renderTickets());
document.getElementById('ticket-tag-filter').addEventListener('input', () => renderTickets());
document.getElementById('ticket-sort').addEventListener('change', () => renderTickets());
document.getElementById('ticket-show-closed').addEventListener('change', () => renderTickets());
document.getElementById('ticket-show-mine').addEventListener('change', () => renderTickets());
function renderTicketPagination(totalFiltered, totalPages) {
let paginationEl = document.getElementById('ticket-pagination');
if (!paginationEl) {
paginationEl = document.createElement('div');
paginationEl.id = 'ticket-pagination';
paginationEl.style.cssText = 'display:flex;align-items:center;justify-content:center;gap:0.5rem;padding:0.75rem 0;flex-wrap:wrap;';
const table = document.getElementById('tickets-table');
table.parentNode.insertBefore(paginationEl, table.nextSibling);
}
if (totalPages <= 1) {
paginationEl.innerHTML = totalFiltered > 0 ? `<span style="color:#888;font-size:0.85rem;">${totalFiltered} tikettiä</span>` : '';
return;
}
let html = '';
// Edellinen-nappi
html += `<button class="btn-secondary" style="padding:0.3rem 0.6rem;font-size:0.85rem;" ${ticketPage <= 1 ? 'disabled' : ''} onclick="ticketPage=1;renderTickets()">⟪</button>`;
html += `<button class="btn-secondary" style="padding:0.3rem 0.6rem;font-size:0.85rem;" ${ticketPage <= 1 ? 'disabled' : ''} onclick="ticketPage--;renderTickets()">← Edellinen</button>`;
// Sivunumerot
const maxShow = 5;
let startPage = Math.max(1, ticketPage - Math.floor(maxShow / 2));
let endPage = Math.min(totalPages, startPage + maxShow - 1);
if (endPage - startPage < maxShow - 1) startPage = Math.max(1, endPage - maxShow + 1);
if (startPage > 1) html += `<span style="color:#888;">...</span>`;
for (let p = startPage; p <= endPage; p++) {
if (p === ticketPage) {
html += `<button style="padding:0.3rem 0.6rem;font-size:0.85rem;background:#0f3460;color:white;border:none;border-radius:6px;font-weight:600;">${p}</button>`;
} else {
html += `<button class="btn-secondary" style="padding:0.3rem 0.6rem;font-size:0.85rem;" onclick="ticketPage=${p};renderTickets()">${p}</button>`;
}
}
if (endPage < totalPages) html += `<span style="color:#888;">...</span>`;
// Seuraava-nappi
html += `<button class="btn-secondary" style="padding:0.3rem 0.6rem;font-size:0.85rem;" ${ticketPage >= totalPages ? 'disabled' : ''} onclick="ticketPage++;renderTickets()">Seuraava →</button>`;
html += `<button class="btn-secondary" style="padding:0.3rem 0.6rem;font-size:0.85rem;" ${ticketPage >= totalPages ? 'disabled' : ''} onclick="ticketPage=${totalPages};renderTickets()">⟫</button>`;
// Näytetään sivuinfo
const startNum = (ticketPage - 1) * TICKETS_PER_PAGE + 1;
const endNum = Math.min(ticketPage * TICKETS_PER_PAGE, totalFiltered);
html += `<span style="color:#888;font-size:0.85rem;margin-left:0.5rem;">${startNum}${endNum} / ${totalFiltered}</span>`;
paginationEl.innerHTML = html;
document.getElementById('ticket-search-input').addEventListener('input', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-status-filter').addEventListener('change', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-type-filter').addEventListener('change', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-tag-filter').addEventListener('input', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-sort').addEventListener('change', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-show-closed').addEventListener('change', () => { ticketPage = 1; renderTickets(); });
document.getElementById('ticket-show-mine').addEventListener('change', () => { ticketPage = 1; renderTickets(); });
document.getElementById('bulk-select-all').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.ticket-checkbox');
checkboxes.forEach(cb => {
@@ -1496,6 +1562,11 @@ document.getElementById('bulk-select-all').addEventListener('change', function()
else bulkSelectedIds.delete(cb.dataset.ticketId);
});
updateBulkToolbar();
// Näytä montako valittu kaikista sivuilta
const allCheckbox = document.getElementById('bulk-select-all');
if (bulkSelectedIds.size > checkboxes.length) {
allCheckbox.title = `${bulkSelectedIds.size} tikettiä valittu (myös muilta sivuilta)`;
}
});
document.getElementById('tickets-tbody').addEventListener('click', (e) => {
@@ -2268,13 +2339,30 @@ async function initTicketSettings() {
).join('');
// Postilaatikoiden näkyvyys — checkbox per postilaatikko
visContainer.innerHTML = mailboxes.map(mb => {
let visHtml = mailboxes.map(mb => {
const isHidden = currentHiddenMailboxes.includes(String(mb.id)) || currentHiddenMailboxes.includes(mb.id);
return `<label style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;font-size:0.9rem;cursor:pointer;">
<input type="checkbox" class="mb-visibility-cb" data-mailbox-id="${mb.id}" ${!isHidden ? 'checked' : ''}>
<span>${esc(mb.company_nimi)}${esc(mb.nimi)} &lt;${esc(mb.smtp_from_email)}&gt;</span>
</label>`;
}).join('');
// Zammad-ryhmät näkyvyyteen (haetaan tiketeistä)
try {
const zammadGroups = [...new Set(tickets.filter(t => t.source === 'zammad' && t.zammad_group).map(t => t.zammad_group))].sort();
if (zammadGroups.length > 0) {
visHtml += '<div style="margin-top:0.75rem;padding-top:0.75rem;border-top:1px solid #eee;"><strong style="font-size:0.85rem;color:#666;">Zammad-ryhmät</strong></div>';
zammadGroups.forEach(grp => {
const key = 'zammad_group:' + grp;
const isHidden = currentHiddenMailboxes.includes(key);
visHtml += `<label style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;font-size:0.9rem;cursor:pointer;">
<input type="checkbox" class="mb-visibility-cb" data-mailbox-id="${esc(key)}" ${!isHidden ? 'checked' : ''}>
<span>${esc(grp)} <span style="color:#888;font-size:0.8rem;">(Zammad)</span></span>
</label>`;
});
}
} catch (e) {}
visContainer.innerHTML = visHtml;
} catch (e) {
sigContainer.innerHTML = '<p style="color:red;font-size:0.85rem;">Virhe ladattaessa postilaatikoita.</p>';
}