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
+51
View File
@@ -0,0 +1,51 @@
/**
* Saved filters API hooks — CRUD for reusable filter criteria.
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost, apiDelete } from './client';
export interface SavedFilter {
id: string;
name: string;
entity_type: string;
filter_criteria: Record<string, any>;
user_id: string;
created_at: string;
updated_at: string;
}
export interface SavedFilterCreateInput {
name: string;
entity_type: string;
filter_criteria: Record<string, any>;
}
export function useSavedFilters(entityType?: string) {
const params = new URLSearchParams();
if (entityType) params.set('entity_type', entityType);
return useQuery({
queryKey: ['saved-filters', entityType],
queryFn: () => apiGet<SavedFilter[]>(`/saved-filters?${params.toString()}`),
});
}
export function useCreateSavedFilter() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: SavedFilterCreateInput) => apiPost<SavedFilter>('/saved-filters', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['saved-filters'] });
},
});
}
export function useDeleteSavedFilter() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => apiDelete(`/saved-filters/${id}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['saved-filters'] });
},
});
}