Adds pagination to transaction history

Implements pagination for the transaction history, enabling users
to navigate through their transactions in manageable chunks. This
improves performance and user experience, especially for users
with a large number of transactions. It also introduces total entry counts.
This commit is contained in:
padreug 2025-11-08 23:51:12 +01:00
parent 4b327a0aab
commit 69b8f6e2d3
4 changed files with 120 additions and 12 deletions

48
crud.py
View file

@ -266,14 +266,14 @@ async def get_entry_lines(journal_entry_id: str) -> list[EntryLine]:
return lines
async def get_all_journal_entries(limit: int = 100) -> list[JournalEntry]:
async def get_all_journal_entries(limit: int = 100, offset: int = 0) -> list[JournalEntry]:
entries_data = await db.fetchall(
"""
SELECT * FROM journal_entries
ORDER BY entry_date DESC, created_at DESC
LIMIT :limit
LIMIT :limit OFFSET :offset
""",
{"limit": limit},
{"limit": limit, "offset": offset},
)
entries = []
@ -301,7 +301,7 @@ async def get_all_journal_entries(limit: int = 100) -> list[JournalEntry]:
async def get_journal_entries_by_user(
user_id: str, limit: int = 100
user_id: str, limit: int = 100, offset: int = 0
) -> list[JournalEntry]:
"""Get journal entries that affect the user's accounts"""
# Get all user-specific accounts
@ -320,6 +320,7 @@ async def get_journal_entries_by_user(
placeholders = ','.join([f":account_{i}" for i in range(len(account_ids))])
params = {f"account_{i}": acc_id for i, acc_id in enumerate(account_ids)}
params["limit"] = limit
params["offset"] = offset
entries_data = await db.fetchall(
f"""
@ -328,7 +329,7 @@ async def get_journal_entries_by_user(
JOIN entry_lines el ON je.id = el.journal_entry_id
WHERE el.account_id IN ({placeholders})
ORDER BY je.entry_date DESC, je.created_at DESC
LIMIT :limit
LIMIT :limit OFFSET :offset
""",
params,
)
@ -357,6 +358,43 @@ async def get_journal_entries_by_user(
return entries
async def count_all_journal_entries() -> int:
"""Count total number of journal entries"""
result = await db.fetchone(
"SELECT COUNT(*) as total FROM journal_entries"
)
return result["total"] if result else 0
async def count_journal_entries_by_user(user_id: str) -> int:
"""Count journal entries that affect the user's accounts"""
# Get all user-specific accounts
user_accounts = await db.fetchall(
"SELECT id FROM accounts WHERE user_id = :user_id",
{"user_id": user_id},
)
if not user_accounts:
return 0
account_ids = [acc["id"] for acc in user_accounts]
# Count journal entries that have lines affecting these accounts
placeholders = ','.join([f":account_{i}" for i in range(len(account_ids))])
params = {f"account_{i}": acc_id for i, acc_id in enumerate(account_ids)}
result = await db.fetchone(
f"""
SELECT COUNT(DISTINCT je.id) as total
FROM journal_entries je
JOIN entry_lines el ON je.id = el.journal_entry_id
WHERE el.account_id IN ({placeholders})
""",
params,
)
return result["total"] if result else 0
# ===== BALANCE AND REPORTING =====

View file

@ -10,6 +10,13 @@ window.app = Vue.createApp({
balance: null,
allUserBalances: [],
transactions: [],
transactionPagination: {
total: 0,
limit: 20,
offset: 0,
has_next: false,
has_prev: false
},
accounts: [],
currencies: [],
users: [],
@ -306,18 +313,39 @@ window.app = Vue.createApp({
console.error('Error loading all user balances:', error)
}
},
async loadTransactions() {
async loadTransactions(offset = null) {
try {
// Use provided offset or current pagination offset
const currentOffset = offset !== null ? offset : this.transactionPagination.offset
const response = await LNbits.api.request(
'GET',
'/castle/api/v1/entries/user',
`/castle/api/v1/entries/user?limit=${this.transactionPagination.limit}&offset=${currentOffset}`,
this.g.user.wallets[0].inkey
)
this.transactions = response.data
// Update transactions and pagination info
this.transactions = response.data.entries
this.transactionPagination.total = response.data.total
this.transactionPagination.offset = response.data.offset
this.transactionPagination.has_next = response.data.has_next
this.transactionPagination.has_prev = response.data.has_prev
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
nextTransactionsPage() {
if (this.transactionPagination.has_next) {
const newOffset = this.transactionPagination.offset + this.transactionPagination.limit
this.loadTransactions(newOffset)
}
},
prevTransactionsPage() {
if (this.transactionPagination.has_prev) {
const newOffset = Math.max(0, this.transactionPagination.offset - this.transactionPagination.limit)
this.loadTransactions(newOffset)
}
},
async loadAccounts() {
try {
const response = await LNbits.api.request(

View file

@ -394,6 +394,35 @@
No transactions yet
</div>
</q-card-section>
<!-- Pagination Controls -->
<q-card-section v-if="transactionPagination.total > transactionPagination.limit" class="q-pt-none">
<div class="row items-center justify-between">
<div class="col-auto">
<q-btn
flat
dense
icon="chevron_left"
label="Previous"
:disable="!transactionPagination.has_prev"
@click="prevTransactionsPage"
/>
</div>
<div class="col text-center text-grey">
{% raw %}{{ transactionPagination.offset + 1 }} - {{ Math.min(transactionPagination.offset + transactionPagination.limit, transactionPagination.total) }} of {{ transactionPagination.total }}{% endraw %}
</div>
<div class="col-auto">
<q-btn
flat
dense
icon-right="chevron_right"
label="Next"
:disable="!transactionPagination.has_next"
@click="nextTransactionsPage"
/>
</div>
</div>
</q-card-section>
</q-card>
<!-- Balance Assertions (Super User Only) -->

View file

@ -265,16 +265,29 @@ async def api_get_journal_entries(limit: int = 100) -> list[JournalEntry]:
@castle_api_router.get("/api/v1/entries/user")
async def api_get_user_entries(
wallet: WalletTypeInfo = Depends(require_invoice_key),
limit: int = 100,
) -> list[JournalEntry]:
limit: int = 20,
offset: int = 0,
) -> dict:
"""Get journal entries that affect the current user's accounts"""
from lnbits.settings import settings as lnbits_settings
from .crud import count_all_journal_entries, count_journal_entries_by_user
# If super user, show all journal entries
if wallet.wallet.user == lnbits_settings.super_user:
return await get_all_journal_entries(limit)
entries = await get_all_journal_entries(limit, offset)
total = await count_all_journal_entries()
else:
entries = await get_journal_entries_by_user(wallet.wallet.user, limit, offset)
total = await count_journal_entries_by_user(wallet.wallet.user)
return await get_journal_entries_by_user(wallet.wallet.user, limit)
return {
"entries": entries,
"total": total,
"limit": limit,
"offset": offset,
"has_next": (offset + limit) < total,
"has_prev": offset > 0,
}
@castle_api_router.get("/api/v1/entries/pending")