V 2.0.1 - Regeln-Filter nach Ordner + Markierung ohne Zuordnung

Neue Features:
- Filter-Dropdown für Regeln nach Ordner
- Option "Ohne Zuordnung" zeigt nur Regeln ohne Ordner
- Regeln ohne Ordner-Zuweisung werden mit orangem Rand und Badge markiert
- Zugeordnete Ordner werden unter jeder Regel angezeigt
- Filter-Einstellung wird in localStorage gespeichert

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-10 14:03:26 +01:00
parent c5ee82e1c2
commit a9a5482c94
5 changed files with 118 additions and 8 deletions

BIN
Docker - Image/V 2.0/V 2.0.tar Executable file

Binary file not shown.

BIN
Docker - Image/V 2.1/V 2.1.tar Executable file

Binary file not shown.

View file

@ -142,6 +142,7 @@ class RegelResponse(BaseModel):
freie_ordner: Optional[List[str]] = []
ziel_ordner: Optional[str] = None
nur_umbenennen: bool = False
ordner_ids: List[int] = [] # IDs der zugeordneten Ordner
class Config:
from_attributes = True
@ -959,9 +960,40 @@ def verarbeite_ordner(id: int, db: Session = Depends(get_db)):
# ============ Regeln ============
@router.get("/regeln", response_model=List[RegelResponse])
@router.get("/regeln")
def liste_regeln(db: Session = Depends(get_db)):
return db.query(SortierRegel).order_by(SortierRegel.prioritaet).all()
"""Gibt alle Regeln mit ihren zugeordneten Ordner-IDs zurück"""
regeln = db.query(SortierRegel).order_by(SortierRegel.prioritaet).all()
# Alle Ordner-Zuweisungen auf einmal laden
alle_zuweisungen = db.query(OrdnerRegel).all()
regel_ordner_map = {}
for z in alle_zuweisungen:
if z.regel_id not in regel_ordner_map:
regel_ordner_map[z.regel_id] = []
regel_ordner_map[z.regel_id].append(z.ordner_id)
# Regeln mit Ordner-IDs anreichern
result = []
for r in regeln:
regel_dict = {
"id": r.id,
"name": r.name,
"prioritaet": r.prioritaet,
"aktiv": r.aktiv,
"muster": r.muster or {},
"extraktion": r.extraktion or {},
"ist_fallback": r.ist_fallback,
"schema": r.schema,
"unterordner": r.unterordner,
"freie_ordner": r.freie_ordner or [],
"ziel_ordner": r.ziel_ordner,
"nur_umbenennen": r.nur_umbenennen,
"ordner_ids": regel_ordner_map.get(r.id, [])
}
result.append(regel_dict)
return result
@router.post("/regeln", response_model=RegelResponse)

View file

