Lagebild: Faktenchecks als Filter + Akkordeon mit Monitor-Icons

- Stat-Cards klickbar als Filter (Gesamt/Bestätigt/Offen/Widerlegt), zentriert
- Kompakte Akkordeon-Zeilen statt großer Karten
- Icons vom echten Monitor (✓ ✗ ? ↻ ⚠) als farbige Quadrate
- Klick auf Zeile klappt Detail auf (Evidenz + Statusverlauf)
- Nur eine Zeile gleichzeitig offen
- Gold-Punkt bei Einträgen mit Statusverlauf

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Code
2026-03-07 17:24:49 +01:00
Ursprung 2bbe0b0bb7
Commit 8189cf9add
2 geänderte Dateien mit 277 neuen und 118 gelöschten Zeilen

Datei anzeigen

@@ -695,27 +695,57 @@ var Lagebild = {
},
/* ===== TAB: FAKTENCHECKS ===== */
/* ===== Factcheck Icons (from real Monitor) ===== */
fcIcons: {
confirmed: '&#10003;',
unconfirmed: '?',
contradicted: '&#10007;',
developing: '&#8635;',
established: '&#10003;',
disputed: '&#9888;',
'false': '&#10007;',
unverified: '?'
},
fcLabels: {
confirmed: 'Bestätigt',
unconfirmed: 'Unbestätigt',
contradicted: 'Widerlegt',
developing: 'Unklar',
established: 'Gesichert',
disputed: 'Umstritten',
'false': 'Falsch',
unverified: 'Nicht verifiziert'
},
renderFactChecksTab: function() {
var checks = this.currentView.fact_checks || [];
if (!checks.length) {
document.getElementById('factchecks-content').innerHTML = '<p style="color:#8896AB">Keine Faktenchecks verf\u00fcgbar.</p>';
document.getElementById('factchecks-content').innerHTML = '<p style="color:#8896AB">Keine Faktenchecks verfügbar.</p>';
return;
}
// Count stats
var stats = { confirmed: 0, unconfirmed: 0, contradicted: 0, developing: 0, established: 0, disputed: 0 };
for (var k = 0; k < checks.length; k++) {
var st = checks[k].status || 'developing';
if (stats[st] !== undefined) stats[st]++;
}
var confirmedTotal = stats.confirmed + stats.established;
var openTotal = stats.unconfirmed + stats.developing;
var contradictedTotal = stats.contradicted + stats.disputed;
// Stat cards (clickable filters)
var h = '<div class="fc-stats">';
h += '<div class="fc-stat"><span class="fc-stat-num">' + checks.length + '</span><span class="fc-stat-label">Gesamt</span></div>';
h += '<div class="fc-stat confirmed"><span class="fc-stat-num">' + (stats.confirmed + stats.established) + '</span><span class="fc-stat-label">Best\u00e4tigt</span></div>';
h += '<div class="fc-stat unconfirmed"><span class="fc-stat-num">' + (stats.unconfirmed + stats.developing) + '</span><span class="fc-stat-label">Offen</span></div>';
if (stats.contradicted + stats.disputed > 0)
h += '<div class="fc-stat contradicted"><span class="fc-stat-num">' + (stats.contradicted + stats.disputed) + '</span><span class="fc-stat-label">Widerlegt</span></div>';
h += '<button class="fc-stat active" data-filter="all"><span class="fc-stat-num">' + checks.length + '</span><span class="fc-stat-label">Gesamt</span></button>';
h += '<button class="fc-stat confirmed" data-filter="confirmed"><span class="fc-stat-num">' + confirmedTotal + '</span><span class="fc-stat-label">Bestätigt</span></button>';
h += '<button class="fc-stat unconfirmed" data-filter="unconfirmed"><span class="fc-stat-num">' + openTotal + '</span><span class="fc-stat-label">Offen</span></button>';
if (contradictedTotal > 0)
h += '<button class="fc-stat contradicted" data-filter="contradicted"><span class="fc-stat-num">' + contradictedTotal + '</span><span class="fc-stat-label">Widerlegt</span></button>';
h += '</div>';
// Sort: status_history first, then by sources_count
checks = checks.slice().sort(function(a, b) {
var aH = (a.status_history || []).length;
var bH = (b.status_history || []).length;
@@ -723,40 +753,100 @@ var Lagebild = {
return (b.sources_count || 0) - (a.sources_count || 0);
});
// Compact accordion list
h += '<div class="fc-list" id="fc-list">';
for (var i = 0; i < checks.length; i++) {
var fc = checks[i];
var status = fc.status || 'developing';
var hasProg = fc.status_history && fc.status_history.length > 1;
var filterGroup = 'all';
if (status === 'confirmed' || status === 'established') filterGroup = 'confirmed';
else if (status === 'unconfirmed' || status === 'developing') filterGroup = 'unconfirmed';
else if (status === 'contradicted' || status === 'disputed') filterGroup = 'contradicted';
h += '<div class="factcheck-card' + (hasProg ? ' highlight' : '') + '" data-status="' + status + '">';
h += '<div class="factcheck-header">';
h += '<span class="status-badge ' + status + '">' + this.stLabel(status) + '</span>';
h += '<span class="factcheck-sources">' + (fc.sources_count || 0) + ' unabh\u00e4ngige Quellen</span>';
var hasProg = fc.status_history && fc.status_history.length > 1;
var icon = this.fcIcons[status] || '?';
var label = this.fcLabels[status] || status;
h += '<div class="fc-row" data-status-group="' + filterGroup + '">';
h += '<div class="fc-row-header" data-fc-index="' + i + '">';
h += '<span class="fc-icon ' + status + '" title="' + this.esc(label) + '">' + icon + '</span>';
h += '<span class="fc-row-claim">' + this.esc(this.fixUmlauts(fc.claim || '')) + '</span>';
h += '<span class="fc-row-sources">' + (fc.sources_count || 0) + '</span>';
if (hasProg) h += '<span class="fc-row-history-dot" title="Statusverlauf vorhanden"></span>';
h += '<span class="fc-row-chevron">&#9656;</span>';
h += '</div>';
h += '<p class="factcheck-claim">' + this.esc(this.fixUmlauts(fc.claim || '')) + '</p>';
// Expandable detail
h += '<div class="fc-row-detail">';
h += '<div class="fc-row-detail-inner">';
h += '<div class="fc-detail-status"><span class="fc-icon ' + status + '">' + icon + '</span> <strong>' + label + '</strong> – ' + (fc.sources_count || 0) + ' unabhängige Quellen</div>';
if (fc.evidence) {
var ev = this.fixUmlauts(fc.evidence);
ev = this.esc(ev).replace(/(https?:\/\/[^\s,)]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>');
h += '<div class="factcheck-evidence"><strong>Evidenz:</strong> ' + ev + '</div>';
h += '<div class="fc-detail-evidence"><strong>Evidenz:</strong> ' + ev + '</div>';
}
if (hasProg) {
h += '<div class="status-progression">';
h += '<span class="progression-label">Verlauf:</span>';
h += '<div class="fc-detail-progression">';
h += '<span class="fc-detail-prog-label">Verlauf:</span>';
for (var j = 0; j < fc.status_history.length; j++) {
var step = fc.status_history[j];
if (j > 0) h += '<span class="progression-arrow">&rarr;</span>';
h += '<span class="progression-step">';
h += '<span class="status-badge ' + step.status + '">' + this.stLabel(step.status) + '</span>';
h += '<span class="fc-icon small ' + step.status + '">' + (this.fcIcons[step.status] || '?') + '</span>';
if (step.at) h += '<span class="progression-time">' + this.fmtShort(step.at) + '</span>';
h += '</span>';
}
h += '</div>';
}
h += '</div></div>';
h += '</div>';
}
h += '</div>';
document.getElementById('factchecks-content').innerHTML = h;
// Filter click handler
var statBtns = document.querySelectorAll('.fc-stat');
statBtns.forEach(function(btn) {
btn.addEventListener('click', function() {
for (var k = 0; k < statBtns.length; k++) statBtns[k].classList.remove('active');
btn.classList.add('active');
var filter = btn.getAttribute('data-filter');
var rows = document.querySelectorAll('.fc-row');
for (var k = 0; k < rows.length; k++) {
if (filter === 'all' || rows[k].getAttribute('data-status-group') === filter) {
rows[k].style.display = '';
} else {
rows[k].style.display = 'none';
}
}
});
});
// Accordion click handler
document.getElementById('fc-list').addEventListener('click', function(e) {
var header = e.target.closest('.fc-row-header');
if (!header) return;
var row = header.closest('.fc-row');
var wasOpen = row.classList.contains('open');
// Close all open rows
var allRows = document.querySelectorAll('.fc-row.open');
for (var k = 0; k < allRows.length; k++) allRows[k].classList.remove('open');
// Toggle clicked row
if (!wasOpen) {
row.classList.add('open');
var detail = row.querySelector('.fc-row-detail');
if (detail) {
setTimeout(function() {
detail.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 50);
}
}
});
},
/* ===== TABS ===== */