diff --git a/Docker - Image/V 2.0/V 2.0.tar b/Docker - Image/V 2.0/V 2.0.tar new file mode 100755 index 0000000..092072c Binary files /dev/null and b/Docker - Image/V 2.0/V 2.0.tar differ diff --git a/Docker - Image/V 2.1/V 2.1.tar b/Docker - Image/V 2.1/V 2.1.tar new file mode 100755 index 0000000..e86b26a Binary files /dev/null and b/Docker - Image/V 2.1/V 2.1.tar differ diff --git a/Source/backend/app/routes/api.py b/Source/backend/app/routes/api.py index 966fd37..abeccbb 100755 --- a/Source/backend/app/routes/api.py +++ b/Source/backend/app/routes/api.py @@ -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) diff --git a/Source/frontend/static/js/app.js b/Source/frontend/static/js/app.js index fa90d64..b4221b9 100755 --- a/Source/frontend/static/js/app.js +++ b/Source/frontend/static/js/app.js @@ -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 ? 'Aktiv' : 'Inaktiv'; + + // Warnung wenn keine Ordner zugeordnet + const ohneOrdner = !r.ordner_ids || r.ordner_ids.length === 0; + const ohneOrdnerBadge = ohneOrdner ? '⚠ Ohne Ordner' : ''; + 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 = `
📁 ${escapeHtml(ordnerNamen)}`; + } + return ` -
+
-

${escapeHtml(r.name)} ${aktivBadge} Prio ${r.prioritaet}

- ${escapeHtml(r.schema)} +

${escapeHtml(r.name)} ${aktivBadge} ${ohneOrdnerBadge} Prio ${r.prioritaet}

+ ${escapeHtml(r.schema)}${ordnerInfo}
diff --git a/Source/frontend/templates/index.html b/Source/frontend/templates/index.html index 916dbc7..a2a8ad6 100755 --- a/Source/frontend/templates/index.html +++ b/Source/frontend/templates/index.html @@ -199,7 +199,12 @@

📑 Sortier-Regeln

-
+
+