Add Transaction Properties #15

Merged
andrewlalis merged 18 commits from transaction-properties into main 2024-02-04 04:31:04 +00:00
2 changed files with 61 additions and 43 deletions
Showing only changes of commit ae2713dbd0 - Show all commits

View File

@ -15,13 +15,14 @@ import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator; import com.andrewlalis.perfin.view.component.validation.validators.CurrencyAmountValidator;
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator; import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
@ -39,6 +40,10 @@ import java.util.*;
import static com.andrewlalis.perfin.PerfinApp.router; import static com.andrewlalis.perfin.PerfinApp.router;
/**
* Controller for the "edit-transaction" view, which is where the user can
* create or edit transactions.
*/
public class EditTransactionController implements RouteSelectionListener { public class EditTransactionController implements RouteSelectionListener {
private static final Logger log = LoggerFactory.getLogger(EditTransactionController.class); private static final Logger log = LoggerFactory.getLogger(EditTransactionController.class);
@ -82,45 +87,8 @@ public class EditTransactionController implements RouteSelectionListener {
var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>() var descriptionValid = new ValidationApplier<>(new PredicateValidator<String>()
.addTerminalPredicate(s -> s == null || s.length() <= 255, "Description is too long.") .addTerminalPredicate(s -> s == null || s.length() <= 255, "Description is too long.")
).validatedInitially().attach(descriptionField, descriptionField.textProperty()); ).validatedInitially().attach(descriptionField, descriptionField.textProperty());
var linkedAccountsValid = initializeLinkedAccountsValidationUi();
// Linked accounts will use a property derived from both the debit and credit selections. initializeTagSelectionUi();
Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts());
debitAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
creditAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
var linkedAccountsValid = new ValidationApplier<>(getLinkedAccountsValidator())
.validatedInitially()
.attach(linkedAccountsContainer, linkedAccountsProperty);
// Set up the list of added tags.
addTagButton.disableProperty().bind(tagsComboBox.valueProperty().map(s -> s == null || s.isBlank()));
addTagButton.setOnAction(event -> {
if (tagsComboBox.getValue() == null) return;
String tag = tagsComboBox.getValue().strip();
if (!selectedTags.contains(tag)) {
selectedTags.add(tag);
selectedTags.sort(String::compareToIgnoreCase);
}
tagsComboBox.setValue(null);
});
tagsComboBox.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
addTagButton.fire();
}
});
BindingUtil.mapContent(tagsVBox.getChildren(), selectedTags, tag -> {
Label label = new Label(tag);
label.setMaxWidth(Double.POSITIVE_INFINITY);
label.getStyleClass().addAll("bold-text");
Button removeButton = new Button("Remove");
removeButton.setOnAction(event -> {
selectedTags.remove(tag);
});
BorderPane tile = new BorderPane(label);
tile.setRight(removeButton);
tile.getStyleClass().addAll("std-spacing");
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
return tile;
});
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid); var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
saveButton.disableProperty().bind(formValid.not()); saveButton.disableProperty().bind(formValid.not());
@ -279,6 +247,47 @@ public class EditTransactionController implements RouteSelectionListener {
}); });
} }
private BooleanExpression initializeLinkedAccountsValidationUi() {
Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts());
debitAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
creditAccountSelector.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
return new ValidationApplier<>(getLinkedAccountsValidator())
.validatedInitially()
.attach(linkedAccountsContainer, linkedAccountsProperty);
}
private void initializeTagSelectionUi() {
addTagButton.disableProperty().bind(tagsComboBox.valueProperty().map(s -> s == null || s.isBlank()));
addTagButton.setOnAction(event -> {
if (tagsComboBox.getValue() == null) return;
String tag = tagsComboBox.getValue().strip();
if (!selectedTags.contains(tag)) {
selectedTags.add(tag);
selectedTags.sort(String::compareToIgnoreCase);
}
tagsComboBox.setValue(null);
});
tagsComboBox.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
addTagButton.fire();
}
});
BindingUtil.mapContent(tagsVBox.getChildren(), selectedTags, this::createTagListTile);
}
private Node createTagListTile(String tag) {
Label label = new Label(tag);
label.setMaxWidth(Double.POSITIVE_INFINITY);
label.getStyleClass().addAll("bold-text");
Button removeButton = new Button("Remove");
removeButton.setOnAction(event -> selectedTags.remove(tag));
BorderPane tile = new BorderPane(label);
tile.setRight(removeButton);
tile.getStyleClass().addAll("std-spacing");
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
return tile;
}
private CreditAndDebitAccounts getSelectedAccounts() { private CreditAndDebitAccounts getSelectedAccounts() {
return new CreditAndDebitAccounts( return new CreditAndDebitAccounts(
creditAccountSelector.getValue(), creditAccountSelector.getValue(),

View File

@ -60,13 +60,22 @@
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/> <ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
</columnConstraints> </columnConstraints>
<Label text="Vendor" labelFor="${vendorComboBox}" styleClass="bold-text"/> <VBox>
<Label text="Vendor" labelFor="${vendorComboBox}" styleClass="bold-text"/>
<Hyperlink text="Manage vendors" styleClass="small-font"/>
</VBox>
<ComboBox fx:id="vendorComboBox" editable="true" maxWidth="Infinity"/> <ComboBox fx:id="vendorComboBox" editable="true" maxWidth="Infinity"/>
<Label text="Category" labelFor="${categoryComboBox}" styleClass="bold-text"/> <VBox>
<Label text="Category" labelFor="${categoryComboBox}" styleClass="bold-text"/>
<Hyperlink text="Manage categories" styleClass="small-font"/>
</VBox>
<ComboBox fx:id="categoryComboBox" editable="true" maxWidth="Infinity"/> <ComboBox fx:id="categoryComboBox" editable="true" maxWidth="Infinity"/>
<Label text="Tags" labelFor="${tagsComboBox}" styleClass="bold-text"/> <VBox>
<Label text="Tags" labelFor="${tagsComboBox}" styleClass="bold-text"/>
<Hyperlink text="Manage tags" styleClass="small-font"/>
</VBox>
<VBox maxWidth="Infinity"> <VBox maxWidth="Infinity">
<HBox styleClass="std-spacing"> <HBox styleClass="std-spacing">
<ComboBox fx:id="tagsComboBox" editable="true" HBox.hgrow="ALWAYS" maxWidth="Infinity"/> <ComboBox fx:id="tagsComboBox" editable="true" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>