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:
@@ -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">▾</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")) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren