Liitetiedostot tikettivastausten sähköposteihin + muistiinpanojen piilotus
- Lisää liitetiedostojen tuki vastauslomakkeeseen (📎 Liitä tiedosto -nappi) - Tuki max 25 MB tiedostoille, useita liitteitä kerralla - Zammad API: liitteet base64-muodossa attachments-kentässä - SMTP: multipart/mixed MIME (boundary, Content-Disposition: attachment) - Sisäiset muistiinpanot (note) suodatetaan pois quoted threadista (eivät näy asiakkaille lähtevissä sähköposteissa) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
49
script.js
49
script.js
@@ -2002,6 +2002,50 @@ document.querySelectorAll('.btn-reply-tab').forEach(btn => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Liitetiedostot ----
|
||||
let replyAttachments = []; // [{name, size, type, data (base64)}]
|
||||
|
||||
document.getElementById('reply-file-input').addEventListener('change', function() {
|
||||
const files = Array.from(this.files);
|
||||
files.forEach(file => {
|
||||
if (file.size > 25 * 1024 * 1024) {
|
||||
alert(`Tiedosto "${file.name}" on liian suuri (max 25 MB)`);
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result.split(',')[1]; // poista data:...;base64, prefix
|
||||
replyAttachments.push({ name: file.name, size: file.size, type: file.type || 'application/octet-stream', data: base64 });
|
||||
renderReplyAttachments();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
this.value = ''; // nollaa input jotta saman tiedoston voi lisätä uudelleen
|
||||
});
|
||||
|
||||
function renderReplyAttachments() {
|
||||
const list = document.getElementById('reply-attachments-list');
|
||||
const count = document.getElementById('reply-attachments-count');
|
||||
if (replyAttachments.length === 0) {
|
||||
list.innerHTML = '';
|
||||
count.textContent = '';
|
||||
return;
|
||||
}
|
||||
count.textContent = `${replyAttachments.length} liite${replyAttachments.length > 1 ? 'ttä' : ''}`;
|
||||
list.innerHTML = replyAttachments.map((a, i) => {
|
||||
const sizeStr = a.size < 1024 ? a.size + ' B' : (a.size < 1048576 ? (a.size / 1024).toFixed(1) + ' KB' : (a.size / 1048576).toFixed(1) + ' MB');
|
||||
return `<span style="display:inline-flex;align-items:center;gap:0.3rem;background:#f0f2f5;padding:3px 8px;border-radius:12px;font-size:0.8rem;">
|
||||
📄 ${esc(a.name)} <span style="color:#aaa;">(${sizeStr})</span>
|
||||
<button onclick="removeReplyAttachment(${i})" style="background:none;border:none;cursor:pointer;color:#c00;font-size:0.9rem;padding:0 2px;" title="Poista">×</button>
|
||||
</span>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function removeReplyAttachment(index) {
|
||||
replyAttachments.splice(index, 1);
|
||||
renderReplyAttachments();
|
||||
}
|
||||
|
||||
// Send reply or note
|
||||
document.getElementById('btn-send-reply').addEventListener('click', async () => {
|
||||
const body = document.getElementById('ticket-reply-body').value.trim();
|
||||
@@ -2039,6 +2083,7 @@ document.getElementById('btn-send-reply').addEventListener('click', async () =>
|
||||
if (zToFld && zToFld.value.trim()) zPayload.to = zToFld.value.trim();
|
||||
if (zCcFld && zCcFld.value.trim()) zPayload.cc = zCcFld.value.trim();
|
||||
if (zBccFld && zBccFld.value.trim()) zPayload.bcc = zBccFld.value.trim();
|
||||
if (replyAttachments.length > 0) zPayload.attachments = replyAttachments;
|
||||
await apiCall('zammad_reply' + ticketCompanyParam(), 'POST', zPayload);
|
||||
} else {
|
||||
const action = ticketReplyType === 'note' ? 'ticket_note' : 'ticket_reply';
|
||||
@@ -2055,8 +2100,12 @@ document.getElementById('btn-send-reply').addEventListener('click', async () =>
|
||||
if (bccFld) payload.bcc = bccFld.value.trim();
|
||||
if (useSig && !useSig.checked) payload.no_signature = true;
|
||||
}
|
||||
if (replyAttachments.length > 0) payload.attachments = replyAttachments;
|
||||
await apiCall(action + ticketCompanyParam(), 'POST', payload);
|
||||
}
|
||||
// Tyhjennä liitteet
|
||||
replyAttachments = [];
|
||||
renderReplyAttachments();
|
||||
// Reload the detail view
|
||||
await showTicketDetail(currentTicketId, currentTicketCompanyId);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user