feat: add pagination support for user transactions retrieval

This commit is contained in:
csimonis 2025-04-23 11:37:11 +02:00
parent 9817fb95db
commit 03d67ef362
6 changed files with 92 additions and 17 deletions

View file

@ -16,6 +16,6 @@ public interface TransactionRepository extends JpaRepository<TransactionEntity,
@Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1")
List<TransactionEntity> findAllByUserId(UserEntity id);
@Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2")
List<TransactionEntity> findByUserIdWithLimit(UserEntity userEntity, Integer limit);
@Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2 OFFSET ?3")
List<TransactionEntity> findByUserIdWithLimit(UserEntity userEntity, Integer limit, Integer offset);
}

View file

@ -20,11 +20,11 @@ public class GetTransactionService {
@Autowired
private TransactionRepository transactionRepository;
public List<TransactionEntity> getUserTransactions(String authToken, Integer limit) {
public List<TransactionEntity> getUserTransactions(String authToken, Integer limit, Integer offset) {
Optional<UserEntity> user = this.userService.getCurrentUser(authToken);
if (user.isPresent()) {
if (limit != null && limit > 0) {
return this.transactionRepository.findByUserIdWithLimit(user.get(), limit);
return this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset);
} else {
return this.transactionRepository.findAllByUserId(user.get());
}

View file

@ -18,8 +18,12 @@ public class TransactionController {
private GetTransactionService transactionService;
@GetMapping("/user/transactions")
public ResponseEntity<List<GetTransactionDto>> getUserTransactions(@RequestHeader("Authorization") String authToken, @RequestParam(value = "limit", required = false) Integer limit) {
List<TransactionEntity> transactionEntities = this.transactionService.getUserTransactions(authToken, limit);
public ResponseEntity<List<GetTransactionDto>> getUserTransactions(
@RequestHeader("Authorization") String authToken,
@RequestParam(value = "limit", required = false) Integer limit,
@RequestParam(value = "offset", required = false) Integer offset
) {
List<TransactionEntity> transactionEntities = this.transactionService.getUserTransactions(authToken, limit, offset);
return ResponseEntity.ok(this.transactionService.mapTransactionsToDtos(transactionEntities));
}

View file

@ -1,10 +1,41 @@
<div *ngIf="isOpen" [@fadeInOut] class="modal-bg" style="z-index: 1000; position: fixed;">
<div class="modal-card" [@cardAnimation]>
<button type="button" (click)="closeDialog()" class="absolute top-2 right-2 text-text-secondary">
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12"/></svg>
</button>
<h2 class="modal-heading">Transaktionen</h2>
<p class="py-2 text-text-secondary mb-4">Hier siehst du alle vergangenen Einzahlungen</p>
<button type="button" (click)="closeDialog()" class="button-primary w-full py-2">
Verstanden
</button>
<div *ngIf="!loading()">
<div *ngFor="let transaction of transactions$ | async">
<div class="flex justify-between items-center mb-4">
<div>
<p class="text-sm font-medium">{{ transaction.status }}</p>
<p class="text-xs text-text-secondary">{{ transaction.createdAt | date : 'd.m.Y H:m'}}</p>
</div>
<span [class]="transaction.amount > 0 ? 'text-emerald' : 'text-accent-red'">
{{ transaction.amount | currency: 'EUR' }}
</span>
</div>
</div>
</div>
<div *ngIf="loading()">
<div *ngFor="let item of skeletonItems">
<div class="flex justify-between items-center mb-4 animate-pulse">
<div>
<div class="h-4 bg-gray-200 rounded w-20"></div>
<div class="h-3 bg-gray-200 rounded w-24 mt-2"></div>
</div>
<div class="h-4 bg-gray-200 rounded w-16"></div>
</div>
</div>
</div>
<div class="inline inline-flex w-full">
<button type="button" (click)="back()" class="button-primary w-full py-2">
<
</button>
<button type="button" (click)="forward()" class="button-primary w-full py-2">
>
</button>
</div>
</div>
</div>

View file

@ -1,10 +1,21 @@
import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
inject,
Input,
Output,
signal,
WritableSignal,
} from '@angular/core';
import { TransactionService } from '@service/transaction.service';
import { Observable } from 'rxjs';
import { Transaction } from '../../model/Transaction';
import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common';
import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component';
const PER_PAGE = 5
@Component({
standalone: true,
selector: 'app-transaction-history',
@ -18,18 +29,43 @@ import { AnimatedNumberComponent } from '@blackjack/components/animated-number/a
],
templateUrl: './transaction-history.component.html',
styleUrl: './transaction-history.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TransactionHistoryComponent {
@Input()
isOpen: boolean = false;
private transactionService: TransactionService = inject(TransactionService);
loading: WritableSignal<boolean> = signal<boolean>(true);
skeletonItems = Array(PER_PAGE).fill({});
@Output()
closeEventEmitter = new EventEmitter<void>();
transactions$: Observable<Transaction[]> = this.transactionService.getUsersTransactions();
private offset: number = 0;
private transactionService: TransactionService = inject(TransactionService);
transactions$: Observable<Transaction[]> = this.loadTransactions();
closeDialog() {
this.isOpen = false;
this.closeEventEmitter.emit();
}
forward() {
this.offset++;
this.transactions$ = this.loadTransactions();
}
back() {
this.offset--;
this.transactions$ = this.loadTransactions();
}
loadTransactions() {
this.loading.set(true);
const transactions$ = this.transactionService.getUsersTransactions(PER_PAGE, this.offset * PER_PAGE);
transactions$.subscribe({complete: () => this.loading.set(false)})
return transactions$;
}
}

View file

@ -7,11 +7,15 @@ import { HttpClient } from '@angular/common/http';
export class TransactionService {
private http: HttpClient = inject(HttpClient);
public getUsersTransactions(limit: number|null = null) {
const baseUrl = '/backend/user/transactions';
public getUsersTransactions(limit: number|null = null, offset: number|null = null) {
let baseUrl = new URL(`${window.location.origin}/backend/user/transactions`);
if (limit !== null) {
return this.http.get<any[]>(`${baseUrl}?limit=${limit}`);
baseUrl.searchParams.append('limit', limit.toString());
}
if (offset !== null) {
baseUrl.searchParams.append('offset', offset.toString());
}
return this.http.get<any[]>(`${baseUrl}`);