Phase 3c: Kundenquellen-Tab mit Filter + Sort + Bulk-Promote

Backend
- routers/sources.py: POST /api/sources/tenant/bulk-promote NEU
  Nimmt Liste von source_ids, promotet jede einzeln zur Grundquelle.
  Returns {promoted, skipped[{id,name,reason}], failed[{id,error}]}.
  Ueberspringt Quellen die schon Grundquellen sind oder deren URL bereits
  als Grundquelle existiert.

Frontend
- dashboard.html sub-tenant-sources: action-bar erweitert um
  3 Filter-Selects (Typ, Kategorie, Org), Bulk-Promote-Button.
  Tabelle bekommt Checkbox-Spalte + sortable Spalten (Sort-Icons).
- sources.js: tenant-Tab komplett refactored
  - State: tenantFilters, tenantSort, tenantSelected (Set)
  - applyTenantFilterAndSort: zentraler Render-Pfad mit allen Filtern + Sort
  - populateTenantFilters: Org-Liste aus Daten, Typ/Kategorie aus META
  - toggleTenantSelect / toggleTenantSelectAll: Selection-Logik
  - bulkPromoteSelected: showConfirm -> POST -> Toast mit Ergebnis
  - renderTenantSources: Checkbox-Spalte, dynamische typeLabel/categoryLabel
  - Counter zeigt jetzt N gefiltert / Gesamt
Dieser Commit ist enthalten in:
claude-dev
2026-05-09 03:07:55 +00:00
Ursprung eda60f9299
Commit 9350e4538a
3 geänderte Dateien mit 220 neuen und 35 gelöschten Zeilen

Datei anzeigen

@@ -340,21 +340,34 @@
<!-- Kundenquellen -->
<div class="section" id="sub-tenant-sources">
<div class="action-bar">
<div style="display:flex;align-items:center;gap:12px;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<input type="text" class="search-input" id="tenantSourceSearch" placeholder="Kundenquelle suchen...">
<select class="filter-select" id="tenantFilterType" onchange="filterTenantSources()">
<option value="">Alle Typen</option>
</select>
<select class="filter-select" id="tenantFilterCategory" onchange="filterTenantSources()">
<option value="">Alle Kategorien</option>
</select>
<select class="filter-select" id="tenantFilterOrg" onchange="filterTenantSources()">
<option value="">Alle Organisationen</option>
</select>
<span class="text-secondary" id="tenantSourceCount"></span>
</div>
<button class="btn btn-primary" id="tenantBulkPromoteBtn" disabled onclick="bulkPromoteSelected()">
Ausgewählte übernehmen (0)
</button>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Domain</th>
<th>Typ</th>
<th>Kategorie</th>
<th>Organisation</th>
<th style="width:32px;"><input type="checkbox" id="tenantSelectAll" onchange="toggleTenantSelectAll(this.checked)"></th>
<th class="sortable" data-sort="name" onclick="sortTenantSources('name')">Name <span class="sort-icon"></span></th>
<th class="sortable" data-sort="domain" onclick="sortTenantSources('domain')">Domain <span class="sort-icon"></span></th>
<th class="sortable" data-sort="source_type" onclick="sortTenantSources('source_type')">Typ <span class="sort-icon"></span></th>
<th class="sortable" data-sort="category" onclick="sortTenantSources('category')">Kategorie <span class="sort-icon"></span></th>
<th class="sortable" data-sort="org_name" onclick="sortTenantSources('org_name')">Organisation <span class="sort-icon"></span></th>
<th>Hinzugefügt von</th>
<th>Aktionen</th>
</tr>
@@ -365,18 +378,6 @@
</div>
</div>
<!-- Quellen-Health -->
<div class="section" id="sub-source-health">
<div class="action-bar">
<h2 style="font-size:16px;font-weight:600;">Quellen-Health & Vorschläge</h2>
<button class="btn btn-primary" id="runHealthCheckBtn" onclick="runHealthCheck()">Jetzt prüfen</button>
</div>
<div id="healthContent">
<div class="text-muted" style="padding:20px;">Tab auswählen um Health-Daten zu laden...</div>
</div>
</div>
</div>
<!-- Audit-Log Section -->
<div class="section" id="sec-audit">
<div class="action-bar" style="flex-wrap:wrap;gap:8px;">