feat: vastauspohjien hallinta asiakaspalvelu-tabissa + allekirjoitus-checkbox

- Vastauspohjat nyt hallittavissa Asiakaspalvelu-tabin kautta (kaikille käyttäjille)
- Uusi "Vastauspohjat" -nappi tikettilistan yläpalkissa
- CRUD: lisää, muokkaa, poista vastauspohjia tukitabin näkymässä
- "Älä käytä allekirjoitusta" -checkbox vastauslomakkeessa (oletuksena päällä)
- Backend: no_signature-parametri estää allekirjoituksen liittämisen
- Poistettu orpo profiili-vastauspohjien JS-koodi

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 21:51:56 +02:00
parent f6e11f8426
commit 2d2680483c
3 changed files with 154 additions and 9 deletions

113
script.js
View File

@@ -1184,6 +1184,7 @@ document.getElementById('profile-form').addEventListener('submit', async (e) =>
} catch (e) { alert(e.message); }
});
// ==================== TICKETS (ASIAKASPALVELU) ====================
let tickets = [];
@@ -1564,8 +1565,10 @@ async function showTicketDetail(id, companyId = '') {
</div>`;
}).join('');
// Show detail, hide list
// Show detail, hide list + other views
document.getElementById('ticket-list-view').style.display = 'none';
document.getElementById('ticket-rules-view').style.display = 'none';
document.getElementById('ticket-templates-view').style.display = 'none';
document.getElementById('ticket-detail-view').style.display = 'block';
// Reset reply form
@@ -1598,8 +1601,9 @@ async function showTicketDetail(id, companyId = '') {
// Allekirjoituksen esikatselu
function updateSignaturePreview(mbId) {
const sigPreview = document.getElementById('signature-preview');
const noSigCheck = document.getElementById('reply-no-signature');
const sig = currentUserSignatures[mbId] || '';
if (sig) {
if (sig && !(noSigCheck && noSigCheck.checked)) {
sigPreview.textContent = '-- \n' + sig;
sigPreview.style.display = 'block';
} else {
@@ -1608,6 +1612,15 @@ async function showTicketDetail(id, companyId = '') {
}
updateSignaturePreview(ticket.mailbox_id || '');
// Allekirjoitus-checkbox: päivitä esikatselu vaihdettaessa
const noSigCheckbox = document.getElementById('reply-no-signature');
if (noSigCheckbox) {
noSigCheckbox.addEventListener('change', () => {
const mbSelect = document.getElementById('reply-mailbox-select');
updateSignaturePreview(mbSelect ? mbSelect.value : '');
});
}
// Vastauspohjat — lataa dropdown
try {
const templates = await apiCall('reply_templates');
@@ -1634,6 +1647,7 @@ async function showTicketDetail(id, companyId = '') {
function showTicketListView() {
document.getElementById('ticket-detail-view').style.display = 'none';
document.getElementById('ticket-rules-view').style.display = 'none';
document.getElementById('ticket-templates-view').style.display = 'none';
document.getElementById('ticket-list-view').style.display = 'block';
currentTicketId = null;
// Reset bulk selection
@@ -1659,19 +1673,23 @@ document.querySelectorAll('.btn-reply-tab').forEach(btn => {
const sigPrev = document.getElementById('signature-preview');
const metaFields = document.getElementById('reply-meta-fields');
const tplWrap = document.getElementById('reply-template-select-wrap');
const noSigLabel = document.getElementById('reply-no-signature') ? document.getElementById('reply-no-signature').closest('label') : null;
if (ticketReplyType === 'note') {
textarea.placeholder = 'Kirjoita sisäinen muistiinpano...';
sendBtn.textContent = 'Tallenna muistiinpano';
sigPrev.style.display = 'none';
if (metaFields) metaFields.style.display = 'none';
if (tplWrap) tplWrap.style.display = 'none';
if (noSigLabel) noSigLabel.style.display = 'none';
} else {
textarea.placeholder = 'Kirjoita vastaus...';
sendBtn.textContent = 'Lähetä vastaus';
if (metaFields) metaFields.style.display = '';
if (tplWrap) tplWrap.style.display = '';
// Näytä allekirjoitus jos on asetettu
if (sigPrev.textContent.trim()) sigPrev.style.display = 'block';
if (noSigLabel) noSigLabel.style.display = '';
// Näytä allekirjoitus jos on asetettu ja checkbox sallii
const noSigCheck = document.getElementById('reply-no-signature');
if (sigPrev.textContent.trim() && !(noSigCheck && noSigCheck.checked)) sigPrev.style.display = 'block';
}
});
});
@@ -1692,8 +1710,10 @@ document.getElementById('btn-send-reply').addEventListener('click', async () =>
if (ticketReplyType !== 'note') {
const mbSel = document.getElementById('reply-mailbox-select');
const ccFld = document.getElementById('reply-cc');
const noSig = document.getElementById('reply-no-signature');
if (mbSel) payload.mailbox_id = mbSel.value;
if (ccFld) payload.cc = ccFld.value.trim();
if (noSig && noSig.checked) payload.no_signature = true;
}
await apiCall(action + ticketCompanyParam(), 'POST', payload);
// Reload the detail view
@@ -1821,6 +1841,7 @@ function renderRules() {
function showRulesView() {
document.getElementById('ticket-list-view').style.display = 'none';
document.getElementById('ticket-detail-view').style.display = 'none';
document.getElementById('ticket-templates-view').style.display = 'none';
document.getElementById('ticket-rules-view').style.display = 'block';
loadRules();
}
@@ -1898,6 +1919,90 @@ async function toggleRule(id, enabled) {
} catch (e) { alert(e.message); }
}
// ==================== VASTAUSPOHJAT (TUKITABISSA) ====================
function showTemplatesView() {
document.getElementById('ticket-list-view').style.display = 'none';
document.getElementById('ticket-detail-view').style.display = 'none';
document.getElementById('ticket-rules-view').style.display = 'none';
document.getElementById('ticket-templates-view').style.display = 'block';
hideTplForm();
renderTplList();
}
function hideTemplatesView() {
document.getElementById('ticket-templates-view').style.display = 'none';
document.getElementById('ticket-list-view').style.display = 'block';
}
function renderTplList() {
const list = document.getElementById('tpl-list');
if (!list) return;
if (replyTemplates.length === 0) {
list.innerHTML = '<p style="color:#aaa;font-size:0.85rem;">Ei vastauspohjia vielä. Lisää ensimmäinen klikkaamalla "+ Lisää pohja".</p>';
return;
}
list.innerHTML = replyTemplates.map(t =>
`<div style="display:flex;justify-content:space-between;align-items:center;padding:0.6rem 0;border-bottom:1px solid #f0f2f5;">
<div style="min-width:0;flex:1;">
<strong style="font-size:0.9rem;">${esc(t.nimi)}</strong>
<div style="font-size:0.8rem;color:#888;max-width:450px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${esc(t.body.substring(0, 100))}</div>
</div>
<div style="display:flex;gap:0.3rem;flex-shrink:0;">
<button class="btn-secondary" onclick="editTpl('${t.id}')" style="padding:4px 10px;font-size:0.8rem;">Muokkaa</button>
<button class="btn-danger" onclick="deleteTpl('${t.id}')" style="padding:4px 10px;font-size:0.8rem;">Poista</button>
</div>
</div>`
).join('');
}
function showTplForm(tpl) {
document.getElementById('tpl-form-container').style.display = '';
document.getElementById('tpl-form-title').textContent = tpl ? 'Muokkaa vastauspohjaa' : 'Uusi vastauspohja';
document.getElementById('tpl-form-id').value = tpl ? tpl.id : '';
document.getElementById('tpl-form-name').value = tpl ? tpl.nimi : '';
document.getElementById('tpl-form-body').value = tpl ? tpl.body : '';
}
function hideTplForm() {
document.getElementById('tpl-form-container').style.display = 'none';
}
document.getElementById('btn-ticket-templates').addEventListener('click', async () => {
await loadTemplates();
showTemplatesView();
});
document.getElementById('btn-templates-back').addEventListener('click', () => hideTemplatesView());
document.getElementById('btn-add-tpl').addEventListener('click', () => showTplForm(null));
document.getElementById('btn-cancel-tpl').addEventListener('click', () => hideTplForm());
document.getElementById('btn-save-tpl').addEventListener('click', async () => {
const nimi = document.getElementById('tpl-form-name').value.trim();
const body = document.getElementById('tpl-form-body').value.trim();
if (!nimi || !body) { alert('Täytä nimi ja sisältö'); return; }
const id = document.getElementById('tpl-form-id').value || undefined;
try {
await apiCall('reply_template_save', 'POST', { id, nimi, body });
hideTplForm();
await loadTemplates();
renderTplList();
} catch (e) { alert(e.message); }
});
window.editTpl = function(id) {
const t = replyTemplates.find(x => x.id === id);
if (t) showTplForm(t);
};
window.deleteTpl = async function(id) {
if (!confirm('Poistetaanko vastauspohja?')) return;
try {
await apiCall('reply_template_delete', 'POST', { id });
await loadTemplates();
renderTplList();
} catch (e) { alert(e.message); }
};
// ==================== BULK ACTIONS ====================
let bulkSelectedIds = new Set();