add live search to filter rules list

Substring search across rule name, source folder, condition values
and action parameters. Filters the table client-side as the user
types and shows a match counter (e.g. "3 / 12").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 10:51:32 +02:00
parent 202920b9ef
commit e4669cfccd
+48 -2
View File
@@ -191,6 +191,23 @@ async function loadFilters() {
const resp = await fetch(`/api/filters/account/${accountId}`);
const filters = await resp.json();
currentFilters = filters;
renderFilters();
}
function filterHaystack(f) {
const parts = [
f.name || '',
f.source_folder || '',
...(f.conditions || []).flatMap(c => [c.field, c.match_type, c.value]),
...(f.actions || []).flatMap(a => [a.action_type, a.parameter || '']),
];
return parts.join(' ').toLowerCase();
}
function renderFilters() {
const container = document.getElementById('filters-container');
const filters = currentFilters;
if (filters.length === 0) {
container.innerHTML = `
<p>Keine Filterregeln für dieses Konto.</p>
@@ -198,8 +215,33 @@ async function loadFilters() {
`;
return;
}
let html = '<button onclick="openNewFilter()" style="margin-bottom:1rem">Neue Regel</button><table><thead><tr><th>Prio</th><th>Name</th><th>Ordner</th><th>Bedingungen</th><th>Aktionen</th><th></th></tr></thead><tbody>';
for (const f of filters) {
const searchTerm = (document.getElementById('filter-search')?.value || '').trim().toLowerCase();
const visible = searchTerm
? filters.filter(f => filterHaystack(f).includes(searchTerm))
: filters;
let html = `
<div style="display:flex; gap:0.75rem; align-items:center; margin-bottom:1rem; flex-wrap:wrap;">
<button onclick="openNewFilter()" style="margin-bottom:0;">Neue Regel</button>
<input type="search" id="filter-search" placeholder="Filter durchsuchen..."
value="${searchTerm.replace(/"/g, '&quot;')}"
oninput="renderFilters()"
style="flex:1; min-width:200px; margin-bottom:0;">
<small style="opacity:0.7; white-space:nowrap;">${visible.length} / ${filters.length}</small>
</div>`;
if (visible.length === 0) {
html += '<p><em>Keine Treffer.</em></p>';
container.innerHTML = html;
// Cursor zurück ins Suchfeld, sonst verliert das Input den Fokus beim Re-render
const s = document.getElementById('filter-search');
if (s) { s.focus(); s.setSelectionRange(searchTerm.length, searchTerm.length); }
return;
}
html += '<table><thead><tr><th>Prio</th><th>Name</th><th>Ordner</th><th>Bedingungen</th><th>Aktionen</th><th></th></tr></thead><tbody>';
for (const f of visible) {
const conds = f.conditions.map(c => {
const fieldLabel = FIELDS.find(x => x.value === c.field)?.label || c.field;
const matchLabel = ALL_MATCH_TYPES.find(x => x.value === c.match_type)?.label || c.match_type;
@@ -225,6 +267,10 @@ async function loadFilters() {
}
html += '</tbody></table>';
container.innerHTML = html;
// Cursor zurück ins Suchfeld nach Re-render
const s = document.getElementById('filter-search');
if (s && searchTerm) { s.focus(); s.setSelectionRange(searchTerm.length, searchTerm.length); }
}
function addCondition(data = null) {