@ -1034,10 +1034,68 @@ function natuerlicheSortierung(a, b) {
return a.localeCompare(b, 'de', { numeric: true, sensitivity: 'base' });
}
// Cache für Ordner (für Filter-Dropdown)
let ordnerCache = [];
async function ladeOrdnerFuerFilter() {
try {
const ordner = await api('/ordner');
ordnerCache = ordner;
const filterSelect = document.getElementById('regeln-ordner-filter');
if (!filterSelect) return;
// Gespeicherten Filter wiederherstellen
const gespeichert = localStorage.getItem('regeln-ordner-filter');
// Bestehende Optionen nach "Ohne Zuordnung" entfernen
while (filterSelect.options.length > 2) {
filterSelect.remove(2);
}
// Ordner hinzufügen
ordner.forEach(o => {
const option = document.createElement('option');
option.value = o.id;
option.textContent = o.name;
filterSelect.appendChild(option);
});
// Gespeicherten Wert setzen
if (gespeichert) {
filterSelect.value = gespeichert;
}
} catch (error) {
console.error('Fehler beim Laden der Ordner für Filter:', error);
}
}
async function ladeRegeln() {
try {
const regeln = await api('/regeln');
// Ordner-Filter Dropdown beim ersten Mal befüllen
const filterSelect = document.getElementById('regeln-ordner-filter');
if (filterSelect && filterSelect.options.length <= 2) {
await ladeOrdnerFuerFilter();
}
// Filter anwenden
if (filterSelect) {
localStorage.setItem('regeln-ordner-filter', filterSelect.value);
}
const filter = filterSelect?.value || 'alle';
let gefilterteRegeln = regeln;
if (filter === 'ohne') {
// Nur Regeln ohne Ordner-Zuweisung
gefilterteRegeln = regeln.filter(r => !r.ordner_ids || r.ordner_ids.length === 0);
} else if (filter !== 'alle') {
// Nach spezifischem Ordner filtern
const ordnerId = parseInt(filter);
gefilterteRegeln = regeln.filter(r => r.ordner_ids && r.ordner_ids.includes(ordnerId));
}
// Sortierung anwenden (und in localStorage speichern)
const sortSelect = document.getElementById('regeln-sortierung');
if (sortSelect) {
@ -1051,7 +1109,7 @@ async function ladeRegeln() {
}
const sortierung = sortSelect?.value || 'name_asc';
regeln.sort((a, b) => {
gefilterteRegeln.sort((a, b) => {
switch (sortierung) {
case 'name_asc':
return natuerlicheSortierung(a.name || '', b.name || '');
@ -1066,7 +1124,7 @@ async function ladeRegeln() {
}
});
renderRegeln(regeln);
renderRegeln(gefilterteRegeln);
} catch (error) {
console.error('Fehler:', error);
}
@ -1083,11 +1141,26 @@ function renderRegeln(regeln) {
container.innerHTML = regeln.map(r => {
const aktivClass = r.aktiv ? '' : 'opacity: 0.5;';
const aktivBadge = r.aktiv ? '<span class="badge badge-success">Aktiv</span>' : '<span class="badge badge-danger">Inaktiv</span>';
// Warnung wenn keine Ordner zugeordnet
const ohneOrdner = !r.ordner_ids || r.ordner_ids.length === 0;
const ohneOrdnerBadge = ohneOrdner ? '<span class="badge badge-warning" title="Keinem Ordner zugeordnet">⚠ Ohne Ordner</span>' : '';
const ohneOrdnerStyle = ohneOrdner ? 'border-left: 3px solid var(--warning);' : '';
// Ordner-Namen anzeigen
let ordnerInfo = '';
if (r.ordner_ids && r.ordner_ids.length > 0 && ordnerCache.length > 0) {
const ordnerNamen = r.ordner_ids
.map(id => ordnerCache.find(o => o.id === id)?.name || `ID ${id}`)
.join(', ');
ordnerInfo = `<br><small style="color: var(--text-secondary);">📁 ${escapeHtml(ordnerNamen)}</small>`;
}
return `
<div class="config-item" style="${aktivClass}">
<div class="config-item" style="${aktivClass}${ohneOrdnerStyle}">
<div class="config-item-info">
<h4>${escapeHtml(r.name)} ${aktivBadge} <span class="badge badge-info">Prio ${r.prioritaet}</span></h4>
<small>${escapeHtml(r.schema)}</small>
<h4>${escapeHtml(r.name)} ${aktivBadge} ${ohneOrdnerBadge} <span class="badge badge-info">Prio ${r.prioritaet}</span></h4>
<small>${escapeHtml(r.schema)}</small>${ordnerInfo}
</div>
<div class="config-item-actions">
<button class="btn btn-sm" onclick="bearbeiteRegel(${r.id})" title="Bearbeiten"></button>

View file

@ -199,7 +199,12 @@
<div class="card">
<div class="card-header">
<h3>📑 Sortier-Regeln</h3>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<select id="regeln-ordner-filter" onchange="ladeRegeln()" title="Nach Ordner filtern" style="padding: 0.25rem 0.5rem; font-size: 0.85rem; max-width: 150px;">
<option value="alle">Alle Ordner</option>
<option value="ohne">⚠ Ohne Zuordnung</option>
<!-- Ordner werden dynamisch geladen -->
</select>
<select id="regeln-sortierung" onchange="ladeRegeln()" title="Sortierung" style="padding: 0.25rem 0.5rem; font-size: 0.85rem;">
<option value="name_asc">Name A-Z</option>
<option value="name_desc">Name Z-A</option>