Add auto-rules management, bulk actions for tickets
- Auto-rules JS: load, render, save, edit, delete, toggle rules - Rules UI: Säännöt view with rule list and form - Bulk actions: checkbox selection in ticket list - Bulk close/delete endpoints (ticket_bulk_status, ticket_bulk_delete) - Bulk toolbar with select all, close selected, delete selected Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
180
script.js
180
script.js
@@ -1016,7 +1016,9 @@ function renderTickets() {
|
||||
const lastType = t.last_message_type === 'reply_out' ? '→' : (t.last_message_type === 'note' ? '📝' : '←');
|
||||
const typeLabel = ticketTypeLabels[t.type] || 'Muu';
|
||||
const rowClass = t.status === 'kasittelyssa' ? 'ticket-row-active' : '';
|
||||
const checked = bulkSelectedIds.has(t.id) ? 'checked' : '';
|
||||
return `<tr data-ticket-id="${t.id}" class="${rowClass}">
|
||||
<td onclick="event.stopPropagation()"><input type="checkbox" class="ticket-checkbox" data-ticket-id="${t.id}" ${checked}></td>
|
||||
<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>
|
||||
@@ -1026,6 +1028,14 @@ function renderTickets() {
|
||||
<td class="nowrap">${esc((t.updated || '').substring(0, 16))}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
// Re-attach checkbox listeners
|
||||
document.querySelectorAll('.ticket-checkbox').forEach(cb => {
|
||||
cb.addEventListener('change', function() {
|
||||
if (this.checked) bulkSelectedIds.add(this.dataset.ticketId);
|
||||
else bulkSelectedIds.delete(this.dataset.ticketId);
|
||||
updateBulkToolbar();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const openCount = tickets.filter(t => t.status !== 'suljettu').length;
|
||||
@@ -1045,6 +1055,15 @@ document.getElementById('ticket-search-input').addEventListener('input', () => r
|
||||
document.getElementById('ticket-status-filter').addEventListener('change', () => renderTickets());
|
||||
document.getElementById('ticket-type-filter').addEventListener('change', () => renderTickets());
|
||||
document.getElementById('ticket-show-closed').addEventListener('change', () => renderTickets());
|
||||
document.getElementById('bulk-select-all').addEventListener('change', function() {
|
||||
const checkboxes = document.querySelectorAll('.ticket-checkbox');
|
||||
checkboxes.forEach(cb => {
|
||||
cb.checked = this.checked;
|
||||
if (this.checked) bulkSelectedIds.add(cb.dataset.ticketId);
|
||||
else bulkSelectedIds.delete(cb.dataset.ticketId);
|
||||
});
|
||||
updateBulkToolbar();
|
||||
});
|
||||
|
||||
document.getElementById('tickets-tbody').addEventListener('click', (e) => {
|
||||
const row = e.target.closest('tr');
|
||||
@@ -1187,8 +1206,14 @@ async function showTicketDetail(id) {
|
||||
|
||||
function showTicketListView() {
|
||||
document.getElementById('ticket-detail-view').style.display = 'none';
|
||||
document.getElementById('ticket-rules-view').style.display = 'none';
|
||||
document.getElementById('ticket-list-view').style.display = 'block';
|
||||
currentTicketId = null;
|
||||
// Reset bulk selection
|
||||
bulkSelectedIds.clear();
|
||||
const selectAll = document.getElementById('bulk-select-all');
|
||||
if (selectAll) selectAll.checked = false;
|
||||
updateBulkToolbar();
|
||||
}
|
||||
|
||||
document.getElementById('btn-ticket-back').addEventListener('click', () => {
|
||||
@@ -1266,6 +1291,161 @@ document.getElementById('btn-fetch-emails').addEventListener('click', async () =
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== TICKET RULES (AUTOMAATTISÄÄNNÖT) ====================
|
||||
|
||||
let ticketRules = [];
|
||||
let editingRuleId = null;
|
||||
|
||||
async function loadRules() {
|
||||
try {
|
||||
ticketRules = await apiCall('ticket_rules');
|
||||
renderRules();
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderRules() {
|
||||
const list = document.getElementById('rules-list');
|
||||
if (ticketRules.length === 0) {
|
||||
list.innerHTML = '<div style="text-align:center;padding:2rem;color:#aaa;">Ei sääntöjä vielä. Lisää ensimmäinen sääntö.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = ticketRules.map(r => {
|
||||
const conditions = [];
|
||||
if (r.from_contains) conditions.push('Lähettäjä: <strong>' + esc(r.from_contains) + '</strong>');
|
||||
if (r.subject_contains) conditions.push('Otsikko: <strong>' + esc(r.subject_contains) + '</strong>');
|
||||
const actions = [];
|
||||
if (r.set_status) actions.push('Tila → ' + (ticketStatusLabels[r.set_status] || r.set_status));
|
||||
if (r.set_type) actions.push('Tyyppi → ' + (ticketTypeLabels[r.set_type] || r.set_type));
|
||||
return `<div style="display:flex;justify-content:space-between;align-items:center;padding:0.75rem 1rem;background:${r.enabled ? '#f8f9fb' : '#fafafa'};border:1px solid #e8ebf0;border-radius:8px;margin-bottom:0.5rem;opacity:${r.enabled ? '1' : '0.5'};">
|
||||
<div>
|
||||
<div style="font-weight:600;color:#0f3460;font-size:0.9rem;">${esc(r.name)}</div>
|
||||
<div style="font-size:0.8rem;color:#888;margin-top:2px;">
|
||||
${conditions.length ? 'Ehdot: ' + conditions.join(', ') : '<em>Ei ehtoja</em>'} → ${actions.length ? actions.join(', ') : '<em>Ei toimenpiteitä</em>'}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.4rem;align-items:center;flex-shrink:0;">
|
||||
<label style="cursor:pointer;font-size:0.8rem;color:#888;display:flex;align-items:center;gap:3px;">
|
||||
<input type="checkbox" ${r.enabled ? 'checked' : ''} onchange="toggleRule('${r.id}', this.checked)"> Päällä
|
||||
</label>
|
||||
<button onclick="editRule('${r.id}')" style="background:none;border:none;cursor:pointer;font-size:1rem;padding:4px;">✎</button>
|
||||
<button onclick="deleteRule('${r.id}')" style="background:none;border:none;cursor:pointer;font-size:1rem;padding:4px;color:#e74c3c;">🗑</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function showRulesView() {
|
||||
document.getElementById('ticket-list-view').style.display = 'none';
|
||||
document.getElementById('ticket-detail-view').style.display = 'none';
|
||||
document.getElementById('ticket-rules-view').style.display = 'block';
|
||||
loadRules();
|
||||
}
|
||||
|
||||
function hideRulesView() {
|
||||
document.getElementById('ticket-rules-view').style.display = 'none';
|
||||
document.getElementById('ticket-list-view').style.display = 'block';
|
||||
}
|
||||
|
||||
function showRuleForm(rule) {
|
||||
document.getElementById('rule-form-container').style.display = '';
|
||||
document.getElementById('rule-form-title').textContent = rule ? 'Muokkaa sääntöä' : 'Uusi sääntö';
|
||||
document.getElementById('rule-form-id').value = rule ? rule.id : '';
|
||||
document.getElementById('rule-form-name').value = rule ? rule.name : '';
|
||||
document.getElementById('rule-form-from').value = rule ? rule.from_contains : '';
|
||||
document.getElementById('rule-form-subject').value = rule ? rule.subject_contains : '';
|
||||
document.getElementById('rule-form-status').value = rule ? (rule.set_status || '') : '';
|
||||
document.getElementById('rule-form-type').value = rule ? (rule.set_type || '') : '';
|
||||
editingRuleId = rule ? rule.id : null;
|
||||
}
|
||||
|
||||
function hideRuleForm() {
|
||||
document.getElementById('rule-form-container').style.display = 'none';
|
||||
editingRuleId = null;
|
||||
}
|
||||
|
||||
document.getElementById('btn-ticket-rules').addEventListener('click', () => showRulesView());
|
||||
document.getElementById('btn-rules-back').addEventListener('click', () => hideRulesView());
|
||||
document.getElementById('btn-add-rule').addEventListener('click', () => showRuleForm(null));
|
||||
document.getElementById('btn-cancel-rule').addEventListener('click', () => hideRuleForm());
|
||||
|
||||
document.getElementById('btn-save-rule').addEventListener('click', async () => {
|
||||
const name = document.getElementById('rule-form-name').value.trim();
|
||||
if (!name) { alert('Nimi puuttuu'); return; }
|
||||
const data = {
|
||||
name,
|
||||
from_contains: document.getElementById('rule-form-from').value.trim(),
|
||||
subject_contains: document.getElementById('rule-form-subject').value.trim(),
|
||||
set_status: document.getElementById('rule-form-status').value,
|
||||
set_type: document.getElementById('rule-form-type').value,
|
||||
enabled: true,
|
||||
};
|
||||
const existingId = document.getElementById('rule-form-id').value;
|
||||
if (existingId) data.id = existingId;
|
||||
try {
|
||||
await apiCall('ticket_rule_save', 'POST', data);
|
||||
hideRuleForm();
|
||||
await loadRules();
|
||||
} catch (e) { alert(e.message); }
|
||||
});
|
||||
|
||||
async function editRule(id) {
|
||||
const rule = ticketRules.find(r => r.id === id);
|
||||
if (rule) showRuleForm(rule);
|
||||
}
|
||||
|
||||
async function deleteRule(id) {
|
||||
if (!confirm('Poistetaanko sääntö?')) return;
|
||||
try {
|
||||
await apiCall('ticket_rule_delete', 'POST', { id });
|
||||
await loadRules();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
async function toggleRule(id, enabled) {
|
||||
const rule = ticketRules.find(r => r.id === id);
|
||||
if (!rule) return;
|
||||
try {
|
||||
await apiCall('ticket_rule_save', 'POST', { ...rule, enabled });
|
||||
await loadRules();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
// ==================== BULK ACTIONS ====================
|
||||
|
||||
let bulkSelectedIds = new Set();
|
||||
|
||||
function updateBulkToolbar() {
|
||||
const toolbar = document.getElementById('bulk-actions-toolbar');
|
||||
if (bulkSelectedIds.size > 0) {
|
||||
toolbar.style.display = 'flex';
|
||||
document.getElementById('bulk-count').textContent = bulkSelectedIds.size + ' valittu';
|
||||
} else {
|
||||
toolbar.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkCloseSelected() {
|
||||
if (bulkSelectedIds.size === 0) return;
|
||||
if (!confirm(`Suljetaanko ${bulkSelectedIds.size} tikettiä?`)) return;
|
||||
try {
|
||||
await apiCall('ticket_bulk_status', 'POST', { ids: [...bulkSelectedIds], status: 'suljettu' });
|
||||
bulkSelectedIds.clear();
|
||||
updateBulkToolbar();
|
||||
await loadTickets();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
async function bulkDeleteSelected() {
|
||||
if (bulkSelectedIds.size === 0) return;
|
||||
if (!confirm(`Poistetaanko ${bulkSelectedIds.size} tikettiä pysyvästi?`)) return;
|
||||
try {
|
||||
await apiCall('ticket_bulk_delete', 'POST', { ids: [...bulkSelectedIds] });
|
||||
bulkSelectedIds.clear();
|
||||
updateBulkToolbar();
|
||||
await loadTickets();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
// ==================== SETTINGS ====================
|
||||
|
||||
async function loadSettings() {
|
||||
|
||||
Reference in New Issue
Block a user