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:
parent
4b327a0aab
commit
69b8f6e2d3
4 changed files with 120 additions and 12 deletions
48
crud.py
48
crud.py
|
|
@ -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 =====
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) -->
|
||||
|
|
|
|||
21
views_api.py
21
views_api.py
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue