From 0b3fbb1efcc29fd3337356e8c1802aff99df1c97 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Tue, 17 Mar 2026 10:29:01 +0100 Subject: [PATCH] feat: PDF-Export mit Kachel-Auswahl und hellem Drucklayout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neuer PDF-Export-Dialog mit Checkboxen: Lagebild, Quellen, Faktencheck, Karte, Timeline - Helles, schlichtes Drucklayout (weiss, Serifenlos, A4-optimiert) - Oeffnet neues Fenster mit sauberem HTML fuer Drucken/PDF-Speichern - Ersetzt alte window.print() Funktion die das dunkle Theme exportierte - Quellenübersicht als Tabelle + Artikelliste mit Links - Faktencheck mit farbcodierten Status-Badges --- src/static/css/style.css | 27 +++++ src/static/dashboard.html | 27 ++++- src/static/js/app.js | 228 +++++++++++++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 5 deletions(-) diff --git a/src/static/css/style.css b/src/static/css/style.css index 8952539..92f7c7c 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -4231,6 +4231,33 @@ select:focus-visible, textarea:focus-visible, } /* === Print Styles === */ + +/* === PDF Export Dialog === */ +.pdf-tile-option { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + color: var(--text-primary); + transition: background 0.15s, border-color 0.15s; +} +.pdf-tile-option:hover { + background: var(--bg-secondary); +} +.pdf-tile-option input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + cursor: pointer; +} +.pdf-tile-option input[type="checkbox"]:checked + span { + font-weight: 500; +} + @media print { .sidebar, .header, diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 96d1707..be8de67 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -240,7 +240,7 @@ - + @@ -781,5 +781,30 @@
+ + + + diff --git a/src/static/js/app.js b/src/static/js/app.js index cffa9bc..cc61164 100644 --- a/src/static/js/app.js +++ b/src/static/js/app.js @@ -2132,6 +2132,229 @@ const App = { } }, + openPdfExportDialog() { + this._closeExportDropdown(); + if (!this.currentIncidentId) return; + openModal('modal-pdf-export'); + }, + + executePdfExport() { + closeModal('modal-pdf-export'); + const checked = [...document.querySelectorAll('#pdf-export-tiles input:checked')].map(c => c.value); + if (!checked.length) { UI.showToast('Keine Kacheln ausgewählt', 'warning'); return; } + this._generatePdf(checked); + }, + + _generatePdf(tiles) { + const title = document.getElementById('incident-title')?.textContent || 'Export'; + const now = new Date().toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); + + let sections = ''; + const esc = (s) => s ? s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"') : ''; + + // === Lagebild === + if (tiles.includes('lagebild')) { + const summaryEl = document.getElementById('summary-text'); + const timestamp = document.getElementById('lagebild-timestamp')?.textContent || ''; + if (summaryEl && summaryEl.innerHTML.trim()) { + sections += '
' + + '

Lagebild

' + + (timestamp ? '

' + esc(timestamp) + '

' : '') + + '
' + summaryEl.innerHTML + '
' + + '
'; + } + } + + // === Quellen === + if (tiles.includes('quellen')) { + const articles = this._currentArticles || []; + if (articles.length) { + const sourceMap = {}; + articles.forEach(a => { + const name = a.source || 'Unbekannt'; + if (!sourceMap[name]) sourceMap[name] = []; + sourceMap[name].push(a); + }); + const sources = Object.entries(sourceMap).sort((a,b) => b[1].length - a[1].length); + let s = '

' + articles.length + ' Artikel aus ' + sources.length + ' Quellen

'; + s += ''; + sources.forEach(([name, arts]) => { + const langs = [...new Set(arts.map(a => (a.language || 'de').toUpperCase()))].join(', '); + s += ''; + }); + s += '
QuelleArtikelSprache
' + esc(name) + '' + arts.length + '' + langs + '
'; + s += '
'; + sources.forEach(([name, arts]) => { + s += '

' + esc(name) + ' (' + arts.length + ')

'; + arts.forEach(a => { + const hl = esc(a.headline_de || a.headline || 'Ohne Titel'); + const url = a.source_url || ''; + const dateStr = a.published_at ? new Date(a.published_at).toLocaleDateString('de-DE') : ''; + s += '
'; + s += url ? '' + hl + '' : '' + hl + ''; + if (dateStr) s += ' (' + dateStr + ')'; + s += '
'; + }); + }); + s += '
'; + sections += '

Quellenübersicht

