Add dedicated SAG email tab with preview and filters
This commit is contained in:
parent
b80f91fae1
commit
827463d59e
@ -795,6 +795,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="emails-tab" data-bs-toggle="tab" data-bs-target="#emails" type="button" role="tab" data-module-tab="emails">
|
||||||
|
<i class="bi bi-envelope me-2"></i>E-mail
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="sales-tab" data-bs-toggle="tab" data-bs-target="#sales" type="button" role="tab" data-module-tab="sales">
|
<button class="nav-link" id="sales-tab" data-bs-toggle="tab" data-bs-target="#sales" type="button" role="tab" data-module-tab="sales">
|
||||||
<i class="bi bi-basket3 me-2"></i>Varekøb & Salg
|
<i class="bi bi-basket3 me-2"></i>Varekøb & Salg
|
||||||
@ -1183,23 +1188,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Linked Emails -->
|
<!-- Email Overview -->
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="card h-100" data-module="emails" data-has-content="unknown">
|
<div class="card h-100">
|
||||||
<div class="card-header">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h6 class="mb-0" style="color: var(--accent);">📧 Linkede e-mails</h6>
|
<h6 class="mb-0" style="color: var(--accent);">📧 E-mail</h6>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" type="button" onclick="openCaseEmailTab()">
|
||||||
|
<i class="bi bi-box-arrow-up-right me-1"></i>Åbn e-mail-fane
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Email Drop Zone -->
|
<div class="card-body d-flex flex-column justify-content-center">
|
||||||
<div class="card-body p-0 d-flex flex-column" id="emailDropZone">
|
<div class="text-muted small mb-2">Den nye e-mail-del ligger i en dedikeret fane med flere funktioner.</div>
|
||||||
<div class="p-3 border-bottom bg-light position-relative">
|
<ul class="small text-muted mb-3 ps-3">
|
||||||
<input type="text" class="form-control form-control-sm" id="emailSearchInput" placeholder="Søg og link e-mail..." autocomplete="off">
|
<li>Liste over sagens e-mails</li>
|
||||||
<div class="list-group position-absolute shadow-sm" id="emailSearchResults" style="z-index: 1000; display: none; top: 100%; left: 0; right: 0; max-height: 300px; overflow-y: auto;"></div>
|
<li>Preview af e-mail-indhold</li>
|
||||||
</div>
|
<li>Søgning og filtrering</li>
|
||||||
<div class="text-center p-2 small text-muted fst-italic border-bottom">
|
<li>Vedhæftninger, link/unlink og import</li>
|
||||||
Træk .msg/.eml filer hertil for at importere
|
</ul>
|
||||||
</div>
|
<div>
|
||||||
<div class="list-group list-group-flush flex-grow-1 overflow-auto" id="linked-emails-list" style="max-height: 250px;">
|
<button class="btn btn-primary btn-sm" type="button" onclick="openCaseEmailTab()">
|
||||||
<div class="p-3 text-center text-muted">Ingen e-mails linket...</div>
|
<i class="bi bi-envelope-open me-1"></i>Gå til E-mail
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2941,6 +2950,71 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> <!-- End Details Tab -->
|
</div> <!-- End Details Tab -->
|
||||||
|
|
||||||
|
<!-- E-mail Tab -->
|
||||||
|
<div class="tab-pane fade" id="emails" role="tabpanel" tabindex="0" data-module="emails" data-has-content="unknown">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0 text-primary"><i class="bi bi-envelope me-2"></i>E-mail på sagen</h6>
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
<input type="file" id="emailImportInput" accept=".eml,.msg" style="display:none" onchange="if(this.files?.length){ uploadEmailFile(this.files[0]); this.value=''; }">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" type="button" onclick="document.getElementById('emailImportInput').click()">
|
||||||
|
<i class="bi bi-cloud-upload me-1"></i>Importér .eml/.msg
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="emailDropZone">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-lg-4 position-relative">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="emailSearchInput" placeholder="Søg og link e-mail..." autocomplete="off">
|
||||||
|
<div class="list-group position-absolute shadow-sm" id="emailSearchResults" style="z-index: 1000; display: none; top: 100%; left: 0; right: 0; max-height: 300px; overflow-y: auto;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="emailFilterInput" placeholder="Filtrer i linkede e-mails...">
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<select id="emailAttachmentFilter" class="form-select form-select-sm">
|
||||||
|
<option value="all">Alle vedhæftninger</option>
|
||||||
|
<option value="with">Med vedhæftning</option>
|
||||||
|
<option value="without">Uden vedhæftning</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<select id="emailReadFilter" class="form-select form-select-sm">
|
||||||
|
<option value="all">Alle læsestatus</option>
|
||||||
|
<option value="unread">Ulæste</option>
|
||||||
|
<option value="read">Læste</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted fst-italic mt-2">Tip: Træk .msg/.eml fil hertil for at importere direkte på sagen.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="border rounded h-100 d-flex flex-column">
|
||||||
|
<div class="p-2 border-bottom d-flex justify-content-between align-items-center">
|
||||||
|
<span class="small fw-semibold text-secondary">Linkede e-mails</span>
|
||||||
|
<span class="badge bg-light text-dark border" id="linkedEmailsCount">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush flex-grow-1 overflow-auto" id="linked-emails-list" style="min-height: 420px; max-height: 65vh;">
|
||||||
|
<div class="p-3 text-center text-muted">Ingen e-mails linket...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="border rounded h-100 d-flex flex-column" id="email-preview-panel" style="min-height: 420px;">
|
||||||
|
<div class="p-3 text-center text-muted d-flex align-items-center justify-content-center flex-grow-1">
|
||||||
|
Vælg en e-mail i listen for at se indhold og vedhæftninger
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Solution Tab -->
|
<!-- Solution Tab -->
|
||||||
<div class="tab-pane fade" id="solution" role="tabpanel" tabindex="0" data-module="solution" data-has-content="{{ 'true' if solution or is_nextcloud else 'false' }}">
|
<div class="tab-pane fade" id="solution" role="tabpanel" tabindex="0" data-module="solution" data-has-content="{{ 'true' if solution or is_nextcloud else 'false' }}">
|
||||||
<!-- Nextcloud Integration Box -->
|
<!-- Nextcloud Integration Box -->
|
||||||
@ -5809,6 +5883,16 @@
|
|||||||
|
|
||||||
// ---------------- EMAILS ----------------
|
// ---------------- EMAILS ----------------
|
||||||
|
|
||||||
|
let linkedEmailsCache = [];
|
||||||
|
let selectedLinkedEmailId = null;
|
||||||
|
|
||||||
|
function openCaseEmailTab() {
|
||||||
|
const trigger = document.getElementById('emails-tab');
|
||||||
|
if (!trigger) return;
|
||||||
|
const instance = bootstrap.Tab.getOrCreateInstance(trigger);
|
||||||
|
instance.show();
|
||||||
|
}
|
||||||
|
|
||||||
async function loadLinkedEmails() {
|
async function loadLinkedEmails() {
|
||||||
const container = document.getElementById('linked-emails-list');
|
const container = document.getElementById('linked-emails-list');
|
||||||
if(!container) return;
|
if(!container) return;
|
||||||
@ -5816,8 +5900,16 @@
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/sag/${caseIds}/email-links`);
|
const res = await fetch(`/api/v1/sag/${caseIds}/email-links`);
|
||||||
if(res.ok) {
|
if(res.ok) {
|
||||||
const emails = await res.json();
|
linkedEmailsCache = await res.json();
|
||||||
renderLinkedEmails(emails);
|
applyLinkedEmailFilters();
|
||||||
|
if (selectedLinkedEmailId && linkedEmailsCache.some(e => Number(e.id) === Number(selectedLinkedEmailId))) {
|
||||||
|
await loadLinkedEmailDetail(selectedLinkedEmailId);
|
||||||
|
} else if (linkedEmailsCache.length > 0) {
|
||||||
|
await loadLinkedEmailDetail(linkedEmailsCache[0].id);
|
||||||
|
} else {
|
||||||
|
selectedLinkedEmailId = null;
|
||||||
|
renderEmailPreviewEmpty();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
container.innerHTML = '<div class="p-3 text-center text-danger">Fejl ved hentning af emails</div>';
|
container.innerHTML = '<div class="p-3 text-center text-danger">Fejl ved hentning af emails</div>';
|
||||||
setModuleContentState('emails', true);
|
setModuleContentState('emails', true);
|
||||||
@ -5829,55 +5921,183 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyLinkedEmailFilters() {
|
||||||
|
const textFilter = (document.getElementById('emailFilterInput')?.value || '').trim().toLowerCase();
|
||||||
|
const attachmentFilter = document.getElementById('emailAttachmentFilter')?.value || 'all';
|
||||||
|
const readFilter = document.getElementById('emailReadFilter')?.value || 'all';
|
||||||
|
|
||||||
|
const filtered = linkedEmailsCache.filter((email) => {
|
||||||
|
if (textFilter) {
|
||||||
|
const haystack = [
|
||||||
|
email.subject,
|
||||||
|
email.sender_email,
|
||||||
|
email.sender_name,
|
||||||
|
email.body_text,
|
||||||
|
email.body_html
|
||||||
|
].join(' ').toLowerCase();
|
||||||
|
if (!haystack.includes(textFilter)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAttachments = Boolean(email.has_attachments) || Number(email.attachment_count || 0) > 0;
|
||||||
|
if (attachmentFilter === 'with' && !hasAttachments) return false;
|
||||||
|
if (attachmentFilter === 'without' && hasAttachments) return false;
|
||||||
|
|
||||||
|
const isRead = Boolean(email.is_read);
|
||||||
|
if (readFilter === 'read' && !isRead) return false;
|
||||||
|
if (readFilter === 'unread' && isRead) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
renderLinkedEmails(filtered);
|
||||||
|
const counter = document.getElementById('linkedEmailsCount');
|
||||||
|
if (counter) counter.textContent = String(filtered.length);
|
||||||
|
}
|
||||||
|
|
||||||
function renderLinkedEmails(emails) {
|
function renderLinkedEmails(emails) {
|
||||||
const container = document.getElementById('linked-emails-list');
|
const container = document.getElementById('linked-emails-list');
|
||||||
|
if (!container) return;
|
||||||
if(!emails || emails.length === 0) {
|
if(!emails || emails.length === 0) {
|
||||||
container.innerHTML = '<div class="p-3 text-center text-muted">Ingen linkede e-mails...</div>';
|
container.innerHTML = '<div class="p-3 text-center text-muted">Ingen linkede e-mails...</div>';
|
||||||
setModuleContentState('emails', false);
|
setModuleContentState('emails', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setModuleContentState('emails', true);
|
setModuleContentState('emails', true);
|
||||||
const threadMap = new Map();
|
container.innerHTML = emails.map(e => {
|
||||||
emails.forEach(e => {
|
const isSelected = Number(selectedLinkedEmailId) === Number(e.id);
|
||||||
const key = e.thread_key || `email-${e.id}`;
|
const receivedDate = e.received_date ? new Date(e.received_date).toLocaleString('da-DK') : '-';
|
||||||
if(!threadMap.has(key)) threadMap.set(key, []);
|
const sender = e.sender_name || e.sender_email || '-';
|
||||||
threadMap.get(key).push(e);
|
const subject = e.subject || '(Ingen emne)';
|
||||||
});
|
const snippetSource = e.body_text || e.body_html || '';
|
||||||
|
const snippet = snippetSource.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 130);
|
||||||
|
const hasAttachments = Boolean(e.has_attachments) || Number(e.attachment_count || 0) > 0;
|
||||||
|
|
||||||
const threads = Array.from(threadMap.values());
|
|
||||||
container.innerHTML = threads.map((threadEmails, threadIndex) => {
|
|
||||||
const labelEmail = threadEmails[0];
|
|
||||||
const messageCount = labelEmail.thread_message_count || threadEmails.length;
|
|
||||||
return `
|
return `
|
||||||
<div class="list-group-item p-0">
|
<button type="button" class="list-group-item list-group-item-action border-0 border-bottom text-start ${isSelected ? 'active' : ''}" onclick="loadLinkedEmailDetail(${e.id})">
|
||||||
<div class="px-3 py-2 border-bottom bg-light d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
||||||
<span class="small fw-semibold text-secondary">Tråd ${threadIndex + 1}</span>
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<span class="badge bg-primary-subtle text-primary-emphasis">${messageCount} beskeder</span>
|
<div class="fw-semibold text-truncate">${escapeHtml(subject)}</div>
|
||||||
</div>
|
<div class="small ${isSelected ? 'text-white-50' : 'text-muted'} text-truncate">${escapeHtml(sender)}</div>
|
||||||
${threadEmails.map(e => `
|
<div class="small ${isSelected ? 'text-white-50' : 'text-muted'} text-truncate">${escapeHtml(snippet || 'Ingen preview')}</div>
|
||||||
<div class="px-3 py-2 border-bottom">
|
</div>
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex flex-column align-items-end gap-1">
|
||||||
<div class="text-truncate">
|
<div class="small ${isSelected ? 'text-white-50' : 'text-muted'}">${escapeHtml(receivedDate)}</div>
|
||||||
<i class="bi bi-envelope text-primary me-1"></i>
|
${hasAttachments ? '<span class="badge bg-info-subtle text-info-emphasis">📎</span>' : ''}
|
||||||
<strong>${e.subject || '(Ingen emne)'}</strong>
|
${!e.is_read ? '<span class="badge bg-warning text-dark">Ulæst</span>' : ''}
|
||||||
<div class="small text-muted text-truncate">${e.sender_email || '-'}</div>
|
<span class="btn btn-sm btn-link p-0 ${isSelected ? 'text-white' : 'text-danger'}" onclick="event.stopPropagation(); unlinkEmail(${e.id});" title="Fjern link">
|
||||||
</div>
|
|
||||||
<button class="btn btn-sm btn-link text-danger p-0 ms-2" onclick="unlinkEmail(${e.id})">
|
|
||||||
<i class="bi bi-link-45deg" style="text-decoration: line-through;"></i>
|
<i class="bi bi-link-45deg" style="text-decoration: line-through;"></i>
|
||||||
</button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('')}
|
</button>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderEmailPreviewEmpty() {
|
||||||
|
const panel = document.getElementById('email-preview-panel');
|
||||||
|
if (!panel) return;
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div class="p-3 text-center text-muted d-flex align-items-center justify-content-center flex-grow-1">
|
||||||
|
Vælg en e-mail i listen for at se indhold og vedhæftninger
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLinkedEmailDetail(emailId) {
|
||||||
|
selectedLinkedEmailId = Number(emailId);
|
||||||
|
const panel = document.getElementById('email-preview-panel');
|
||||||
|
if (!panel) return;
|
||||||
|
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div class="p-4 text-center text-muted">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
|
Henter e-mail...
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
renderLinkedEmails(linkedEmailsCache.filter((email) => {
|
||||||
|
const textFilter = (document.getElementById('emailFilterInput')?.value || '').trim().toLowerCase();
|
||||||
|
const attachmentFilter = document.getElementById('emailAttachmentFilter')?.value || 'all';
|
||||||
|
const readFilter = document.getElementById('emailReadFilter')?.value || 'all';
|
||||||
|
|
||||||
|
if (textFilter) {
|
||||||
|
const haystack = [email.subject, email.sender_email, email.sender_name, email.body_text, email.body_html].join(' ').toLowerCase();
|
||||||
|
if (!haystack.includes(textFilter)) return false;
|
||||||
|
}
|
||||||
|
const hasAttachments = Boolean(email.has_attachments) || Number(email.attachment_count || 0) > 0;
|
||||||
|
if (attachmentFilter === 'with' && !hasAttachments) return false;
|
||||||
|
if (attachmentFilter === 'without' && hasAttachments) return false;
|
||||||
|
const isRead = Boolean(email.is_read);
|
||||||
|
if (readFilter === 'read' && !isRead) return false;
|
||||||
|
if (readFilter === 'unread' && isRead) return false;
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/v1/emails/${emailId}`);
|
||||||
|
if (!res.ok) {
|
||||||
|
panel.innerHTML = '<div class="p-3 text-danger">Kunne ikke hente e-mail detaljer.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const email = await res.json();
|
||||||
|
const subject = email.subject || '(Ingen emne)';
|
||||||
|
const sender = email.sender_name || email.sender_email || '-';
|
||||||
|
const received = email.received_date ? new Date(email.received_date).toLocaleString('da-DK') : '-';
|
||||||
|
const attachments = Array.isArray(email.attachments) ? email.attachments : [];
|
||||||
|
const bodyText = email.body_text || '';
|
||||||
|
const bodyHtml = email.body_html || '';
|
||||||
|
|
||||||
|
panel.innerHTML = `
|
||||||
|
<div class="border-bottom p-3">
|
||||||
|
<div class="fw-bold mb-1">${escapeHtml(subject)}</div>
|
||||||
|
<div class="small text-muted">Fra: ${escapeHtml(sender)}</div>
|
||||||
|
<div class="small text-muted">Dato: ${escapeHtml(received)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 border-bottom">
|
||||||
|
<div class="small fw-semibold mb-2">Vedhæftninger (${attachments.length})</div>
|
||||||
|
<div id="email-attachments-list" class="d-flex flex-wrap gap-2"></div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 overflow-auto" style="max-height: 45vh; white-space: normal;">
|
||||||
|
${bodyText ? `<pre class="mb-0" style="white-space: pre-wrap; font-family: inherit;">${escapeHtml(bodyText)}</pre>` : (bodyHtml ? bodyHtml : '<div class="text-muted">Ingen indhold</div>')}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const attachmentContainer = document.getElementById('email-attachments-list');
|
||||||
|
if (attachmentContainer) {
|
||||||
|
if (!attachments.length) {
|
||||||
|
attachmentContainer.innerHTML = '<span class="text-muted small">Ingen vedhæftninger</span>';
|
||||||
|
} else {
|
||||||
|
attachmentContainer.innerHTML = attachments.map(att => {
|
||||||
|
const attachmentName = att.filename || `Vedhæftning ${att.id}`;
|
||||||
|
const url = `/api/v1/emails/${email.id}/attachments/${att.id}`;
|
||||||
|
return `<a class="btn btn-sm btn-outline-secondary" href="${url}"><i class="bi bi-download me-1"></i>${escapeHtml(attachmentName)}</a>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheIdx = linkedEmailsCache.findIndex((item) => Number(item.id) === Number(email.id));
|
||||||
|
if (cacheIdx >= 0) {
|
||||||
|
linkedEmailsCache[cacheIdx].is_read = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
panel.innerHTML = '<div class="p-3 text-danger">Fejl ved hentning af e-mail detaljer.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function unlinkEmail(emailId) {
|
async function unlinkEmail(emailId) {
|
||||||
if(!confirm("Fjern link til denne email?")) return;
|
if(!confirm("Fjern link til denne email?")) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/sag/${caseIds}/email-links/${emailId}`, { method: 'DELETE' });
|
const res = await fetch(`/api/v1/sag/${caseIds}/email-links/${emailId}`, { method: 'DELETE' });
|
||||||
if(res.ok) loadLinkedEmails();
|
if(res.ok) {
|
||||||
|
if (Number(selectedLinkedEmailId) === Number(emailId)) {
|
||||||
|
selectedLinkedEmailId = null;
|
||||||
|
renderEmailPreviewEmpty();
|
||||||
|
}
|
||||||
|
loadLinkedEmails();
|
||||||
|
}
|
||||||
} catch(e) { alert(e); }
|
} catch(e) { alert(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5905,6 +6125,36 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
['emailFilterInput', 'emailAttachmentFilter', 'emailReadFilter'].forEach((id) => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) return;
|
||||||
|
const eventName = id === 'emailFilterInput' ? 'input' : 'change';
|
||||||
|
el.addEventListener(eventName, () => {
|
||||||
|
applyLinkedEmailFilters();
|
||||||
|
const visible = linkedEmailsCache.filter((email) => {
|
||||||
|
const textFilter = (document.getElementById('emailFilterInput')?.value || '').trim().toLowerCase();
|
||||||
|
const attachmentFilter = document.getElementById('emailAttachmentFilter')?.value || 'all';
|
||||||
|
const readFilter = document.getElementById('emailReadFilter')?.value || 'all';
|
||||||
|
|
||||||
|
if (textFilter) {
|
||||||
|
const haystack = [email.subject, email.sender_email, email.sender_name, email.body_text, email.body_html].join(' ').toLowerCase();
|
||||||
|
if (!haystack.includes(textFilter)) return false;
|
||||||
|
}
|
||||||
|
const hasAttachments = Boolean(email.has_attachments) || Number(email.attachment_count || 0) > 0;
|
||||||
|
if (attachmentFilter === 'with' && !hasAttachments) return false;
|
||||||
|
if (attachmentFilter === 'without' && hasAttachments) return false;
|
||||||
|
const isRead = Boolean(email.is_read);
|
||||||
|
if (readFilter === 'read' && !isRead) return false;
|
||||||
|
if (readFilter === 'unread' && isRead) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!visible.some((email) => Number(email.id) === Number(selectedLinkedEmailId))) {
|
||||||
|
renderEmailPreviewEmpty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function searchEmails(query) {
|
async function searchEmails(query) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/emails?q=${encodeURIComponent(query)}&limit=5`);
|
const res = await fetch(`/api/v1/emails?q=${encodeURIComponent(query)}&limit=5`);
|
||||||
@ -5957,6 +6207,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadEmailFile(file) {
|
async function uploadEmailFile(file) {
|
||||||
|
if (!file) return;
|
||||||
|
const lowerName = String(file.name || '').toLowerCase();
|
||||||
|
if (!(lowerName.endsWith('.eml') || lowerName.endsWith('.msg'))) {
|
||||||
|
alert('Kun .eml og .msg filer understøttes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user