diff --git a/lang/es.json b/lang/es.json
index b0112af9..2e54b37d 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -1350,6 +1350,7 @@
"Savings rate": "Tasa de ahorro",
"Search": "Buscar",
"Search bank...": "Buscar banco...",
+ "Search accounts...": "Buscar cuentas...",
"Search banks...": "Buscar bancos...",
"Search categories...": "Buscar categorías...",
"Search description or notes...": "Buscar descripción o notas...",
@@ -1384,6 +1385,7 @@
"Select an account": "Selecciona una cuenta",
"Select an icon": "Seleccionar un icono",
"Select at least a category or a label to track.": "Selecciona al menos una categoría o etiqueta para rastrear.",
+ "Select accounts...": "Seleccionar cuentas...",
"Select balance column": "Seleccionar columna de balance",
"Select balance column (optional)": "Seleccionar columna de balance (opcional)",
"Select bank...": "Seleccionar banco...",
diff --git a/resources/js/components/transactions/transaction-filters.test.tsx b/resources/js/components/transactions/transaction-filters.test.tsx
new file mode 100644
index 00000000..482f223a
--- /dev/null
+++ b/resources/js/components/transactions/transaction-filters.test.tsx
@@ -0,0 +1,118 @@
+import { type Account } from '@/types/account';
+import { type TransactionFilters as FiltersType } from '@/types/transaction';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { TransactionFilters } from './transaction-filters';
+
+const accounts: Account[] = [
+ {
+ id: 'acc-1',
+ name: 'Checking',
+ name_iv: null,
+ encrypted: false,
+ bank: {
+ id: 'bank-1',
+ user_id: null,
+ name: 'Acme Bank',
+ logo: 'https://example.com/acme.png',
+ },
+ type: 'checking',
+ currency_code: 'USD',
+ banking_connection_id: null,
+ external_account_id: null,
+ linked_at: null,
+ },
+ {
+ id: 'acc-2',
+ name: 'Savings',
+ name_iv: null,
+ encrypted: false,
+ bank: null,
+ type: 'savings',
+ currency_code: 'USD',
+ banking_connection_id: null,
+ external_account_id: null,
+ linked_at: null,
+ },
+];
+
+const emptyFilters: FiltersType = {
+ dateFrom: null,
+ dateTo: null,
+ amountMin: null,
+ amountMax: null,
+ categoryIds: [],
+ accountIds: [],
+ labelIds: [],
+ creditorName: '',
+ debtorName: '',
+ searchText: '',
+ aiCategorizedOnly: false,
+};
+
+function renderFilters(
+ filters: FiltersType,
+ accountList: Account[] = accounts,
+) {
+ const onFiltersChange = vi.fn();
+ render(
+ ,
+ );
+ return onFiltersChange;
+}
+
+describe('TransactionFilters accounts dropdown', () => {
+ beforeEach(() => {
+ globalThis.ResizeObserver = class {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ };
+ Element.prototype.scrollIntoView = vi.fn();
+ Element.prototype.hasPointerCapture = vi.fn(() => false);
+ Element.prototype.setPointerCapture = vi.fn();
+ Element.prototype.releasePointerCapture = vi.fn();
+ });
+
+ it('adds an account id when an account is selected', () => {
+ const onFiltersChange = renderFilters(emptyFilters);
+
+ fireEvent.click(screen.getByRole('button', { name: /Filters/ }));
+ fireEvent.click(screen.getByText('Select accounts...'));
+ fireEvent.click(screen.getByText('Checking'));
+
+ expect(onFiltersChange).toHaveBeenCalledWith(
+ expect.objectContaining({ accountIds: ['acc-1'] }),
+ );
+ });
+
+ it('removes an account id when a selected account is toggled off', () => {
+ const onFiltersChange = renderFilters({
+ ...emptyFilters,
+ accountIds: ['acc-1'],
+ });
+
+ fireEvent.click(screen.getByRole('button', { name: /Filters/ }));
+ fireEvent.click(screen.getByText('1 selected'));
+ fireEvent.click(screen.getByText('Checking'));
+
+ expect(onFiltersChange).toHaveBeenCalledWith(
+ expect.objectContaining({ accountIds: [] }),
+ );
+ });
+
+ it('shows an empty state when there are no accounts', () => {
+ renderFilters(emptyFilters, []);
+
+ fireEvent.click(screen.getByRole('button', { name: /Filters/ }));
+ fireEvent.click(screen.getByText('Select accounts...'));
+
+ expect(screen.getByText('No accounts found.')).toBeInTheDocument();
+ });
+});
diff --git a/resources/js/components/transactions/transaction-filters.tsx b/resources/js/components/transactions/transaction-filters.tsx
index 236ae37f..4046693b 100644
--- a/resources/js/components/transactions/transaction-filters.tsx
+++ b/resources/js/components/transactions/transaction-filters.tsx
@@ -5,6 +5,7 @@ import { ChevronsUpDown, Tag, X } from 'lucide-react';
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { AccountName } from '@/components/accounts/account-name';
+import { BankLogo } from '@/components/bank-logo';
import { SavedFilters } from '@/components/transactions/saved-filters';
import { AiSparkleIcon } from '@/components/ui/ai-sparkle-icon';
import { Badge } from '@/components/ui/badge';
@@ -35,6 +36,7 @@ import { type Account } from '@/types/account';
import { type Category, getCategoryColorClasses } from '@/types/category';
import { getLabelColorClasses, type Label } from '@/types/label';
import { type TransactionFilters as FiltersType } from '@/types/transaction';
+import { type UUID } from '@/types/uuid';
import { CategoryIcon } from '../shared/category-combobox';
interface TransactionFiltersProps {
@@ -62,6 +64,7 @@ export function TransactionFilters({
const [categoryDropdownOpen, setCategoryDropdownOpen] = useState(false);
const [categorySearch, setCategorySearch] = useState('');
const [labelDropdownOpen, setLabelDropdownOpen] = useState(false);
+ const [accountDropdownOpen, setAccountDropdownOpen] = useState(false);
const [searchText, setSearchText] = useState(filters.searchText);
const [creditorName, setCreditorName] = useState(filters.creditorName);
const [debtorName, setDebtorName] = useState(filters.debtorName);
@@ -187,7 +190,7 @@ export function TransactionFilters({
});
}
- function handleAccountToggle(accountId: number) {
+ function handleAccountToggle(accountId: UUID) {
const newAccountIds = filters.accountIds.includes(accountId)
? filters.accountIds.filter((id) => id !== accountId)
: [...filters.accountIds, accountId];
@@ -367,28 +370,6 @@ export function TransactionFilters({
-
-
{__('Categories')}
@@ -658,37 +639,109 @@ export function TransactionFilters({
{!hideAccountFilter && (
{__('Accounts')}
-
- {accounts.map((account) => {
- const isSelected =
- filters.accountIds.includes(
- account.id,
- );
- return (
-
- handleAccountToggle(
- account.id,
- )
- }
+
+
+
+
+
+
+
+
-
- );
- })}
+
+
+ {__(
+ 'No accounts found.',
+ )}
+
+ {accounts.map(
+ (account) => {
+ const isSelected =
+ filters.accountIds.includes(
+ account.id,
+ );
+ return (
+
+ handleAccountToggle(
+ account.id,
+ )
+ }
+ >
+
+
+
+ );
+ },
+ )}
+
+
+
+
)}
@@ -718,6 +771,28 @@ export function TransactionFilters({
+
+