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.PredicateValidator;
import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
@ -39,6 +40,10 @@ import java.util.*;
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 {
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>()
.addTerminalPredicate(s -> s == null || s.length() <= 255, "Description is too long.")
).validatedInitially().attach(descriptionField, descriptionField.textProperty());
// Linked accounts will use a property derived from both the debit and credit selections.
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 linkedAccountsValid = initializeLinkedAccountsValidationUi();
initializeTagSelectionUi();
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
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() {
return new CreditAndDebitAccounts(
creditAccountSelector.getValue(),

View File

@ -60,13 +60,22 @@
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
</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"/>
<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"/>
<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">
<HBox styleClass="std-spacing">
<ComboBox fx:id="tagsComboBox" editable="true" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>