From 7f243bbc1e830df88dce453355842d2d9be728bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Fri, 14 Nov 2025 13:02:42 +0100 Subject: [PATCH] Load more transactions button --- .../Sync/AccountSyncController.php | 1 + .../Controllers/Sync/BankSyncController.php | 1 + .../Sync/CategorySyncController.php | 1 + .../create-automation-rule-dialog.tsx | 6 +- .../js/components/sync-status-button.tsx | 1 + .../transactions/category-select.tsx | 1 + .../components/ui/data-table-pagination.tsx | 14 +--- resources/js/hooks/use-online-status.ts | 1 + .../js/pages/settings/automation-rules.tsx | 48 ++++++----- resources/js/pages/transactions/index.tsx | 80 +++++++++---------- resources/js/services/account-sync.ts | 1 + resources/js/services/bank-sync.ts | 1 + resources/js/services/category-sync.ts | 1 + resources/js/types/automation-rule.ts | 1 + tests/Feature/Sync/AccountSyncTest.php | 1 + tests/Feature/Sync/CategorySyncTest.php | 1 + 16 files changed, 82 insertions(+), 78 deletions(-) diff --git a/app/Http/Controllers/Sync/AccountSyncController.php b/app/Http/Controllers/Sync/AccountSyncController.php index 62a84ccc..fdcfe20f 100644 --- a/app/Http/Controllers/Sync/AccountSyncController.php +++ b/app/Http/Controllers/Sync/AccountSyncController.php @@ -27,3 +27,4 @@ class AccountSyncController extends Controller } } + diff --git a/app/Http/Controllers/Sync/BankSyncController.php b/app/Http/Controllers/Sync/BankSyncController.php index c8c63fa6..36c68f9f 100644 --- a/app/Http/Controllers/Sync/BankSyncController.php +++ b/app/Http/Controllers/Sync/BankSyncController.php @@ -28,3 +28,4 @@ class BankSyncController extends Controller } } + diff --git a/app/Http/Controllers/Sync/CategorySyncController.php b/app/Http/Controllers/Sync/CategorySyncController.php index f6275c66..ae7b7582 100644 --- a/app/Http/Controllers/Sync/CategorySyncController.php +++ b/app/Http/Controllers/Sync/CategorySyncController.php @@ -25,3 +25,4 @@ class CategorySyncController extends Controller } } + diff --git a/resources/js/components/automation-rules/create-automation-rule-dialog.tsx b/resources/js/components/automation-rules/create-automation-rule-dialog.tsx index de03c8cc..dcbc7460 100644 --- a/resources/js/components/automation-rules/create-automation-rule-dialog.tsx +++ b/resources/js/components/automation-rules/create-automation-rule-dialog.tsx @@ -45,7 +45,7 @@ export function CreateAutomationRuleDialog({ const [open, setOpen] = useState(false); const [categories, setCategories] = useState([]); const [title, setTitle] = useState(''); - const [priority, setPriority] = useState('0'); + const [priority, setPriority] = useState('10'); const [ruleStructure, setRuleStructure] = useState({ groups: [createEmptyGroup()], groupOperator: 'and', @@ -123,7 +123,7 @@ export function CreateAutomationRuleDialog({ onSuccess: async () => { setOpen(false); setTitle(''); - setPriority('0'); + setPriority('10'); setRuleStructure({ groups: [createEmptyGroup()], groupOperator: 'and', @@ -186,7 +186,7 @@ export function CreateAutomationRuleDialog({ min="0" value={priority} onChange={(e) => setPriority(e.target.value)} - placeholder="0" + placeholder="10" required />

diff --git a/resources/js/components/sync-status-button.tsx b/resources/js/components/sync-status-button.tsx index be9dca93..9d879e9e 100644 --- a/resources/js/components/sync-status-button.tsx +++ b/resources/js/components/sync-status-button.tsx @@ -75,3 +75,4 @@ export function SyncStatusButton() { ); } + diff --git a/resources/js/components/transactions/category-select.tsx b/resources/js/components/transactions/category-select.tsx index 8457ca69..96879e45 100644 --- a/resources/js/components/transactions/category-select.tsx +++ b/resources/js/components/transactions/category-select.tsx @@ -105,3 +105,4 @@ export function CategorySelect({ ); } + diff --git a/resources/js/components/ui/data-table-pagination.tsx b/resources/js/components/ui/data-table-pagination.tsx index c5333f23..4a6514dc 100644 --- a/resources/js/components/ui/data-table-pagination.tsx +++ b/resources/js/components/ui/data-table-pagination.tsx @@ -5,15 +5,17 @@ interface DataTablePaginationProps { rowCountLabel?: string; displayedCount?: number; total?: number; + children?: React.ReactNode; } export function DataTablePagination({ displayedCount = undefined, total = undefined, rowCountLabel = 'row(s) total', + children, }: DataTablePaginationProps) { return ( -

+
{displayedCount && total && (
{Math.min(total, displayedCount)} of {total} {rowCountLabel} @@ -24,15 +26,7 @@ export function DataTablePagination({ {displayedCount} {rowCountLabel}
)} - {((displayedCount ?? 0) < (total ?? 0)) &&
- -
} + {children}
); } diff --git a/resources/js/hooks/use-online-status.ts b/resources/js/hooks/use-online-status.ts index 4bf6643d..9ff93a79 100644 --- a/resources/js/hooks/use-online-status.ts +++ b/resources/js/hooks/use-online-status.ts @@ -22,3 +22,4 @@ export function useOnlineStatus() { return isOnline; } + diff --git a/resources/js/pages/settings/automation-rules.tsx b/resources/js/pages/settings/automation-rules.tsx index 7879a365..371acc3d 100644 --- a/resources/js/pages/settings/automation-rules.tsx +++ b/resources/js/pages/settings/automation-rules.tsx @@ -108,7 +108,12 @@ export default function AutomationRules() { })), [rawRules], ); - const [sorting, setSorting] = useState([]); + const [sorting, setSorting] = useState([ + { + id: 'priority', + desc: false, + }, + ]); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState( {}, @@ -116,23 +121,22 @@ export default function AutomationRules() { const columns: ColumnDef[] = [ { - accessorKey: 'title', - header: ({ column }) => { - return ( - - ); - }, + accessorKey: 'priority', + header: 'Priority', cell: ({ row }) => { return ( -
+
+ {row.getValue('priority')} +
+ ); + }, + }, + { + accessorKey: 'title', + header: 'Title', + cell: ({ row }) => { + return ( +
{row.getValue('title')}
); @@ -256,12 +260,12 @@ export default function AutomationRules() { {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext(), - )} + header + .column + .columnDef + .header, + header.getContext(), + )} ); }, diff --git a/resources/js/pages/transactions/index.tsx b/resources/js/pages/transactions/index.tsx index 9ff1c5eb..aca9b1ff 100644 --- a/resources/js/pages/transactions/index.tsx +++ b/resources/js/pages/transactions/index.tsx @@ -10,7 +10,7 @@ import { } from '@tanstack/react-table'; import { isWithinInterval, parseISO } from 'date-fns'; import { useLiveQuery } from 'dexie-react-hooks'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { index as transactionsIndex } from '@/actions/App/Http/Controllers/TransactionController'; @@ -30,10 +30,12 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; +import { Button } from '@/components/ui/button'; import { DataTable } from '@/components/ui/data-table'; import { DataTablePagination } from '@/components/ui/data-table-pagination'; import { DataTableViewOptions } from '@/components/ui/data-table-view-options'; import { Skeleton } from '@/components/ui/skeleton'; +import { Spinner } from '@/components/ui/spinner'; import { useEncryptionKey } from '@/contexts/encryption-key-context'; import AppSidebarLayout from '@/layouts/app/app-sidebar-layout'; import { decrypt, encrypt, importKey } from '@/lib/crypto'; @@ -50,6 +52,7 @@ import { type DecryptedTransaction, type TransactionFilters as Filters, } from '@/types/transaction'; +import { ChevronDown } from 'lucide-react'; const breadcrumbs: BreadcrumbItem[] = [ { @@ -125,7 +128,7 @@ export default function Transactions({ categories, accounts, banks }: Props) { const [isBulkUpdating, setIsBulkUpdating] = useState(false); const [isReEvaluating, setIsReEvaluating] = useState(false); const [displayedCount, setDisplayedCount] = useState(25); - const observerTarget = useRef(null); + const [isLoadingMore, setIsLoadingMore] = useState(false); const updateTransaction = useCallback( (updatedTransaction: DecryptedTransaction) => { @@ -552,7 +555,7 @@ export default function Transactions({ categories, accounts, banks }: Props) { const selectedCategory = result.categoryId ? categories.find((c) => c.id === result.categoryId) || - null + null : null; let decryptedNotes = transaction.decryptedNotes; @@ -696,7 +699,7 @@ export default function Transactions({ categories, accounts, banks }: Props) { const selectedCategory = result.categoryId ? categories.find((c) => c.id === result.categoryId) || - null + null : null; let decryptedNotes = transaction.decryptedNotes; @@ -805,34 +808,18 @@ export default function Transactions({ categories, accounts, banks }: Props) { }); const loadMore = useCallback(() => { - if (displayedCount < sortedTransactions.length) { - setDisplayedCount((prev) => - Math.min(prev + 25, sortedTransactions.length), - ); + if (displayedCount < sortedTransactions.length && !isLoadingMore) { + setIsLoadingMore(true); + requestAnimationFrame(() => { + setDisplayedCount((prev) => + Math.min(prev + 25, sortedTransactions.length), + ); + requestAnimationFrame(() => { + setIsLoadingMore(false); + }); + }); } - }, [displayedCount, sortedTransactions.length]); - - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting) { - loadMore(); - } - }, - { threshold: 0.1 }, - ); - - const currentTarget = observerTarget.current; - if (currentTarget) { - observer.observe(currentTarget); - } - - return () => { - if (currentTarget) { - observer.unobserve(currentTarget); - } - }; - }, [loadMore]); + }, [displayedCount, sortedTransactions.length, isLoadingMore]); useEffect(() => { setDisplayedCount(25); @@ -1031,18 +1018,25 @@ export default function Transactions({ categories, accounts, banks }: Props) { displayedCount={displayedCount} total={sortedTransactions.length} rowCountLabel="transactions total" - /> - - {displayedCount < sortedTransactions.length && ( -
-
- Loading more... -
-
- )} + > + {displayedCount < sortedTransactions.length && ( + + )} + )}
diff --git a/resources/js/services/account-sync.ts b/resources/js/services/account-sync.ts index 0e1a4927..177a4212 100644 --- a/resources/js/services/account-sync.ts +++ b/resources/js/services/account-sync.ts @@ -46,3 +46,4 @@ class AccountSyncService { export const accountSyncService = new AccountSyncService(); + diff --git a/resources/js/services/bank-sync.ts b/resources/js/services/bank-sync.ts index 0ebdd303..f875ee54 100644 --- a/resources/js/services/bank-sync.ts +++ b/resources/js/services/bank-sync.ts @@ -42,3 +42,4 @@ class BankSyncService { export const bankSyncService = new BankSyncService(); + diff --git a/resources/js/services/category-sync.ts b/resources/js/services/category-sync.ts index 2ec313c3..4971993a 100644 --- a/resources/js/services/category-sync.ts +++ b/resources/js/services/category-sync.ts @@ -46,3 +46,4 @@ class CategorySyncService { export const categorySyncService = new CategorySyncService(); + diff --git a/resources/js/types/automation-rule.ts b/resources/js/types/automation-rule.ts index ebb0b341..a0f5197f 100644 --- a/resources/js/types/automation-rule.ts +++ b/resources/js/types/automation-rule.ts @@ -61,3 +61,4 @@ export function formatRuleActions(rule: AutomationRule): string { return 'Add note'; } + diff --git a/tests/Feature/Sync/AccountSyncTest.php b/tests/Feature/Sync/AccountSyncTest.php index 9dda17a1..ccdc6473 100644 --- a/tests/Feature/Sync/AccountSyncTest.php +++ b/tests/Feature/Sync/AccountSyncTest.php @@ -79,3 +79,4 @@ it('requires authentication', function () { $response->assertUnauthorized(); }); + diff --git a/tests/Feature/Sync/CategorySyncTest.php b/tests/Feature/Sync/CategorySyncTest.php index d1a99c9a..f3c8d92f 100644 --- a/tests/Feature/Sync/CategorySyncTest.php +++ b/tests/Feature/Sync/CategorySyncTest.php @@ -53,3 +53,4 @@ it('requires authentication', function () { $response->assertUnauthorized(); }); +