Added transaction descriptions to account history tiles.

This commit is contained in:
Andrew Lalis 2024-06-03 20:42:58 -04:00
parent 4cf95dba85
commit 5ce2360f05
2 changed files with 56 additions and 37 deletions

View File

@ -179,7 +179,7 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements
SELECT id, timestamp, 'BALANCE_RECORD' AS type, account_id SELECT id, timestamp, 'BALANCE_RECORD' AS type, account_id
FROM balance_record FROM balance_record
) )
WHERE account_id = ? AND timestamp <= ? WHERE account_id = ? AND timestamp < ?
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT\s""" + maxResults; LIMIT\s""" + maxResults;
try (var stmt = conn.prepareStatement(query)) { try (var stmt = conn.prepareStatement(query)) {

View File

@ -3,12 +3,10 @@ package com.andrewlalis.perfin.view.component;
import com.andrewlalis.perfin.control.TransactionsViewController; import com.andrewlalis.perfin.control.TransactionsViewController;
import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.AccountRepository;
import com.andrewlalis.perfin.data.DataSource; 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.CurrencyUtil;
import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.model.AccountEntry; import com.andrewlalis.perfin.model.*;
import com.andrewlalis.perfin.model.BalanceRecord;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.model.Timestamped;
import com.andrewlalis.perfin.model.history.HistoryTextItem; import com.andrewlalis.perfin.model.history.HistoryTextItem;
import com.andrewlalis.perfin.view.BindingUtil; import com.andrewlalis.perfin.view.BindingUtil;
import javafx.application.Platform; import javafx.application.Platform;
@ -27,6 +25,8 @@ import javafx.scene.text.TextFlow;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static com.andrewlalis.perfin.PerfinApp.router; import static com.andrewlalis.perfin.PerfinApp.router;
@ -60,7 +60,7 @@ public class AccountHistoryView extends ScrollPane {
int maxItems = initialItemsToLoadProperty.get(); int maxItems = initialItemsToLoadProperty.get();
DataSource ds = Profile.getCurrent().dataSource(); DataSource ds = Profile.getCurrent().dataSource();
ds.mapRepoAsync(AccountRepository.class, repo -> repo.findEventsBefore(accountId, lastTimestamp(), maxItems)) 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() { public void clear() {
@ -91,56 +91,75 @@ public class AccountHistoryView extends ScrollPane {
return lastTimestamp; return lastTimestamp;
} }
private Node makeTile(Timestamped entity) { private CompletableFuture<Node> makeTile(Timestamped entity) {
switch (entity) { switch (entity) {
case HistoryTextItem textItem -> { 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 -> { case AccountEntry ae -> {
Hyperlink txLink = new Hyperlink("Transaction #" + ae.getTransactionId()); Hyperlink txLink = new Hyperlink("Transaction #" + ae.getTransactionId());
txLink.setOnAction(event -> router.navigate("transactions", new TransactionsViewController.RouteContext(ae.getTransactionId()))); txLink.setOnAction(event -> router.navigate("transactions", new TransactionsViewController.RouteContext(ae.getTransactionId())));
String descriptionFormat = ae.getType() == AccountEntry.Type.CREDIT String descriptionFormat = ae.getType() == AccountEntry.Type.CREDIT
? "credited %s from this account." ? "credited %s from this account"
: "debited %s to this account."; : "debited %s to this account";
String description = descriptionFormat.formatted(CurrencyUtil.formatMoney(ae.getMoneyValue())); final String description = descriptionFormat.formatted(CurrencyUtil.formatMoney(ae.getMoneyValue()));
TextFlow textFlow = new TextFlow(txLink, new Text(description));
return new AccountHistoryTile(ae.getTimestamp(), textFlow); CompletableFuture<Node> future = new CompletableFuture<>();
Profile.getCurrent().dataSource().useRepoAsync(TransactionRepository.class, repo -> {
Optional<Transaction> 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 -> { case BalanceRecord br -> {
Hyperlink brLink = new Hyperlink("Balance Record #" + br.id); Hyperlink brLink = new Hyperlink("Balance Record #" + br.id);
brLink.setOnAction(event -> router.navigate("balance-record", br)); 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, brLink,
new Text("added with a value of %s.".formatted(CurrencyUtil.formatMoney(br.getMoneyAmount()))) new Text("added with a value of %s.".formatted(CurrencyUtil.formatMoney(br.getMoneyAmount())))
)); )));
} }
default -> { 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<Timestamped> entities, int requestedItems) { private void addEntitiesToHistory(List<Timestamped> entities, int requestedItems) {
if (!itemsVBox.getChildren().isEmpty()) { var futures = entities.stream().map(this::makeTile).toList();
itemsVBox.getChildren().add(new Separator(Orientation.HORIZONTAL)); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
} .thenRun(() -> {
itemsVBox.getChildren().addAll(entities.stream() List<AnchorPane> tiles = futures.stream().map(CompletableFuture::join)
.map(this::makeTile) .map(tile -> {
.map(tile -> { // Use this to scrunch content to the left.
// Use this to scrunch content to the left. AnchorPane ap = new AnchorPane(tile);
AnchorPane ap = new AnchorPane(tile); AnchorPane.setLeftAnchor(tile, 0.0);
AnchorPane.setLeftAnchor(tile, 0.0); return ap;
return ap; })
}) .toList();
.toList()); Platform.runLater(() -> {
if (entities.size() < requestedItems) { if (!itemsVBox.getChildren().isEmpty()) {
canLoadMore.set(false); itemsVBox.getChildren().add(new Separator(Orientation.HORIZONTAL));
BorderPane endMarker = new BorderPane(new Label("This is the start of the history.")); }
endMarker.getStyleClass().addAll("large-font", "italic-text"); itemsVBox.getChildren().addAll(tiles);
itemsVBox.getChildren().add(endMarker); if (entities.size() < requestedItems) {
} canLoadMore.set(false);
if (!entities.isEmpty()) { BorderPane endMarker = new BorderPane(new Label("This is the start of the history."));
lastTimestamp = entities.getLast().getTimestamp(); endMarker.getStyleClass().addAll("large-font", "italic-text");
} itemsVBox.getChildren().add(endMarker);
}
if (!entities.isEmpty()) {
lastTimestamp = entities.getLast().getTimestamp();
}
});
});
} }
} }