Add ticket types, move Asiakaspalvelu tab first, hide closed tickets
- Asiakaspalvelu tab moved to first position in navigation - Added ticket type field (Laskutus, Tekniikka, Vika, Muu) with type filter dropdown and type column in ticket list - Type selector in ticket detail view with API endpoint - Closed tickets hidden by default (selectable via "Kaikki tilat") - Käsittelyssä rows highlighted with green background - Type badges with color coding per category Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
46
script.js
46
script.js
@@ -958,6 +958,13 @@ const ticketStatusLabels = {
|
||||
suljettu: 'Suljettu',
|
||||
};
|
||||
|
||||
const ticketTypeLabels = {
|
||||
laskutus: 'Laskutus',
|
||||
tekniikka: 'Tekniikka',
|
||||
vika: 'Vika',
|
||||
muu: 'Muu',
|
||||
};
|
||||
|
||||
async function loadTickets() {
|
||||
try {
|
||||
tickets = await apiCall('tickets');
|
||||
@@ -968,7 +975,20 @@ async function loadTickets() {
|
||||
function renderTickets() {
|
||||
const query = document.getElementById('ticket-search-input').value.toLowerCase().trim();
|
||||
const statusFilter = document.getElementById('ticket-status-filter').value;
|
||||
const typeFilter = document.getElementById('ticket-type-filter').value;
|
||||
let filtered = tickets;
|
||||
|
||||
// Default: hide closed tickets unless explicitly selected or "kaikki"
|
||||
if (!statusFilter || statusFilter === '') {
|
||||
filtered = filtered.filter(t => t.status !== 'suljettu');
|
||||
} else if (statusFilter !== 'kaikki') {
|
||||
filtered = filtered.filter(t => t.status === statusFilter);
|
||||
}
|
||||
|
||||
if (typeFilter) {
|
||||
filtered = filtered.filter(t => (t.type || 'muu') === typeFilter);
|
||||
}
|
||||
|
||||
if (query) {
|
||||
filtered = filtered.filter(t =>
|
||||
(t.subject || '').toLowerCase().includes(query) ||
|
||||
@@ -976,9 +996,6 @@ function renderTickets() {
|
||||
(t.from_email || '').toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
if (statusFilter) {
|
||||
filtered = filtered.filter(t => t.status === statusFilter);
|
||||
}
|
||||
|
||||
const ttbody = document.getElementById('tickets-tbody');
|
||||
const noTickets = document.getElementById('no-tickets');
|
||||
@@ -991,8 +1008,11 @@ function renderTickets() {
|
||||
document.getElementById('tickets-table').style.display = 'table';
|
||||
ttbody.innerHTML = filtered.map(t => {
|
||||
const lastType = t.last_message_type === 'reply_out' ? '→' : (t.last_message_type === 'note' ? '📝' : '←');
|
||||
return `<tr data-ticket-id="${t.id}">
|
||||
const typeLabel = ticketTypeLabels[t.type] || 'Muu';
|
||||
const rowClass = t.status === 'kasittelyssa' ? 'ticket-row-active' : '';
|
||||
return `<tr data-ticket-id="${t.id}" class="${rowClass}">
|
||||
<td><span class="ticket-status ticket-status-${t.status}">${ticketStatusLabels[t.status] || t.status}</span></td>
|
||||
<td><span class="ticket-type ticket-type-${t.type || 'muu'}">${typeLabel}</span></td>
|
||||
<td><strong>${esc(t.subject)}</strong></td>
|
||||
<td>${esc(t.from_name || t.from_email)}</td>
|
||||
<td style="text-align:center;">${lastType} ${t.message_count}</td>
|
||||
@@ -1001,7 +1021,9 @@ function renderTickets() {
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
document.getElementById('ticket-count').textContent = `${tickets.length} tikettiä`;
|
||||
|
||||
const openCount = tickets.filter(t => t.status !== 'suljettu').length;
|
||||
document.getElementById('ticket-count').textContent = `${openCount} avointa tikettiä (${tickets.length} yht.)`;
|
||||
|
||||
// Status summary
|
||||
const counts = {};
|
||||
@@ -1015,6 +1037,7 @@ function renderTickets() {
|
||||
|
||||
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('tickets-tbody').addEventListener('click', (e) => {
|
||||
const row = e.target.closest('tr');
|
||||
@@ -1036,6 +1059,12 @@ async function showTicketDetail(id) {
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
|
||||
<select id="ticket-type-select" style="padding:6px 10px;border:2px solid #e0e0e0;border-radius:8px;font-size:0.85rem;">
|
||||
<option value="muu" ${(ticket.type || 'muu') === 'muu' ? 'selected' : ''}>Muu</option>
|
||||
<option value="laskutus" ${ticket.type === 'laskutus' ? 'selected' : ''}>Laskutus</option>
|
||||
<option value="tekniikka" ${ticket.type === 'tekniikka' ? 'selected' : ''}>Tekniikka</option>
|
||||
<option value="vika" ${ticket.type === 'vika' ? 'selected' : ''}>Vika</option>
|
||||
</select>
|
||||
<select id="ticket-status-select" style="padding:6px 10px;border:2px solid #e0e0e0;border-radius:8px;font-size:0.85rem;">
|
||||
<option value="uusi" ${ticket.status === 'uusi' ? 'selected' : ''}>Uusi</option>
|
||||
<option value="kasittelyssa" ${ticket.status === 'kasittelyssa' ? 'selected' : ''}>Käsittelyssä</option>
|
||||
@@ -1063,6 +1092,13 @@ async function showTicketDetail(id) {
|
||||
});
|
||||
} catch (e) { /* non-admin may not access users */ }
|
||||
|
||||
// Type change handler
|
||||
document.getElementById('ticket-type-select').addEventListener('change', async function() {
|
||||
try {
|
||||
await apiCall('ticket_type', 'POST', { id: currentTicketId, type: this.value });
|
||||
} catch (e) { alert(e.message); }
|
||||
});
|
||||
|
||||
// Status change handler
|
||||
document.getElementById('ticket-status-select').addEventListener('change', async function() {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user