Added start of transaction edit page.

This commit is contained in:
Andrew Lalis 2024-01-11 08:46:57 -05:00
parent 89d7438ab1
commit 7ceaca7068
8 changed files with 70 additions and 24 deletions

View File

@ -84,7 +84,7 @@ public class PerfinApp extends Application {
router.map("account", PerfinApp.class.getResource("/account-view.fxml")); router.map("account", PerfinApp.class.getResource("/account-view.fxml"));
router.map("edit-account", PerfinApp.class.getResource("/edit-account.fxml")); router.map("edit-account", PerfinApp.class.getResource("/edit-account.fxml"));
router.map("transactions", PerfinApp.class.getResource("/transactions-view.fxml")); router.map("transactions", PerfinApp.class.getResource("/transactions-view.fxml"));
router.map("create-transaction", PerfinApp.class.getResource("/create-transaction.fxml")); router.map("edit-transaction", PerfinApp.class.getResource("/edit-transaction.fxml"));
router.map("create-balance-record", PerfinApp.class.getResource("/create-balance-record.fxml")); router.map("create-balance-record", PerfinApp.class.getResource("/create-balance-record.fxml"));
router.map("balance-record", PerfinApp.class.getResource("/balance-record-view.fxml")); router.map("balance-record", PerfinApp.class.getResource("/balance-record-view.fxml"));

View File

@ -1,11 +1,13 @@
package com.andrewlalis.perfin.control; package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener; import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
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.data.util.FileUtil; import com.andrewlalis.perfin.data.util.FileUtil;
import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.CreditAndDebitAccounts; import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.model.Transaction;
import com.andrewlalis.perfin.view.AccountComboBoxCellFactory; import com.andrewlalis.perfin.view.AccountComboBoxCellFactory;
import com.andrewlalis.perfin.view.component.FileSelectionArea; import com.andrewlalis.perfin.view.component.FileSelectionArea;
import com.andrewlalis.perfin.view.component.validation.ValidationApplier; import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
@ -31,7 +33,9 @@ import java.util.List;
import static com.andrewlalis.perfin.PerfinApp.router; import static com.andrewlalis.perfin.PerfinApp.router;
public class CreateTransactionController implements RouteSelectionListener { public class EditTransactionController implements RouteSelectionListener {
@FXML public Label titleLabel;
@FXML public TextField timestampField; @FXML public TextField timestampField;
@FXML public TextField amountField; @FXML public TextField amountField;
@FXML public ChoiceBox<Currency> currencyChoiceBox; @FXML public ChoiceBox<Currency> currencyChoiceBox;
@ -55,15 +59,13 @@ public class CreateTransactionController implements RouteSelectionListener {
return ts != null && ts.isBefore(LocalDateTime.now()); return ts != null && ts.isBefore(LocalDateTime.now());
}, "Timestamp cannot be in the future.") }, "Timestamp cannot be in the future.")
).validatedInitially().attachToTextField(timestampField); ).validatedInitially().attachToTextField(timestampField);
var amountValid = new ValidationApplier<>( var amountValid = new ValidationApplier<>(
new CurrencyAmountValidator(() -> currencyChoiceBox.getValue(), false, false) new CurrencyAmountValidator(() -> currencyChoiceBox.getValue(), false, false)
).validatedInitially().attachToTextField(amountField, currencyChoiceBox.valueProperty()); ).validatedInitially().attachToTextField(amountField, currencyChoiceBox.valueProperty());
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());
// Linked accounts will use a property derived from both the debit and credit selections.
Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts()); Property<CreditAndDebitAccounts> linkedAccountsProperty = new SimpleObjectProperty<>(getSelectedAccounts());
linkDebitAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts())); linkDebitAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
linkCreditAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts())); linkCreditAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> linkedAccountsProperty.setValue(getSelectedAccounts()));
@ -85,6 +87,7 @@ public class CreateTransactionController implements RouteSelectionListener {
linkCreditAccountComboBox.setCellFactory(cellFactory); linkCreditAccountComboBox.setCellFactory(cellFactory);
linkCreditAccountComboBox.setButtonCell(cellFactory.call(null)); linkCreditAccountComboBox.setButtonCell(cellFactory.call(null));
currencyChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> { currencyChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Set currency to " + newValue);
updateLinkAccountComboBoxes(newValue); updateLinkAccountComboBoxes(newValue);
}); });
@ -123,25 +126,49 @@ public class CreateTransactionController implements RouteSelectionListener {
@Override @Override
public void onRouteSelected(Object context) { public void onRouteSelected(Object context) {
resetForm(); Transaction transaction = (Transaction) context;
} boolean creatingNew = transaction == null;
private void resetForm() { if (creatingNew) {
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT)); titleLabel.setText("Create New Transaction");
amountField.setText(null); timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
descriptionField.setText(null); amountField.setText(null);
attachmentsSelectionArea.clear(); currencyChoiceBox.getSelectionModel().selectFirst();
Thread.ofVirtual().start(() -> { descriptionField.setText(null);
Profile.getCurrent().getDataSource().useAccountRepository(repo -> { attachmentsSelectionArea.clear();
var currencies = repo.findAllUsedCurrencies().stream()
.sorted(Comparator.comparing(Currency::getCurrencyCode)) } else {
.toList(); titleLabel.setText("Edit Transaction #" + transaction.id);
timestampField.setText(DateUtil.formatUTCAsLocal(transaction.getTimestamp()));
amountField.setText(CurrencyUtil.formatMoneyAsBasicNumber(transaction.getMoneyAmount()));
currencyChoiceBox.setValue(transaction.getCurrency());
descriptionField.setText(transaction.getDescription());
// TODO: Add an editable list of attachments from which some can be added and removed.
Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.id);
Platform.runLater(() -> { Platform.runLater(() -> {
currencyChoiceBox.getItems().setAll(currencies); System.out.println(linkCreditAccountComboBox.getItems().indexOf(accounts.creditAccount()));
currencyChoiceBox.getSelectionModel().selectFirst(); // linkCreditAccountComboBox.getSelectionModel().select(accounts.creditAccount());
// linkCreditAccountComboBox.getButtonCell().updateIndex(linkCreditAccountComboBox.getSelectionModel().getSelectedIndex());
// linkDebitAccountComboBox.getSelectionModel().select(accounts.debitAccount());
// linkDebitAccountComboBox.getButtonCell().updateIndex(linkDebitAccountComboBox.getSelectionModel().getSelectedIndex());
}); });
}));
}
Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
var currencies = repo.findAllUsedCurrencies().stream()
.sorted(Comparator.comparing(Currency::getCurrencyCode))
.toList();
Platform.runLater(() -> {
currencyChoiceBox.getItems().setAll(currencies);
if (creatingNew) {
currencyChoiceBox.getSelectionModel().selectFirst();
} else {
currencyChoiceBox.getSelectionModel().select(transaction.getCurrency());
}
}); });
}); }));
} }
private CreditAndDebitAccounts getSelectedAccounts() { private CreditAndDebitAccounts getSelectedAccounts() {
@ -186,6 +213,7 @@ public class CreateTransactionController implements RouteSelectionListener {
linkCreditAccountComboBox.getItems().addAll(availableAccounts); linkCreditAccountComboBox.getItems().addAll(availableAccounts);
linkCreditAccountComboBox.getSelectionModel().selectLast(); linkCreditAccountComboBox.getSelectionModel().selectLast();
linkCreditAccountComboBox.getButtonCell().updateIndex(availableAccounts.size() - 1); linkCreditAccountComboBox.getButtonCell().updateIndex(availableAccounts.size() - 1);
System.out.println("link account boxes updated.");
}); });
}); });
}); });

