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:
@@ -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, '"')}"
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user