From 1ab7e54bd7fb43687c0c0606aadc4cfebbee6564 Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Wed, 22 Apr 2026 22:09:13 +0200 Subject: [PATCH] improve history collection --- routstr/core/admin.py | 34 +++++--- ui/app/transactions/page.tsx | 152 ++++++++++++++++++++++++++++------- ui/lib/api/services/admin.ts | 6 +- 3 files changed, 153 insertions(+), 39 deletions(-) diff --git a/routstr/core/admin.py b/routstr/core/admin.py index e8eebfcf..a8c69327 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -1335,42 +1335,56 @@ async def get_transactions_api( type: str | None = None, status: str | None = None, search: str | None = None, - limit: int = 100, + source: str | None = None, + limit: int = 50, + offset: int = 0, ) -> dict: async with create_session() as session: - from sqlmodel import col + from sqlmodel import col, func - stmt = select(CashuTransaction) + base = select(CashuTransaction) if type: - stmt = stmt.where(CashuTransaction.type == type) + base = base.where(CashuTransaction.type == type) + if source: + if source == "x-cashu": + base = base.where( + (CashuTransaction.source == "x-cashu") + | (CashuTransaction.source == None) # noqa: E711 + ) + else: + base = base.where(CashuTransaction.source == source) if status: if status == "collected": - stmt = stmt.where(CashuTransaction.collected == True) # noqa: E712 + base = base.where(CashuTransaction.collected == True) # noqa: E712 elif status == "swept": - stmt = stmt.where(CashuTransaction.swept == True) # noqa: E712 + base = base.where(CashuTransaction.swept == True) # noqa: E712 elif status == "pending": - stmt = stmt.where( + base = base.where( CashuTransaction.collected == False, # noqa: E712 CashuTransaction.swept == False, # noqa: E712 ) if search: search_pattern = f"%{search}%" - stmt = stmt.where( + base = base.where( (col(CashuTransaction.id).like(search_pattern)) | (col(CashuTransaction.token).like(search_pattern)) | (col(CashuTransaction.request_id).like(search_pattern)) | (col(CashuTransaction.api_key_hashed_key).like(search_pattern)) ) - stmt = stmt.order_by(col(CashuTransaction.created_at).desc()).limit(limit) + count_result = await session.exec( + select(func.count()).select_from(base.subquery()) + ) + total = count_result.one() + stmt = base.order_by(col(CashuTransaction.created_at).desc()).offset(offset).limit(limit) results = await session.exec(stmt) transactions = results.all() return { "transactions": [tx.dict() for tx in transactions], - "total": len(transactions), + "total": total, } diff --git a/ui/app/transactions/page.tsx b/ui/app/transactions/page.tsx index ffdc4ebb..235d273c 100644 --- a/ui/app/transactions/page.tsx +++ b/ui/app/transactions/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, keepPreviousData } from '@tanstack/react-query'; import { AppPageShell } from '@/components/app-page-shell'; import { PageHeader } from '@/components/page-header'; import { @@ -50,6 +50,8 @@ import { Receipt, Key, Zap, + ChevronLeft, + ChevronRight, } from 'lucide-react'; import { AdminService, type Transaction } from '@/lib/api/services/admin'; import { format } from 'date-fns'; @@ -225,21 +227,63 @@ export default function TransactionsPage() { localStorage.setItem(STORAGE_KEY, JSON.stringify(filters)); }, [search, type, status]); - const { data, isLoading, refetch, isRefetching } = useQuery({ - queryKey: ['transactions', type, status, search], + const PAGE_SIZE = 50; + const [activeTab, setActiveTab] = useState('x-cashu'); + const [xcashuPage, setXcashuPage] = useState(0); + const [apikeyPage, setApikeyPage] = useState(0); + + const typeParam = type === 'all' ? undefined : type; + const statusParam = status === 'all' ? undefined : status; + const searchParam = search || undefined; + + const xcashuQuery = useQuery({ + queryKey: [ + 'transactions', + 'x-cashu', + typeParam, + statusParam, + searchParam, + xcashuPage, + ], queryFn: () => AdminService.getTransactions( - type === 'all' ? undefined : type, - status === 'all' ? undefined : status, - search || undefined, - 100 + typeParam, + statusParam, + searchParam, + 'x-cashu', + PAGE_SIZE, + xcashuPage * PAGE_SIZE ), + placeholderData: keepPreviousData, + }); + + const apikeyQuery = useQuery({ + queryKey: [ + 'transactions', + 'apikey', + typeParam, + statusParam, + searchParam, + apikeyPage, + ], + queryFn: () => + AdminService.getTransactions( + typeParam, + statusParam, + searchParam, + 'apikey', + PAGE_SIZE, + apikeyPage * PAGE_SIZE + ), + placeholderData: keepPreviousData, }); const handleClearFilters = () => { setSearch(''); setType('all'); setStatus('all'); + setXcashuPage(0); + setApikeyPage(0); }; const copyToClipboard = (text: string, id: string) => { @@ -289,14 +333,21 @@ export default function TransactionsPage() { .filter(Boolean) .join(' • '); - const xcashuTxs = - data?.transactions.filter((tx) => !tx.source || tx.source === 'x-cashu') ?? - []; - const apikeyTxs = - data?.transactions.filter((tx) => tx.source === 'apikey') ?? []; + // Reset pages when filters change + useEffect(() => { + setXcashuPage(0); + setApikeyPage(0); + }, [type, status, search]); - const renderCardContent = (txs: Transaction[]) => { - if (isLoading) { + const activeQuery = activeTab === 'x-cashu' ? xcashuQuery : apikeyQuery; + const isRefetching = xcashuQuery.isRefetching || apikeyQuery.isRefetching; + + const renderCardContent = ( + query: typeof xcashuQuery, + page: number, + setPage: (p: number) => void + ) => { + if (query.isLoading) { return (
{Array.from({ length: 8 }).map((_, index) => ( @@ -308,13 +359,51 @@ export default function TransactionsPage() {
); } + + const transactions = query.data?.transactions ?? []; + const total = query.data?.total ?? 0; + const totalPages = Math.ceil(total / PAGE_SIZE); + return ( - + <> + {totalPages > 1 && ( +
+ + {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)}{' '} + of {total} + +
+ + + {page + 1} / {totalPages} + + +
+
+ )} + + ); }; @@ -326,7 +415,10 @@ export default function TransactionsPage() { description='View all incoming and outgoing Cashu token transactions.' actions={