Added historical balance checker and cleaned up actions layout on account page.

This commit is contained in:
Andrew Lalis 2024-02-07 09:05:50 -05:00
parent 104de66a66
commit 6bafd06fc0
3 changed files with 90 additions and 41 deletions

View File

@ -2,17 +2,25 @@ package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener; import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.AccountRepository;
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.Account; import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.MoneyValue;
import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.component.AccountHistoryView; import com.andrewlalis.perfin.view.component.AccountHistoryView;
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression; import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.VBox; import javafx.scene.layout.HBox;
import java.time.*;
import static com.andrewlalis.perfin.PerfinApp.router; import static com.andrewlalis.perfin.PerfinApp.router;
@ -30,10 +38,13 @@ public class AccountViewController implements RouteSelectionListener {
@FXML public AccountHistoryView accountHistory; @FXML public AccountHistoryView accountHistory;
@FXML public VBox actionsVBox; @FXML public HBox actionsBox;
@FXML public DatePicker balanceCheckerDatePicker;
@FXML public Button balanceCheckerButton;
@FXML public void initialize() { @FXML public void initialize() {
actionsVBox.getChildren().forEach(node -> { actionsBox.getChildren().forEach(node -> {
Button button = (Button) node; Button button = (Button) node;
BooleanExpression buttonActive = accountArchivedProperty; BooleanExpression buttonActive = accountArchivedProperty;
if (button.getText().equalsIgnoreCase("Unarchive")) { if (button.getText().equalsIgnoreCase("Unarchive")) {
@ -43,6 +54,28 @@ public class AccountViewController implements RouteSelectionListener {
button.managedProperty().bind(button.visibleProperty()); button.managedProperty().bind(button.visibleProperty());
button.visibleProperty().bind(button.disableProperty().not()); button.visibleProperty().bind(button.disableProperty().not());
}); });
var datePickerValid = new ValidationApplier<>(new PredicateValidator<LocalDate>()
.addPredicate(date -> date.isBefore(LocalDate.now()), "Date must be in the past.")
).attach(balanceCheckerDatePicker, balanceCheckerDatePicker.valueProperty());
balanceCheckerButton.disableProperty().bind(datePickerValid.not());
balanceCheckerButton.setOnAction(event -> {
LocalDate date = balanceCheckerDatePicker.getValue();
final Instant timestamp = date.atStartOfDay(ZoneId.systemDefault())
.withZoneSameInstant(ZoneOffset.UTC)
.toInstant();
Profile.getCurrent().dataSource().mapRepoAsync(
AccountRepository.class,
repo -> repo.deriveBalance(account.id, timestamp)
).thenAccept(balance -> Platform.runLater(() -> {
String msg = String.format(
"Your balance as of %s is %s, according to Perfin's data.",
date,
CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency()))
);
Popups.message(balanceCheckerButton, msg);
}));
});
} }
@Override @Override

View File

@ -81,6 +81,7 @@ public class StyledText extends VBox {
idx = 0; idx = 0;
currentRun.setLength(0); currentRun.setLength(0);
currentParagraph = new TextFlow(); currentParagraph = new TextFlow();
currentParagraph.setStyle("-fx-text-fill: inherit;");
while (idx < text.length()) { while (idx < text.length()) {
if (text.startsWith("**", idx)) { if (text.startsWith("**", idx)) {
@ -118,6 +119,7 @@ public class StyledText extends VBox {
int endIdx = text.indexOf(marker, idx + marker.length()); int endIdx = text.indexOf(marker, idx + marker.length());
Text textItem = new Text(text.substring(idx + marker.length(), endIdx)); Text textItem = new Text(text.substring(idx + marker.length(), endIdx));
textItem.getStyleClass().add(styleClass); textItem.getStyleClass().add(styleClass);
textItem.setStyle("-fx-text-fill: inherit;");
currentParagraph.getChildren().add(textItem); currentParagraph.getChildren().add(textItem);
idx = endIdx + marker.length(); idx = endIdx + marker.length();
} }
@ -183,6 +185,7 @@ public class StyledText extends VBox {
if (!currentParagraph.getChildren().isEmpty()) { if (!currentParagraph.getChildren().isEmpty()) {
flows.add(currentParagraph); flows.add(currentParagraph);
currentParagraph = new TextFlow(); currentParagraph = new TextFlow();
currentParagraph.setStyle("-fx-text-fill: inherit;");
} }
} }
} }

View File

@ -5,6 +5,8 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.text.Text?> <?import javafx.scene.text.Text?>
<?import com.andrewlalis.perfin.view.component.StyledText?>
<?import javafx.scene.text.TextFlow?>
<BorderPane <BorderPane
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
@ -16,49 +18,60 @@
<center> <center>
<VBox> <VBox>
<!-- Main account properties and actions --> <!-- Main account properties and actions -->
<BorderPane> <FlowPane styleClass="std-padding,std-spacing">
<center> <!-- Main account properties. -->
<VBox styleClass="std-padding,std-spacing"> <PropertiesPane vgap="5" hgap="5">
<FlowPane> <Label text="Name" styleClass="bold-text"/>
<!-- Main account properties. --> <Label fx:id="accountNameLabel"/>
<PropertiesPane vgap="5" hgap="5">
<Label text="Name" styleClass="bold-text"/>
<Label fx:id="accountNameLabel"/>
<Label text="Number" styleClass="bold-text"/> <Label text="Number" styleClass="bold-text"/>
<Label fx:id="accountNumberLabel" styleClass="mono-font"/> <Label fx:id="accountNumberLabel" styleClass="mono-font"/>
<Label text="Currency" styleClass="bold-text"/> <Label text="Currency" styleClass="bold-text"/>
<Label fx:id="accountCurrencyLabel"/> <Label fx:id="accountCurrencyLabel"/>
<Label text="Created At" styleClass="bold-text"/> <Label text="Created At" styleClass="bold-text"/>
<Label fx:id="accountCreatedAtLabel" styleClass="mono-font"/> <Label fx:id="accountCreatedAtLabel" styleClass="mono-font"/>
<VBox> <VBox>
<Label text="Current Balance" styleClass="bold-text" fx:id="balanceLabel"/> <Label text="Current Balance" styleClass="bold-text" fx:id="balanceLabel"/>
<Text <Text
styleClass="small-font,secondary-color-fill" styleClass="small-font,secondary-color-fill"
wrappingWidth="${balanceLabel.width}" wrappingWidth="${balanceLabel.width}"
>Computed using the last recorded balance and all transactions since.</Text> >Computed using the last recorded balance and all transactions since.</Text>
</VBox>
<Label fx:id="accountBalanceLabel" styleClass="mono-font"/>
</PropertiesPane>
</FlowPane>
</VBox> </VBox>
</center> <Label fx:id="accountBalanceLabel" styleClass="mono-font"/>
<right> </PropertiesPane>
<VBox styleClass="std-padding,std-spacing"> </FlowPane>
<Label text="Actions" styleClass="bold-text"/>
<VBox fx:id="actionsVBox" styleClass="std-spacing"> <!-- Action buttons -->
<Button text="Edit" onAction="#goToEditPage" maxWidth="Infinity"/> <HBox fx:id="actionsBox" styleClass="std-padding,std-spacing,small-font">
<Button text="Record Balance" onAction="#goToCreateBalanceRecord" maxWidth="Infinity"/> <Button text="Edit" onAction="#goToEditPage"/>
<Button text="Archive" onAction="#archiveAccount" maxWidth="Infinity"/> <Button text="Record Balance" onAction="#goToCreateBalanceRecord"/>
<Button text="Delete" onAction="#deleteAccount" maxWidth="Infinity"/> <Button text="Archive" onAction="#archiveAccount"/>
<Button text="Unarchive" onAction="#unarchiveAccount" maxWidth="Infinity"/> <Button text="Delete" onAction="#deleteAccount"/>
</VBox> <Button text="Unarchive" onAction="#unarchiveAccount"/>
</VBox> </HBox>
</right>
</BorderPane> <!-- Historical Balance Checker -->
<VBox styleClass="std-padding,std-spacing">
<HBox styleClass="std-spacing">
<AnchorPane>
<Label text="Check Historical Balance" styleClass="bold-text" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
</AnchorPane>
<DatePicker fx:id="balanceCheckerDatePicker"/>
<Button text="Check" fx:id="balanceCheckerButton"/>
</HBox>
<TextFlow maxWidth="500">
<Text styleClass="small-font,secondary-color-fill">
Use this tool to check the balance of this account on any
given day in the past. Perfin will compute the balance
according to any balance records and transactions it finds
around that time.
</Text>
</TextFlow>
</VBox>
<!-- Account history --> <!-- Account history -->
<VBox VBox.vgrow="ALWAYS"> <VBox VBox.vgrow="ALWAYS">