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:
12
api.php
12
api.php
@@ -5132,20 +5132,20 @@ switch ($action) {
|
|||||||
$ticketId = substr(uniqid(), -8) . bin2hex(random_bytes(2));
|
$ticketId = substr(uniqid(), -8) . bin2hex(random_bytes(2));
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
_dbExecute(
|
_dbExecute(
|
||||||
"INSERT INTO tickets (id, company_id, subject, from_email, from_name, status, type, priority, zammad_ticket_id, ticket_number, created, updated)
|
"INSERT INTO tickets (id, company_id, subject, from_email, from_name, status, type, priority, zammad_ticket_id, zammad_group, source, ticket_number, created, updated)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
[$ticketId, $companyId, $zt['title'] ?? '', $zt['customer'] ?? '', $zt['customer'] ?? '',
|
[$ticketId, $companyId, $zt['title'] ?? '', $zt['customer'] ?? '', $zt['customer'] ?? '',
|
||||||
$status, $type, $priority, $zammadId, (int)($zt['number'] ?? 0),
|
$status, $type, $priority, $zammadId, $group, 'zammad', (int)($zt['number'] ?? 0),
|
||||||
$zt['created_at'] ? date('Y-m-d H:i:s', strtotime($zt['created_at'])) : $now,
|
$zt['created_at'] ? date('Y-m-d H:i:s', strtotime($zt['created_at'])) : $now,
|
||||||
$zt['updated_at'] ? date('Y-m-d H:i:s', strtotime($zt['updated_at'])) : $now]
|
$zt['updated_at'] ? date('Y-m-d H:i:s', strtotime($zt['updated_at'])) : $now]
|
||||||
);
|
);
|
||||||
$created++;
|
$created++;
|
||||||
} else {
|
} else {
|
||||||
$ticketId = $existing['id'];
|
$ticketId = $existing['id'];
|
||||||
// Päivitä status/priority
|
// Päivitä status/priority/group
|
||||||
_dbExecute(
|
_dbExecute(
|
||||||
"UPDATE tickets SET status = ?, type = ?, priority = ?, subject = ?, updated = ? WHERE id = ?",
|
"UPDATE tickets SET status = ?, type = ?, priority = ?, subject = ?, zammad_group = ?, updated = ? WHERE id = ?",
|
||||||
[$status, $type, $priority, $zt['title'] ?? '', date('Y-m-d H:i:s', strtotime($zt['updated_at'] ?? 'now')), $ticketId]
|
[$status, $type, $priority, $zt['title'] ?? '', $group, date('Y-m-d H:i:s', strtotime($zt['updated_at'] ?? 'now')), $ticketId]
|
||||||
);
|
);
|
||||||
$updated++;
|
$updated++;
|
||||||
}
|
}
|
||||||
|
|||||||
110
script.js
110
script.js
@@ -1350,11 +1350,15 @@ let ticketTypeLabels = {
|
|||||||
muu: 'Muu',
|
muu: 'Muu',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ticketPage = 1;
|
||||||
|
const TICKETS_PER_PAGE = 100;
|
||||||
|
|
||||||
async function loadTickets() {
|
async function loadTickets() {
|
||||||
try {
|
try {
|
||||||
// Hae kaikkien yritysten tiketit jos useampi yritys
|
// Hae kaikkien yritysten tiketit jos useampi yritys
|
||||||
const allParam = availableCompanies.length > 1 ? '&all=1' : '';
|
const allParam = availableCompanies.length > 1 ? '&all=1' : '';
|
||||||
tickets = await apiCall('tickets' + allParam);
|
tickets = await apiCall('tickets' + allParam);
|
||||||
|
ticketPage = 1;
|
||||||
renderTickets();
|
renderTickets();
|
||||||
} catch (e) { console.error(e); }
|
} catch (e) { console.error(e); }
|
||||||
}
|
}
|
||||||
@@ -1367,9 +1371,15 @@ function renderTickets() {
|
|||||||
const showMine = document.getElementById('ticket-show-mine').checked;
|
const showMine = document.getElementById('ticket-show-mine').checked;
|
||||||
let filtered = tickets;
|
let filtered = tickets;
|
||||||
|
|
||||||
// Piilota piilotettujen postilaatikoiden tiketit
|
// Piilota piilotettujen postilaatikoiden ja Zammad-ryhmien tiketit
|
||||||
if (currentHiddenMailboxes.length > 0) {
|
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ä
|
// Suljetut näkyvät vain kun täppä on päällä
|
||||||
@@ -1431,6 +1441,14 @@ function renderTickets() {
|
|||||||
|
|
||||||
const ttbody = document.getElementById('tickets-tbody');
|
const ttbody = document.getElementById('tickets-tbody');
|
||||||
const noTickets = document.getElementById('no-tickets');
|
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) {
|
if (filtered.length === 0) {
|
||||||
ttbody.innerHTML = '';
|
ttbody.innerHTML = '';
|
||||||
noTickets.style.display = 'block';
|
noTickets.style.display = 'block';
|
||||||
@@ -1439,7 +1457,7 @@ function renderTickets() {
|
|||||||
noTickets.style.display = 'none';
|
noTickets.style.display = 'none';
|
||||||
document.getElementById('tickets-table').style.display = 'table';
|
document.getElementById('tickets-table').style.display = 'table';
|
||||||
const multiCompany = availableCompanies.length > 1;
|
const multiCompany = availableCompanies.length > 1;
|
||||||
ttbody.innerHTML = filtered.map(t => {
|
ttbody.innerHTML = pageTickets.map(t => {
|
||||||
const lastType = t.last_message_type === 'reply_out' ? '→' : (t.last_message_type === 'note' ? '📝' : '←');
|
const lastType = t.last_message_type === 'reply_out' ? '→' : (t.last_message_type === 'note' ? '📝' : '←');
|
||||||
const typeLabel = ticketTypeLabels[t.type] || 'Muu';
|
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' : ''));
|
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.kasittelyssa) parts.push(`${counts.kasittelyssa} käsittelyssä`);
|
||||||
if (counts.odottaa) parts.push(`${counts.odottaa} odottaa`);
|
if (counts.odottaa) parts.push(`${counts.odottaa} odottaa`);
|
||||||
document.getElementById('ticket-status-summary').textContent = parts.join(' · ');
|
document.getElementById('ticket-status-summary').textContent = parts.join(' · ');
|
||||||
|
|
||||||
|
// Paginointipalkki
|
||||||
|
renderTicketPagination(totalFiltered, totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('ticket-search-input').addEventListener('input', () => renderTickets());
|
function renderTicketPagination(totalFiltered, totalPages) {
|
||||||
document.getElementById('ticket-status-filter').addEventListener('change', () => renderTickets());
|
let paginationEl = document.getElementById('ticket-pagination');
|
||||||
document.getElementById('ticket-type-filter').addEventListener('change', () => renderTickets());
|
if (!paginationEl) {
|
||||||
document.getElementById('ticket-tag-filter').addEventListener('input', () => renderTickets());
|
paginationEl = document.createElement('div');
|
||||||
document.getElementById('ticket-sort').addEventListener('change', () => renderTickets());
|
paginationEl.id = 'ticket-pagination';
|
||||||
document.getElementById('ticket-show-closed').addEventListener('change', () => renderTickets());
|
paginationEl.style.cssText = 'display:flex;align-items:center;justify-content:center;gap:0.5rem;padding:0.75rem 0;flex-wrap:wrap;';
|
||||||
document.getElementById('ticket-show-mine').addEventListener('change', () => renderTickets());
|
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() {
|
document.getElementById('bulk-select-all').addEventListener('change', function() {
|
||||||
const checkboxes = document.querySelectorAll('.ticket-checkbox');
|
const checkboxes = document.querySelectorAll('.ticket-checkbox');
|
||||||
checkboxes.forEach(cb => {
|
checkboxes.forEach(cb => {
|
||||||
@@ -1496,6 +1562,11 @@ document.getElementById('bulk-select-all').addEventListener('change', function()
|
|||||||
else bulkSelectedIds.delete(cb.dataset.ticketId);
|
else bulkSelectedIds.delete(cb.dataset.ticketId);
|
||||||
});
|
});
|
||||||
updateBulkToolbar();
|
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) => {
|
document.getElementById('tickets-tbody').addEventListener('click', (e) => {
|
||||||
@@ -2268,13 +2339,30 @@ async function initTicketSettings() {
|
|||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
// Postilaatikoiden näkyvyys — checkbox per postilaatikko
|
// 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);
|
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;">
|
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' : ''}>
|
<input type="checkbox" class="mb-visibility-cb" data-mailbox-id="${mb.id}" ${!isHidden ? 'checked' : ''}>
|
||||||
<span>${esc(mb.company_nimi)} — ${esc(mb.nimi)} <${esc(mb.smtp_from_email)}></span>
|
<span>${esc(mb.company_nimi)} — ${esc(mb.nimi)} <${esc(mb.smtp_from_email)}></span>
|
||||||
</label>`;
|
</label>`;
|
||||||
}).join('');
|
}).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) {
|
} catch (e) {
|
||||||
sigContainer.innerHTML = '<p style="color:red;font-size:0.85rem;">Virhe ladattaessa postilaatikoita.</p>';
|
sigContainer.innerHTML = '<p style="color:red;font-size:0.85rem;">Virhe ladattaessa postilaatikoita.</p>';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user