feat(filters): smart query bar with amount operators and multi-select dropdowns

- Query bar parses >500, >=500, <500, <=500, 500-1500 into amount_min/max filters
- Parsed tokens shown as dismissable chips below the query bar
- Category, Bank, Tag, Type filters upgraded from single-select to multi-select
- MultiSelect dropdown component with checkbox list and active-state border
- Backend: TransactionFilters uses string[] for categories/bank_names/tag_ids/transaction_types
- SQL: ANY($n::text[]) / ANY($n::int[]) for array filters
This commit is contained in:
2026-03-14 20:39:28 +11:00
parent 8076d1a949
commit 5206388958
6 changed files with 244 additions and 64 deletions
+12 -4
View File
@@ -14,21 +14,29 @@ interface TransactionsResponse {
interface TransactionFilters {
from?: string;
to?: string;
category?: string;
bank_name?: string;
categories?: string[];
bank_names?: string[];
tag_ids?: string[];
transaction_types?: string[];
search?: string;
statement_id?: string;
tag_id?: string;
sort_by?: string;
sort_dir?: string;
limit?: number;
offset?: number;
amount_min?: number;
amount_max?: number;
}
function buildParams(filters: TransactionFilters): string {
const params = new URLSearchParams();
Object.entries(filters).forEach(([key, val]) => {
if (val !== undefined && val !== "") params.set(key, String(val));
if (val === undefined || val === "") return;
if (Array.isArray(val)) {
if (val.length > 0) params.set(key, val.join(","));
} else {
params.set(key, String(val));
}
});
return params.toString();
}