From dbd5568296341429b6778871b4a245437ab140ec Mon Sep 17 00:00:00 2001 From: claude-dev Date: Sun, 8 Mar 2026 14:51:31 +0100 Subject: [PATCH] =?UTF-8?q?Quellenverwaltung:=20Filter,=20Sortierung,=20Ar?= =?UTF-8?q?tikelz=C3=A4hler,=20Umlaute?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Filter-Dropdowns für Typ, Kategorie und Status - Sortierbare Spalten (Name, Domain, Typ, Kategorie, Artikel, Status) - Artikel-Spalte zeigt article_count an - Umlaute korrigiert (Löschen, Übernehmen, Läuft ab, Hinzugefügt von) - 53 kaputte/doppelte Quellen bereinigt (Bashinho, Netzpolitik, Facebook) Co-Authored-By: Claude Opus 4.6 --- src/static/css/style.css | 29 ++++++++++++++++ src/static/dashboard.html | 40 +++++++++++++++++----- src/static/js/sources.js | 71 +++++++++++++++++++++++++++++++-------- 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/static/css/style.css b/src/static/css/style.css index a29339b..42c4f4f 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -309,6 +309,35 @@ input:focus, select:focus, textarea:focus { } /* --- Tables --- */ +/* Filter-Selects */ +.filter-select { + padding: 6px 10px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + font-size: 13px; + cursor: pointer; +} + +/* Sortierbare Spalten */ +th.sortable { + cursor: pointer; + user-select: none; + white-space: nowrap; +} +th.sortable:hover { + color: var(--text-primary); +} +.sort-icon { + font-size: 10px; + opacity: 0.7; +} + +.text-right { + text-align: right; +} + .table-wrap { overflow-x: auto; } diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 79d255d..8f5e1f2 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -183,7 +183,7 @@ Organisation Typ Max Nutzer - Laeuft ab + Läuft ab Aktionen @@ -205,8 +205,31 @@
-
+
+ + +
@@ -217,12 +240,13 @@ - + - - - - + + + + + @@ -250,7 +274,7 @@ - + diff --git a/src/static/js/sources.js b/src/static/js/sources.js index 6901b14..04b9ac7 100644 --- a/src/static/js/sources.js +++ b/src/static/js/sources.js @@ -4,6 +4,8 @@ let globalSourcesCache = []; let tenantSourcesCache = []; let editingSourceId = null; +let globalSortField = "name"; +let globalSortAsc = true; const CATEGORY_LABELS = { nachrichtenagentur: "Nachrichtenagentur", @@ -21,7 +23,7 @@ const CATEGORY_LABELS = { const TYPE_LABELS = { rss_feed: "RSS-Feed", web_source: "Webquelle", - excluded: "Gesperrt", + excluded: "Ausgeschlossen", }; // --- Init --- @@ -63,7 +65,7 @@ async function loadGlobalSources() { function renderGlobalSources(sources) { const tbody = document.getElementById("globalSourceTable"); if (sources.length === 0) { - tbody.innerHTML = ''; + tbody.innerHTML = ''; return; } tbody.innerHTML = sources.map((s) => ` @@ -73,31 +75,72 @@ function renderGlobalSources(sources) { + `).join(""); document.getElementById("globalSourceCount").textContent = `${sources.length} Grundquellen`; + + // Sort-Icons aktualisieren + document.querySelectorAll("th.sortable .sort-icon").forEach(el => el.textContent = ""); + const activeHeader = document.querySelector(`th.sortable[data-sort="${globalSortField}"] .sort-icon`); + if (activeHeader) activeHeader.textContent = globalSortAsc ? " ▲" : " ▼"; } -// Suche +// Filter + Sortierung document.addEventListener("DOMContentLoaded", () => { const el = document.getElementById("globalSourceSearch"); if (el) { - el.addEventListener("input", () => { - const q = el.value.toLowerCase(); - const filtered = globalSourcesCache.filter((s) => - s.name.toLowerCase().includes(q) || (s.domain || "").toLowerCase().includes(q) || (s.category || "").toLowerCase().includes(q) - ); - renderGlobalSources(filtered); - }); + el.addEventListener("input", () => filterGlobalSources()); } }); +function filterGlobalSources() { + const q = (document.getElementById("globalSourceSearch")?.value || "").toLowerCase(); + const typeFilter = document.getElementById("globalFilterType")?.value || ""; + const catFilter = document.getElementById("globalFilterCategory")?.value || ""; + const statusFilter = document.getElementById("globalFilterStatus")?.value || ""; + + let filtered = globalSourcesCache.filter((s) => { + if (q && !(s.name.toLowerCase().includes(q) || (s.domain || "").toLowerCase().includes(q) || (s.url || "").toLowerCase().includes(q))) return false; + if (typeFilter && s.source_type !== typeFilter) return false; + if (catFilter && s.category !== catFilter) return false; + if (statusFilter && s.status !== statusFilter) return false; + return true; + }); + + // Sortierung anwenden + filtered.sort((a, b) => { + let va = a[globalSortField] ?? ""; + let vb = b[globalSortField] ?? ""; + if (globalSortField === "article_count") { + va = va || 0; vb = vb || 0; + return globalSortAsc ? va - vb : vb - va; + } + va = String(va).toLowerCase(); + vb = String(vb).toLowerCase(); + const cmp = va.localeCompare(vb, "de"); + return globalSortAsc ? cmp : -cmp; + }); + + renderGlobalSources(filtered); +} + +function sortGlobalSources(field) { + if (globalSortField === field) { + globalSortAsc = !globalSortAsc; + } else { + globalSortField = field; + globalSortAsc = true; + } + filterGlobalSources(); +} + // --- Grundquelle erstellen/bearbeiten --- function openNewGlobalSource() { editingSourceId = null; @@ -172,7 +215,7 @@ function setupSourceForms() { function confirmDeleteGlobalSource(id, name) { showConfirm( - "Grundquelle loeschen", + "Grundquelle löschen", `Soll die Grundquelle "${name}" endgültig gelöscht werden? Sie wird für alle Monitore entfernt.`, async () => { try { @@ -210,7 +253,7 @@ function renderTenantSources(sources) { `).join(""); @@ -320,7 +363,7 @@ async function addDiscoveredFeeds() { }); if (selected.length === 0) { - alert("Keine Feeds ausgewaehlt"); + alert("Keine Feeds ausgewählt"); return; }
NameName URLDomainTypKategorieStatusDomain Typ Kategorie Artikel Status Aktionen
Typ Kategorie OrganisationHinzugefuegt vonHinzugefügt von Aktionen
Keine Grundquellen
Keine Grundquellen
${esc(s.domain || "-")} ${TYPE_LABELS[s.source_type] || s.source_type} ${CATEGORY_LABELS[s.category] || s.category}${s.article_count || 0} ${s.status === "active" ? "Aktiv" : "Inaktiv"} - +
${esc(s.org_name || "-")} ${esc(s.added_by || "-")} - +