feat(5.22): Saved Searches / Smart Lists — reusable filters for list views

- SavedFilter model with TenantMixin (name, entity_type, filter_criteria JSONB, user_id)
- Routes: GET/POST /saved-filters, DELETE /saved-filters/{id} with RBAC
- Alembic migration 0029_saved_filters creates saved_filters table
- Frontend: SavedFilters.tsx component with save/load/delete UI
- Frontend: api/savedFilters.ts with React Query hooks
- Integrated into ContactsListPage as example
- i18n keys for savedFilters.* in de.json and en.json
- Tests: test_saved_filters.py (9 tests) + SavedFilters.test.tsx (3 tests)
- Registered SavedFilter in conftest.py
This commit is contained in:
Agent Zero
2026-07-23 23:44:50 +02:00
parent 2c9e74776e
commit 182af355d1
12 changed files with 663 additions and 0 deletions
+16
View File
@@ -16,6 +16,7 @@ import { ContactFolderTree, type ContactFilter } from '@/components/contacts/Con
import { ContactList, type ContactViewMode } from '@/components/contacts/ContactList';
import { ContactDetail } from '@/components/contacts/ContactDetail';
import { ContactEditModal } from '@/components/contacts/ContactEditModal';
import { SavedFilters } from '@/components/SavedFilters';
import { ArrowDownAZ, ArrowUpZA, ChevronLeft, LayoutGrid, List, Plus } from 'lucide-react';
import {
useUnifiedContacts,
@@ -328,6 +329,21 @@ export function ContactsListPage() {
</button>
</div>
{/* Saved Filters */}
<div className="px-3 py-1.5 border-b border-secondary-200 bg-secondary-50 flex-shrink-0">
<SavedFilters
entityType="contacts"
currentCriteria={{ search: debouncedSearch, type: contactType, sortBy, sortOrder, folderId }}
onLoadFilter={(criteria) => {
if (criteria.search) setSearch(criteria.search); else setSearch('');
if (criteria.sortBy) setSortBy(criteria.sortBy);
if (criteria.sortOrder) setSortOrder(criteria.sortOrder);
if (criteria.type) setSelectedFilter(criteria.type as ContactFilter);
setPage(1);
}}
/>
</div>
{/* List */}
<div className="flex-1 min-h-0">
<ContactList