' + s + '
'; + } + } + + // === Faktencheck === + if (tiles.includes('faktencheck')) { + const fcItems = document.querySelectorAll('#factcheck-list .factcheck-item'); + if (fcItems.length) { + let s = '
'; + fcItems.forEach(item => { + const status = item.dataset.fcStatus || ''; + const statusEl = item.querySelector('.fc-status-text, .factcheck-status'); + const claimEl = item.querySelector('.fc-claim-text, .factcheck-claim'); + const evidenceEls = item.querySelectorAll('.fc-evidence-chip, .evidence-chip'); + const statusText = statusEl ? statusEl.textContent.trim() : status; + const claim = claimEl ? claimEl.textContent.trim() : ''; + const statusClass = (status.includes('confirmed') || status.includes('verified')) ? 'confirmed' + : (status.includes('refuted') || status.includes('disputed')) ? 'refuted' + : 'unverified'; + s += '
' + + '' + esc(statusText) + '' + + '
' + esc(claim) + '
'; + if (evidenceEls.length) { + s += '
'; + evidenceEls.forEach(ev => { + const link = ev.closest('a'); + const href = link ? link.href : ''; + const text = ev.textContent.trim(); + s += href + ? '' + esc(text) + ' ' + : '' + esc(text) + ' '; + }); + s += '
'; + } + s += '
'; + }); + s += '
'; + sections += '

Faktencheck

' + s + '
'; + } + } + + // === Karte === + if (tiles.includes('karte')) { + const mapContainer = document.getElementById('map-container'); + const mapEmpty = document.getElementById('map-empty'); + if (mapContainer && (!mapEmpty || mapEmpty.style.display === 'none')) { + const stats = document.getElementById('map-stats')?.textContent || ''; + // Try to capture map as canvas image + let mapImg = ''; + try { + const leafletPane = mapContainer.querySelector('.leaflet-container'); + if (leafletPane && typeof leafletPane.toDataURL === 'function') { + mapImg = ''; + } + } catch(e) {} + if (!mapImg) { + // Leaflet container is not a canvas, so we use a placeholder + mapImg = '
Die interaktive Karte kann nicht direkt in PDF exportiert werden. Nutzen Sie die Screenshot-Funktion Ihres Browsers (z.B. Strg+Shift+S in Firefox).
'; + } + sections += '

Geografische Verteilung

' + + (stats ? '

' + esc(stats) + '

' : '') + + mapImg + '
'; + } + } + + // === Timeline === + if (tiles.includes('timeline')) { + const buckets = document.querySelectorAll('#timeline .ht-bucket'); + if (buckets.length) { + let s = '
'; + buckets.forEach(bucket => { + const label = bucket.querySelector('.ht-bucket-label'); + const items = bucket.querySelectorAll('.ht-item'); + if (label) s += '

' + esc(label.textContent.trim()) + '

'; + items.forEach(item => { + const time = item.querySelector('.ht-item-time'); + const ttl = item.querySelector('.ht-item-title'); + const src = item.querySelector('.ht-item-source'); + s += '
'; + if (time) s += '' + esc(time.textContent.trim()) + ' '; + if (ttl) s += '' + esc(ttl.textContent.trim()) + ''; + if (src) s += ' ' + esc(src.textContent.trim()) + ''; + s += '
'; + }); + }); + s += '
'; + sections += '

Ereignis-Timeline

' + s + '
'; + } + } + + if (!sections) { UI.showToast('Keine Inhalte zum Exportieren', 'warning'); return; } + + const printHtml = '' ++ '\n' ++ '\n' ++ '\n' ++ '\n' + esc(title) + ' \u2014 AegisSight Export' ++ '\n' ++ '\n' ++ '\n' ++ '\n
' ++ '\n

' + esc(title) + '

' ++ '\n
AegisSight Monitor \u2014 Exportiert am ' + esc(now) + '
' ++ '\n
' ++ sections ++ '\n' ++ '\n'; + + const printWin = window.open('', '_blank', 'width=800,height=600'); + if (!printWin) { UI.showToast('Popup blockiert \u2014 bitte Popup-Blocker deaktivieren', 'error'); return; } + printWin.document.write(printHtml); + printWin.document.close(); + printWin.onload = function() { printWin.focus(); printWin.print(); }; + setTimeout(function() { try { printWin.focus(); printWin.print(); } catch(e) {} }, 500); + }, + + async exportIncident(format, scope) { this._closeExportDropdown(); if (!this.currentIncidentId) return; @@ -2160,10 +2383,7 @@ const App = { } }, - printIncident() { - this._closeExportDropdown(); - window.print(); - }, + // === Sidebar-Stats ===