diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java index cec7837..c9eb1da 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java @@ -179,7 +179,7 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements SELECT id, timestamp, 'BALANCE_RECORD' AS type, account_id FROM balance_record ) - WHERE account_id = ? AND timestamp <= ? + WHERE account_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT\s""" + maxResults; try (var stmt = conn.prepareStatement(query)) { diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryView.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryView.java index ac7d46e..df3669a 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryView.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryView.java @@ -3,12 +3,10 @@ package com.andrewlalis.perfin.view.component; import com.andrewlalis.perfin.control.TransactionsViewController; import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.DataSource; +import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.util.CurrencyUtil; import com.andrewlalis.perfin.data.util.DateUtil; -import com.andrewlalis.perfin.model.AccountEntry; -import com.andrewlalis.perfin.model.BalanceRecord; -import com.andrewlalis.perfin.model.Profile; -import com.andrewlalis.perfin.model.Timestamped; +import com.andrewlalis.perfin.model.*; import com.andrewlalis.perfin.model.history.HistoryTextItem; import com.andrewlalis.perfin.view.BindingUtil; import javafx.application.Platform; @@ -27,6 +25,8 @@ import javafx.scene.text.TextFlow; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import static com.andrewlalis.perfin.PerfinApp.router; @@ -60,7 +60,7 @@ public class AccountHistoryView extends ScrollPane { int maxItems = initialItemsToLoadProperty.get(); DataSource ds = Profile.getCurrent().dataSource(); ds.mapRepoAsync(AccountRepository.class, repo -> repo.findEventsBefore(accountId, lastTimestamp(), maxItems)) - .thenAccept(entities -> Platform.runLater(() -> addEntitiesToHistory(entities, maxItems))); + .thenAccept(entities -> addEntitiesToHistory(entities, maxItems)); } public void clear() { @@ -91,56 +91,75 @@ public class AccountHistoryView extends ScrollPane { return lastTimestamp; } - private Node makeTile(Timestamped entity) { + private CompletableFuture makeTile(Timestamped entity) { switch (entity) { case HistoryTextItem textItem -> { - return new AccountHistoryTile(textItem.getTimestamp(), new TextFlow(new Text(textItem.getDescription()))); + return CompletableFuture.completedFuture( + new AccountHistoryTile(textItem.getTimestamp(), new TextFlow(new Text(textItem.getDescription()))) + ); } case AccountEntry ae -> { Hyperlink txLink = new Hyperlink("Transaction #" + ae.getTransactionId()); txLink.setOnAction(event -> router.navigate("transactions", new TransactionsViewController.RouteContext(ae.getTransactionId()))); String descriptionFormat = ae.getType() == AccountEntry.Type.CREDIT - ? "credited %s from this account." - : "debited %s to this account."; - String description = descriptionFormat.formatted(CurrencyUtil.formatMoney(ae.getMoneyValue())); - TextFlow textFlow = new TextFlow(txLink, new Text(description)); - return new AccountHistoryTile(ae.getTimestamp(), textFlow); + ? "credited %s from this account" + : "debited %s to this account"; + final String description = descriptionFormat.formatted(CurrencyUtil.formatMoney(ae.getMoneyValue())); + + CompletableFuture future = new CompletableFuture<>(); + Profile.getCurrent().dataSource().useRepoAsync(TransactionRepository.class, repo -> { + Optional optionalTransaction = repo.findById(ae.getTransactionId()); + String extraText = optionalTransaction.map(transaction -> ": " + transaction.getDescription()) + .orElse(". No transaction information found."); + TextFlow textFlow = new TextFlow(txLink, new Text(description + extraText)); + future.complete(new AccountHistoryTile(ae.getTimestamp(), textFlow)); + }); + return future; + } case BalanceRecord br -> { Hyperlink brLink = new Hyperlink("Balance Record #" + br.id); brLink.setOnAction(event -> router.navigate("balance-record", br)); - return new AccountHistoryTile(br.getTimestamp(), new TextFlow( + return CompletableFuture.completedFuture(new AccountHistoryTile(br.getTimestamp(), new TextFlow( brLink, new Text("added with a value of %s.".formatted(CurrencyUtil.formatMoney(br.getMoneyAmount()))) - )); + ))); } default -> { - return new AccountHistoryTile(entity.getTimestamp(), new TextFlow(new Text("Unsupported entity: " + entity.getClass().getName()))); + return CompletableFuture.completedFuture( + new AccountHistoryTile(entity.getTimestamp(), new TextFlow(new Text("Unsupported entity: " + entity.getClass().getName()))) + ); } } } private void addEntitiesToHistory(List entities, int requestedItems) { - if (!itemsVBox.getChildren().isEmpty()) { - itemsVBox.getChildren().add(new Separator(Orientation.HORIZONTAL)); - } - itemsVBox.getChildren().addAll(entities.stream() - .map(this::makeTile) - .map(tile -> { - // Use this to scrunch content to the left. - AnchorPane ap = new AnchorPane(tile); - AnchorPane.setLeftAnchor(tile, 0.0); - return ap; - }) - .toList()); - if (entities.size() < requestedItems) { - canLoadMore.set(false); - BorderPane endMarker = new BorderPane(new Label("This is the start of the history.")); - endMarker.getStyleClass().addAll("large-font", "italic-text"); - itemsVBox.getChildren().add(endMarker); - } - if (!entities.isEmpty()) { - lastTimestamp = entities.getLast().getTimestamp(); - } + var futures = entities.stream().map(this::makeTile).toList(); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenRun(() -> { + List tiles = futures.stream().map(CompletableFuture::join) + .map(tile -> { + // Use this to scrunch content to the left. + AnchorPane ap = new AnchorPane(tile); + AnchorPane.setLeftAnchor(tile, 0.0); + return ap; + }) + .toList(); + Platform.runLater(() -> { + if (!itemsVBox.getChildren().isEmpty()) { + itemsVBox.getChildren().add(new Separator(Orientation.HORIZONTAL)); + } + itemsVBox.getChildren().addAll(tiles); + if (entities.size() < requestedItems) { + canLoadMore.set(false); + BorderPane endMarker = new BorderPane(new Label("This is the start of the history.")); + endMarker.getStyleClass().addAll("large-font", "italic-text"); + itemsVBox.getChildren().add(endMarker); + } + if (!entities.isEmpty()) { + lastTimestamp = entities.getLast().getTimestamp(); + } + }); + }); } }