From f9a0fea9ab56f81e87bbdfaa918a530644a7a6c5 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 4 Feb 2024 12:35:59 -0500 Subject: [PATCH] Added dashboard page and initial account and transaction modules. --- .../com/andrewlalis/perfin/PerfinApp.java | 1 + .../perfin/control/DashboardController.java | 39 +++++++ .../perfin/control/MainViewController.java | 14 +-- .../control/ProfilesViewController.java | 2 +- .../perfin/data/AccountRepository.java | 2 + .../perfin/data/TransactionRepository.java | 1 + .../data/impl/JdbcAccountRepository.java | 35 ++++++ .../data/impl/JdbcTransactionRepository.java | 9 ++ .../perfin/view/component/AccountTile.java | 2 +- .../perfin/view/component/StyledText.java | 10 +- .../view/component/module/AccountsModule.java | 101 ++++++++++++++++++ .../component/module/DashboardModule.java | 36 +++++++ .../view/component/module/ModuleHeader.java | 25 +++++ .../module/RecentTransactionsModule.java | 99 +++++++++++++++++ src/main/resources/dashboard.fxml | 13 +++ src/main/resources/main-view.fxml | 3 +- 16 files changed, 378 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/andrewlalis/perfin/control/DashboardController.java create mode 100644 src/main/java/com/andrewlalis/perfin/view/component/module/AccountsModule.java create mode 100644 src/main/java/com/andrewlalis/perfin/view/component/module/DashboardModule.java create mode 100644 src/main/java/com/andrewlalis/perfin/view/component/module/ModuleHeader.java create mode 100644 src/main/java/com/andrewlalis/perfin/view/component/module/RecentTransactionsModule.java create mode 100644 src/main/resources/dashboard.fxml diff --git a/src/main/java/com/andrewlalis/perfin/PerfinApp.java b/src/main/java/com/andrewlalis/perfin/PerfinApp.java index 0ec8083..46d3d98 100644 --- a/src/main/java/com/andrewlalis/perfin/PerfinApp.java +++ b/src/main/java/com/andrewlalis/perfin/PerfinApp.java @@ -84,6 +84,7 @@ public class PerfinApp extends Application { msgConsumer.accept("Initializing application views."); Platform.runLater(() -> { // App pages. + router.map("dashboard", PerfinApp.class.getResource("/dashboard.fxml")); router.map("accounts", PerfinApp.class.getResource("/accounts-view.fxml")); router.map("account", PerfinApp.class.getResource("/account-view.fxml")); router.map("edit-account", PerfinApp.class.getResource("/edit-account.fxml")); diff --git a/src/main/java/com/andrewlalis/perfin/control/DashboardController.java b/src/main/java/com/andrewlalis/perfin/control/DashboardController.java new file mode 100644 index 0000000..4c1eb6d --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/control/DashboardController.java @@ -0,0 +1,39 @@ +package com.andrewlalis.perfin.control; + +import com.andrewlalis.javafx_scene_router.RouteSelectionListener; +import com.andrewlalis.perfin.view.component.module.AccountsModule; +import com.andrewlalis.perfin.view.component.module.DashboardModule; +import com.andrewlalis.perfin.view.component.module.RecentTransactionsModule; +import javafx.fxml.FXML; +import javafx.geometry.Bounds; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; + +public class DashboardController implements RouteSelectionListener { + @FXML public ScrollPane modulesScrollPane; + @FXML public FlowPane modulesFlowPane; + + private DashboardModule accountsModule; + private DashboardModule transactionsModule; + + @FXML public void initialize() { + var viewportWidth = modulesScrollPane.viewportBoundsProperty().map(Bounds::getWidth); + modulesFlowPane.minWidthProperty().bind(viewportWidth); + modulesFlowPane.prefWidthProperty().bind(viewportWidth); + modulesFlowPane.maxWidthProperty().bind(viewportWidth); + + accountsModule = new AccountsModule(modulesFlowPane); + accountsModule.columnsProperty.set(2); + + transactionsModule = new RecentTransactionsModule(modulesFlowPane); + transactionsModule.columnsProperty.set(2); + + modulesFlowPane.getChildren().addAll(accountsModule, transactionsModule); + } + + @Override + public void onRouteSelected(Object context) { + accountsModule.refreshContents(); + transactionsModule.refreshContents(); + } +} diff --git a/src/main/java/com/andrewlalis/perfin/control/MainViewController.java b/src/main/java/com/andrewlalis/perfin/control/MainViewController.java index 9e8e2fc..995d70f 100644 --- a/src/main/java/com/andrewlalis/perfin/control/MainViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/MainViewController.java @@ -40,7 +40,7 @@ public class MainViewController { } ); - router.navigate("accounts"); + router.navigate("dashboard"); // Initialize the help manual components. helpPane.managedProperty().bind(helpPane.visibleProperty()); @@ -75,14 +75,6 @@ public class MainViewController { router.navigateForward(); } - @FXML public void goToAccounts() { - router.replace("accounts"); - } - - @FXML public void goToTransactions() { - router.replace("transactions"); - } - @FXML public void viewProfiles() { ProfilesStage.open(mainContainer.getScene().getWindow()); } @@ -106,4 +98,8 @@ public class MainViewController { @FXML public void helpViewTransactions() { helpRouter.replace("transactions"); } + + @FXML public void goToDashboard() { + router.replace("dashboard"); + } } diff --git a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java index 0304a94..842fde1 100644 --- a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java @@ -108,7 +108,7 @@ public class ProfilesViewController { Profile.setCurrent(PerfinApp.profileLoader.load(name)); ProfileLoader.saveLastProfile(name); ProfilesStage.closeView(); - router.replace("accounts"); + router.replace("dashboard"); if (showPopup) Popups.message(profilesVBox, "The profile \"" + name + "\" has been loaded."); return true; } catch (ProfileLoadException e) { diff --git a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java index b8f97ff..0c5352c 100644 --- a/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/AccountRepository.java @@ -17,6 +17,8 @@ public interface AccountRepository extends Repository, AutoCloseable { long insert(AccountType type, String accountNumber, String name, Currency currency); Page findAll(PageRequest pagination); List findAllOrderedByRecentHistory(); + List findTopNOrderedByRecentHistory(int n); + List findTopNRecentlyActive(int n, int daysSinceLastActive); List findAllByCurrency(Currency currency); Optional findById(long id); void updateName(long id, String name); diff --git a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java index e5d845c..1275202 100644 --- a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java @@ -28,6 +28,7 @@ public interface TransactionRepository extends Repository, AutoCloseable { ); Optional findById(long id); Page findAll(PageRequest pagination); + List findRecentN(int n); long countAll(); long countAllAfter(long transactionId); long countAllByAccounts(Set accountIds); 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 73faf1d..786dad8 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcAccountRepository.java @@ -3,6 +3,7 @@ package com.andrewlalis.perfin.data.impl; import com.andrewlalis.perfin.data.*; import com.andrewlalis.perfin.data.pagination.Page; import com.andrewlalis.perfin.data.pagination.PageRequest; +import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.data.util.DbUtil; import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.AccountEntry; @@ -66,6 +67,40 @@ public record JdbcAccountRepository(Connection conn, Path contentDir) implements ); } + @Override + public List findTopNOrderedByRecentHistory(int n) { + return DbUtil.findAll( + conn, + """ + SELECT DISTINCT ON (account.id) account.*, hi.timestamp AS _ + FROM account + LEFT OUTER JOIN history_account ha ON ha.account_id = account.id + LEFT OUTER JOIN history_item hi ON hi.history_id = ha.history_id + WHERE NOT account.archived + ORDER BY hi.timestamp DESC, account.created_at DESC + LIMIT\s""" + n, + JdbcAccountRepository::parseAccount + ); + } + + @Override + public List findTopNRecentlyActive(int n, int daysSinceLastActive) { + LocalDateTime cutoff = DateUtil.nowAsUTC().minusDays(daysSinceLastActive); + return DbUtil.findAll( + conn, + """ + SELECT DISTINCT ON (account.id) account.*, hi.timestamp AS _ + FROM account + LEFT OUTER JOIN history_account ha ON ha.account_id = account.id + LEFT OUTER JOIN history_item hi ON hi.history_id = ha.history_id + WHERE NOT account.archived AND hi.timestamp >= ? + ORDER BY hi.timestamp DESC, account.created_at DESC + LIMIT\s""" + n, + List.of(DbUtil.timestampFromUtcLDT(cutoff)), + JdbcAccountRepository::parseAccount + ); + } + @Override public List findAllByCurrency(Currency currency) { return DbUtil.findAll( diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java index edd8bb1..097970a 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java @@ -142,6 +142,15 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem ); } + @Override + public List findRecentN(int n) { + return DbUtil.findAll( + conn, + "SELECT * FROM transaction ORDER BY timestamp DESC LIMIT " + n, + JdbcTransactionRepository::parseTransaction + ); + } + @Override public long countAll() { return DbUtil.findOne(conn, "SELECT COUNT(id) FROM transaction", Collections.emptyList(), rs -> rs.getLong(1)).orElse(0L); diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java index 1787078..ec6ab1d 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountTile.java @@ -25,7 +25,7 @@ import static com.andrewlalis.perfin.PerfinApp.router; * A compact tile that displays information about an account. */ public class AccountTile extends BorderPane { - private static final Map ACCOUNT_TYPE_COLORS = Map.of( + public static final Map ACCOUNT_TYPE_COLORS = Map.of( AccountType.CHECKING, "-fx-theme-account-type-checking", AccountType.SAVINGS, "-fx-theme-account-type-savings", AccountType.CREDIT_CARD, "-fx-theme-account-type-credit-card" diff --git a/src/main/java/com/andrewlalis/perfin/view/component/StyledText.java b/src/main/java/com/andrewlalis/perfin/view/component/StyledText.java index b6fdfd8..cbba7fb 100644 --- a/src/main/java/com/andrewlalis/perfin/view/component/StyledText.java +++ b/src/main/java/com/andrewlalis/perfin/view/component/StyledText.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.List; import static com.andrewlalis.perfin.PerfinApp.helpRouter; +import static com.andrewlalis.perfin.PerfinApp.router; /** * A component that renders markdown-ish text as a series of TextFlow elements, @@ -26,9 +27,15 @@ public class StyledText extends VBox { private StringProperty text; private boolean initialized = false; + public StyledText() { + getStyleClass().add("spacing-extra"); + } + public final void setText(String value) { + initialized = false; if (value == null) value = ""; textProperty().set(value); + layoutChildren(); // Re-render the underlying text. } public final String getText() { @@ -54,7 +61,6 @@ public class StyledText extends VBox { String s = getText(); getChildren().clear(); getChildren().addAll(renderText(s)); - getStyleClass().add("spacing-extra"); initialized = true; } super.layoutChildren(); @@ -135,6 +141,8 @@ public class StyledText extends VBox { hyperlink.setOnAction(event -> PerfinApp.instance.getHostServices().showDocument(link)); } else if (link.startsWith("help:")) { hyperlink.setOnAction(event -> helpRouter.navigate(link.substring(5).strip())); + } else if (link.startsWith("app:")) { + hyperlink.setOnAction(event -> router.navigate(link.substring(4).strip())); } } hyperlink.setBorder(Border.EMPTY); diff --git a/src/main/java/com/andrewlalis/perfin/view/component/module/AccountsModule.java b/src/main/java/com/andrewlalis/perfin/view/component/module/AccountsModule.java new file mode 100644 index 0000000..ecf5142 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/view/component/module/AccountsModule.java @@ -0,0 +1,101 @@ +package com.andrewlalis.perfin.view.component.module; + +import com.andrewlalis.perfin.data.AccountRepository; +import com.andrewlalis.perfin.data.util.CurrencyUtil; +import com.andrewlalis.perfin.model.Account; +import com.andrewlalis.perfin.model.MoneyValue; +import com.andrewlalis.perfin.model.Profile; +import com.andrewlalis.perfin.view.component.AccountTile; +import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import java.math.BigDecimal; + +import static com.andrewlalis.perfin.PerfinApp.router; + +/** + * A module that displays a basic overview of recent accounts. + */ +public class AccountsModule extends DashboardModule { + private final VBox accountsVBox = new VBox(); + + public AccountsModule(Pane parent) { + super(parent); + accountsVBox.getStyleClass().add("tile-container"); + ScrollPane scrollPane = new ScrollPane(accountsVBox); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + + Button addAccountButton = new Button("Add Account"); + addAccountButton.setOnAction(event -> router.navigate("edit-account", null)); + Button viewAllAccountsButton = new Button("All Accounts"); + viewAllAccountsButton.setOnAction(event -> router.navigate("accounts")); + Button refreshButton = new Button("Refresh"); + refreshButton.setOnAction(event -> refreshContents()); + + this.getChildren().add(new ModuleHeader( + "Recently Active Accounts", + addAccountButton, + viewAllAccountsButton, + refreshButton + )); + this.getChildren().add(scrollPane); + } + + @Override + public void refreshContents() { + Profile.getCurrent().dataSource().mapRepoAsync( + AccountRepository.class, + AccountRepository::findAllOrderedByRecentHistory + ) + .thenApply(accounts -> accounts.stream().map(AccountsModule::buildMiniAccountTile).toList()) + .thenAccept(nodes -> Platform.runLater(() -> { + accountsVBox.getChildren().clear(); + accountsVBox.getChildren().addAll(nodes); + })); + } + + private static Node buildMiniAccountTile(Account account) { + BorderPane borderPane = new BorderPane(); + borderPane.getStyleClass().addAll("tile", "hand-cursor"); + borderPane.setOnMouseClicked(event -> router.navigate("account", account)); + + Label nameLabel = new Label(account.getName()); + nameLabel.getStyleClass().addAll("bold-text"); + Label numberLabel = new Label(account.getAccountNumber()); + numberLabel.getStyleClass().addAll("mono-font"); + Label typeLabel = new Label(account.getType().toString()); + typeLabel.getStyleClass().add("bold-text"); + typeLabel.setStyle("-fx-text-fill: " + AccountTile.ACCOUNT_TYPE_COLORS.get(account.getType())); + Label balanceLabel = new Label("Computing balance..."); + balanceLabel.getStyleClass().addAll("mono-font"); + balanceLabel.setDisable(true); + + Profile.getCurrent().dataSource().mapRepoAsync( + AccountRepository.class, + repo -> repo.deriveCurrentBalance(account.id) + ).thenAccept(bal -> Platform.runLater(() -> { + String text = CurrencyUtil.formatMoneyWithCurrencyPrefix(new MoneyValue(bal, account.getCurrency())); + balanceLabel.setText(text); + if (account.getType().areDebitsPositive() && bal.compareTo(BigDecimal.ZERO) < 0) { + balanceLabel.getStyleClass().add("negative-color-text-fill"); + } else if (!account.getType().areDebitsPositive() && bal.compareTo(BigDecimal.ZERO) < 0) { + balanceLabel.getStyleClass().add("positive-color-text-fill"); + } + balanceLabel.setDisable(false); + })); + + VBox contentBox = new VBox(nameLabel, numberLabel, typeLabel); + borderPane.setCenter(contentBox); + borderPane.setRight(balanceLabel); + return borderPane; + } +} diff --git a/src/main/java/com/andrewlalis/perfin/view/component/module/DashboardModule.java b/src/main/java/com/andrewlalis/perfin/view/component/module/DashboardModule.java new file mode 100644 index 0000000..b47c187 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/view/component/module/DashboardModule.java @@ -0,0 +1,36 @@ +package com.andrewlalis.perfin.view.component.module; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; + +/** + * A container intended to display content on the app's dashboard, where modules + * are arranged in a flexible grid based on some constraints. + */ +public class DashboardModule extends VBox { + public final IntegerProperty columnsProperty = new SimpleIntegerProperty(1); + public final DoubleProperty minColumnWidthProperty = new SimpleDoubleProperty(500); + public final DoubleProperty rowHeightProperty = new SimpleDoubleProperty(500); + + public DashboardModule(Pane parent) { + ObservableValue dynamicWidth = parent.widthProperty().map(parentWidth -> { + double parentWidthD = parentWidth.doubleValue(); + if (parentWidthD < minColumnWidthProperty.doubleValue() * columnsProperty.doubleValue()) return parentWidthD; + return Math.floor(parentWidthD / columnsProperty.doubleValue()); + }); + this.minWidthProperty().bind(dynamicWidth); + this.prefWidthProperty().bind(dynamicWidth); + this.maxWidthProperty().bind(dynamicWidth); + + this.minHeightProperty().bind(rowHeightProperty); + this.prefHeightProperty().bind(rowHeightProperty); + this.maxHeightProperty().bind(rowHeightProperty); + } + + public void refreshContents() {} +} diff --git a/src/main/java/com/andrewlalis/perfin/view/component/module/ModuleHeader.java b/src/main/java/com/andrewlalis/perfin/view/component/module/ModuleHeader.java new file mode 100644 index 0000000..b429c61 --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/view/component/module/ModuleHeader.java @@ -0,0 +1,25 @@ +package com.andrewlalis.perfin.view.component.module; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; + +/** + * A standardized header format for dashboard modules, which includes a left-aligned + * title and right-aligned list of action items (usually buttons). + */ +public class ModuleHeader extends BorderPane { + public ModuleHeader(String title, Node... actionItems) { + this.getStyleClass().addAll("std-padding"); + + Label titleLabel = new Label(title); + titleLabel.getStyleClass().addAll("bold-text", "large-font"); + this.setLeft(titleLabel); + + HBox actionsHBox = new HBox(); + actionsHBox.getStyleClass().addAll("std-spacing", "small-font"); + actionsHBox.getChildren().addAll(actionItems); + this.setRight(actionsHBox); + } +} diff --git a/src/main/java/com/andrewlalis/perfin/view/component/module/RecentTransactionsModule.java b/src/main/java/com/andrewlalis/perfin/view/component/module/RecentTransactionsModule.java new file mode 100644 index 0000000..a846c8d --- /dev/null +++ b/src/main/java/com/andrewlalis/perfin/view/component/module/RecentTransactionsModule.java @@ -0,0 +1,99 @@ +package com.andrewlalis.perfin.view.component.module; + +import com.andrewlalis.perfin.control.TransactionsViewController; +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.Profile; +import com.andrewlalis.perfin.model.Transaction; +import com.andrewlalis.perfin.view.BindingUtil; +import com.andrewlalis.perfin.view.component.StyledText; +import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import static com.andrewlalis.perfin.PerfinApp.router; + +public class RecentTransactionsModule extends DashboardModule { + private final VBox transactionsVBox = new VBox(); + + public RecentTransactionsModule(Pane parent) { + super(parent); + transactionsVBox.getStyleClass().add("tile-container"); + ScrollPane scrollPane = new ScrollPane(transactionsVBox); + scrollPane.setFitToHeight(true); + scrollPane.setFitToWidth(true); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + + Button addTransactionButton = new Button("Add Transaction"); + addTransactionButton.setOnAction(event -> router.navigate("edit-transaction")); + Button viewTransactionsButton = new Button("All Transactions"); + viewTransactionsButton.setOnAction(event -> router.navigate("transactions")); + Button refreshButton = new Button("Refresh"); + refreshButton.setOnAction(event -> refreshContents()); + + this.getChildren().add(new ModuleHeader( + "Recent Transactions", + addTransactionButton, + viewTransactionsButton, + refreshButton + )); + this.getChildren().add(scrollPane); + } + + @Override + public void refreshContents() { + Profile.getCurrent().dataSource().mapRepoAsync( + TransactionRepository.class, + repo -> repo.findRecentN(5) + ) + .thenApply(transactions -> transactions.stream().map(RecentTransactionsModule::buildMiniTransactionTile).toList()) + .thenAccept(nodes -> Platform.runLater(() -> { + transactionsVBox.getChildren().clear(); + transactionsVBox.getChildren().addAll(nodes); + })); + } + + private static Node buildMiniTransactionTile(Transaction tx) { + BorderPane borderPane = new BorderPane(); + borderPane.getStyleClass().addAll("tile", "hand-cursor"); + borderPane.setOnMouseClicked(event -> router.navigate("transactions", new TransactionsViewController.RouteContext(tx.id))); + + Label dateLabel = new Label(DateUtil.formatUTCAsLocalWithZone(tx.getTimestamp())); + dateLabel.getStyleClass().addAll("mono-font", "small-font", "secondary-color-text-fill"); + StyledText linkedAccountsLabel = new StyledText(); + linkedAccountsLabel.getStyleClass().addAll("small-font"); + Profile.getCurrent().dataSource().mapRepoAsync( + TransactionRepository.class, + repo -> repo.findLinkedAccounts(tx.id) + ) + .thenAccept(accounts -> Platform.runLater(() -> { + StringBuilder sb = new StringBuilder(); + if (accounts.hasCredit()) { + sb.append("Credited from **").append(accounts.creditAccount().getName()).append("**"); + if (accounts.hasDebit()) sb.append(". "); + } + if (accounts.hasDebit()) { + sb.append("Debited to **").append(accounts.debitAccount().getName()).append("**"); + } + linkedAccountsLabel.setText(sb.toString()); + })); + Label descriptionLabel = new Label(tx.getDescription()); + BindingUtil.bindManagedAndVisible(descriptionLabel, descriptionLabel.textProperty().isNotEmpty()); + + Label balanceLabel = new Label(CurrencyUtil.formatMoneyWithCurrencyPrefix(tx.getMoneyAmount())); + balanceLabel.getStyleClass().addAll("mono-font"); + + VBox contentBox = new VBox(dateLabel, descriptionLabel, linkedAccountsLabel); + borderPane.setCenter(contentBox); + borderPane.setRight(balanceLabel); + + return borderPane; + } +} diff --git a/src/main/resources/dashboard.fxml b/src/main/resources/dashboard.fxml new file mode 100644 index 0000000..cf6b99d --- /dev/null +++ b/src/main/resources/dashboard.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/main/resources/main-view.fxml b/src/main/resources/main-view.fxml index d185ba0..3c30419 100644 --- a/src/main/resources/main-view.fxml +++ b/src/main/resources/main-view.fxml @@ -15,8 +15,7 @@