feat(ui): Filter als Checkbox-Dropdowns (Knopf klappt Mehrfachauswahl auf)

Auf Nutzerwunsch statt offen liegender Checkbox-Reihen: jeder Filter ist
jetzt ein Dropdown-Knopf, der beim Klick eine Checkbox-Liste aufklappt.
Der Knopf zeigt die Anzahl der gesetzten Haken als goldene Plakette,
Klick daneben schliesst das Panel, nichts angehakt = alle. Gilt ueberall
(Quellenliste, Aufgaben, Audit-Log), da zentral in renderFilterGroup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-25 18:27:42 +00:00
Ursprung 2df2fa1cfa
Commit bc3b0bb9ee
3 geänderte Dateien mit 99 neuen und 33 gelöschten Zeilen

Datei anzeigen

@@ -1375,18 +1375,30 @@ function populateSelect(el, items, allLabel) {
if (current && items.some(it => it.key === current)) el.value = current;
}
// Checkbox-Filtergruppe (Standard für alle Filter im Portal, statt Dropdowns).
// Checkbox-Dropdown (Standard für alle Filter im Portal). Ein Knopf mit dem
// Filternamen klappt eine Liste von Checkboxen auf (Mehrfachauswahl).
// selected ist ein Set mit den angehakten Keys. Nichts angehakt = keine
// Einschränkung. onChange wird nach jedem Toggle aufgerufen.
// Einschränkung. onChange wird nach jedem Toggle aufgerufen, das Panel
// bleibt dabei offen.
function renderFilterGroup(container, title, items, selected, onChange) {
if (!container) return;
const wrap = document.createElement("div");
wrap.className = "filter-group";
const lbl = document.createElement("span");
lbl.className = "filter-group-label";
lbl.textContent = title;
lbl.title = "Nichts angehakt = alle";
wrap.appendChild(lbl);
wrap.className = "filter-dd";
const btn = document.createElement("button");
btn.type = "button";
btn.className = "filter-dd-btn";
btn.title = "Nichts angehakt = alle";
const updateBtn = () => {
btn.innerHTML = esc(title)
+ (selected.size ? ` <span class="filter-dd-count">${selected.size}</span>` : "")
+ ' <span class="filter-dd-caret">&#9662;</span>';
btn.classList.toggle("has-selection", selected.size > 0);
};
updateBtn();
const panel = document.createElement("div");
panel.className = "filter-dd-panel";
(items || []).forEach((it) => {
const label = document.createElement("label");
label.className = "filter-check";
@@ -1396,15 +1408,41 @@ function renderFilterGroup(container, title, items, selected, onChange) {
cb.addEventListener("change", () => {
if (cb.checked) selected.add(it.key);
else selected.delete(it.key);
updateBtn();
onChange();
});
label.appendChild(cb);
label.appendChild(document.createTextNode(it.label));
wrap.appendChild(label);
panel.appendChild(label);
});
if (!(items || []).length) {
const empty = document.createElement("div");
empty.className = "text-muted";
empty.style.fontSize = "12px";
empty.textContent = "Keine Werte vorhanden";
panel.appendChild(empty);
}
btn.addEventListener("click", (e) => {
e.stopPropagation();
const wasOpen = wrap.classList.contains("open");
closeAllFilterDropdowns();
if (!wasOpen) wrap.classList.add("open");
});
panel.addEventListener("click", (e) => e.stopPropagation());
wrap.appendChild(btn);
wrap.appendChild(panel);
container.appendChild(wrap);
}
function closeAllFilterDropdowns() {
document.querySelectorAll(".filter-dd.open").forEach((el) => el.classList.remove("open"));
}
// Klick irgendwo anders schließt offene Filter-Dropdowns
document.addEventListener("click", () => closeAllFilterDropdowns());
document.addEventListener("DOMContentLoaded", () => {
// Beim Page-Load Meta einmalig laden (asynchron, blockiert nicht)
if (window.API && (localStorage.getItem("token") || window.location.pathname === "/dashboard")) {