View File

@ -69,6 +69,10 @@ public class TransactionViewController {
}); });
} }
@FXML public void editTransaction() {
router.navigate("edit-transaction", this.transaction);
}
@FXML public void deleteTransaction() { @FXML public void deleteTransaction() {
boolean confirm = Popups.confirm( boolean confirm = Popups.confirm(
"Are you sure you want to delete this transaction? This will " + "Are you sure you want to delete this transaction? This will " +

View File

@ -158,7 +158,7 @@ public class TransactionsViewController implements RouteSelectionListener {
} }
@FXML public void addTransaction() { @FXML public void addTransaction() {
router.navigate("create-transaction"); router.navigate("edit-transaction");
} }
@FXML public void exportTransactions() { @FXML public void exportTransactions() {

View File

@ -18,6 +18,12 @@ public class DateUtil {
.format(DEFAULT_DATETIME_FORMAT_WITH_ZONE); .format(DEFAULT_DATETIME_FORMAT_WITH_ZONE);
} }
public static String formatUTCAsLocal(LocalDateTime utcTimestamp) {
return utcTimestamp.atOffset(ZoneOffset.UTC)
.atZoneSameInstant(ZoneId.systemDefault())
.format(DEFAULT_DATETIME_FORMAT);
}
public static LocalDateTime localToUTC(LocalDateTime localTime, ZoneId localZone) { public static LocalDateTime localToUTC(LocalDateTime localTime, ZoneId localZone) {
return localTime.atZone(localZone).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime(); return localTime.atZone(localZone).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
} }

View File

@ -61,6 +61,10 @@ public class FileSelectionArea extends VBox {
selectedFiles.clear(); selectedFiles.clear();
} }
public void setSelectedFiles(List<Path> files) {
selectedFiles.setAll(files);
}
private Node buildFileItem(Path path) { private Node buildFileItem(Path path) {
Label filenameLabel = new Label(path.getFileName().toString()); Label filenameLabel = new Label(path.getFileName().toString());
filenameLabel.getStyleClass().addAll("mono-font"); filenameLabel.getStyleClass().addAll("mono-font");

View File

@ -5,8 +5,11 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx" <BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.andrewlalis.perfin.control.CreateTransactionController" fx:controller="com.andrewlalis.perfin.control.EditTransactionController"
> >
<top>
<Label fx:id="titleLabel" styleClass="large-font,bold-text,std-padding"/>
</top>
<center> <center>
<ScrollPane fitToWidth="true" fitToHeight="true"> <ScrollPane fitToWidth="true" fitToHeight="true">
<VBox style="-fx-max-width: 500px;"> <VBox style="-fx-max-width: 500px;">

View File

@ -43,9 +43,10 @@
</TextFlow> </TextFlow>
</VBox> </VBox>
<AttachmentsViewPane fx:id="attachmentsViewPane"/> <AttachmentsViewPane fx:id="attachmentsViewPane"/>
<FlowPane styleClass="std-padding, std-spacing"> <HBox styleClass="std-padding,std-spacing" alignment="CENTER_LEFT">
<Button text="Edit" onAction="#editTransaction"/>
<Button text="Delete this transaction" onAction="#deleteTransaction"/> <Button text="Delete this transaction" onAction="#deleteTransaction"/>
</FlowPane> </HBox>
</VBox> </VBox>
</ScrollPane> </ScrollPane>
</center> </center>