Added editing, saving, and deleting accounts.
This commit is contained in:
parent
30df89d5b7
commit
2a47d93c97
2
pom.xml
2
pom.xml
|
@ -29,7 +29,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.andrewlalis</groupId>
|
<groupId>com.andrewlalis</groupId>
|
||||||
<artifactId>javafx-scene-router</artifactId>
|
<artifactId>javafx-scene-router</artifactId>
|
||||||
<version>1.4.0</version>
|
<version>1.5.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -2,12 +2,15 @@ package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
public class AccountViewController implements RouteSelectionListener {
|
public class AccountViewController implements RouteSelectionListener {
|
||||||
private Account account;
|
private Account account;
|
||||||
|
|
||||||
|
@ -32,4 +35,18 @@ public class AccountViewController implements RouteSelectionListener {
|
||||||
accountCurrencyField.setText(account.getCurrency().getDisplayName());
|
accountCurrencyField.setText(account.getCurrency().getDisplayName());
|
||||||
accountCreatedAtField.setText(account.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
accountCreatedAtField.setText(account.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void goToEditPage() {
|
||||||
|
router.navigate("edit-account", account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void deleteAccount() {
|
||||||
|
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||||
|
repo.delete(account);
|
||||||
|
});
|
||||||
|
router.getHistory().clear();
|
||||||
|
router.navigate("accounts");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,11 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
@FXML
|
@FXML
|
||||||
public ComboBox<Currency> accountCurrencyComboBox;
|
public ComboBox<Currency> accountCurrencyComboBox;
|
||||||
@FXML
|
@FXML
|
||||||
public ChoiceBox<String> accountTypeChoiceBox;
|
public ChoiceBox<AccountType> accountTypeChoiceBox;
|
||||||
|
|
||||||
|
private boolean editingNewAccount() {
|
||||||
|
return account == null;
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
@ -39,16 +43,16 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
}
|
}
|
||||||
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
||||||
|
|
||||||
accountTypeChoiceBox.getItems().add("Checking");
|
accountTypeChoiceBox.getItems().add(AccountType.CHECKING);
|
||||||
accountTypeChoiceBox.getItems().add("Savings");
|
accountTypeChoiceBox.getItems().add(AccountType.SAVINGS);
|
||||||
accountTypeChoiceBox.getItems().add("Credit Card");
|
accountTypeChoiceBox.getItems().add(AccountType.CREDIT_CARD);
|
||||||
accountTypeChoiceBox.getSelectionModel().select("Checking");
|
accountTypeChoiceBox.getSelectionModel().select(AccountType.CHECKING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRouteSelected(Object context) {
|
public void onRouteSelected(Object context) {
|
||||||
this.account = (Account) context;
|
this.account = (Account) context;
|
||||||
if (account == null) {
|
if (editingNewAccount()) {
|
||||||
titleLabel.setText("Editing New Account");
|
titleLabel.setText("Editing New Account");
|
||||||
} else {
|
} else {
|
||||||
titleLabel.setText("Editing Account: " + account.getName());
|
titleLabel.setText("Editing Account: " + account.getName());
|
||||||
|
@ -58,26 +62,38 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void save() {
|
public void save() {
|
||||||
if (account == null) {
|
try (var accountRepo = Profile.getCurrent().getDataSource().getAccountRepository()) {
|
||||||
// If we're editing a new account.
|
if (editingNewAccount()) {
|
||||||
String name = accountNameField.getText().strip();
|
String name = accountNameField.getText().strip();
|
||||||
String number = accountNumberField.getText().strip();
|
String number = accountNumberField.getText().strip();
|
||||||
AccountType type = AccountType.parse(accountTypeChoiceBox.getValue());
|
AccountType type = accountTypeChoiceBox.getValue();
|
||||||
Currency currency = accountCurrencyComboBox.getValue();
|
Currency currency = accountCurrencyComboBox.getValue();
|
||||||
Account newAccount = new Account(type, number, name, currency);
|
Account newAccount = new Account(type, number, name, currency);
|
||||||
Profile.getCurrent().getDataSource().getAccountRepository().insert(newAccount);
|
long id = accountRepo.insert(newAccount);
|
||||||
|
Account savedAccount = accountRepo.findById(id).orElseThrow();
|
||||||
|
|
||||||
// Once we create the new account, go to the account.
|
// Once we create the new account, go to the account.
|
||||||
router.getHistory().clear();
|
router.getHistory().clear();
|
||||||
router.navigate("accounts");
|
router.navigate("account", savedAccount);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Not implemented.");
|
System.out.println("Updating account " + account.getName());
|
||||||
|
account.setName(accountNameField.getText().strip());
|
||||||
|
account.setAccountNumber(accountNumberField.getText().strip());
|
||||||
|
account.setType(accountTypeChoiceBox.getValue());
|
||||||
|
account.setCurrency(accountCurrencyComboBox.getValue());
|
||||||
|
accountRepo.update(account);
|
||||||
|
Account updatedAccount = accountRepo.findById(account.getId()).orElseThrow();
|
||||||
|
router.getHistory().clear();
|
||||||
|
router.navigate("account", updatedAccount);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
router.navigateBack();
|
router.navigateBackAndClear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetForm() {
|
public void resetForm() {
|
||||||
|
@ -87,7 +103,10 @@ public class EditAccountController implements RouteSelectionListener {
|
||||||
accountTypeChoiceBox.getSelectionModel().selectFirst();
|
accountTypeChoiceBox.getSelectionModel().selectFirst();
|
||||||
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
accountCurrencyComboBox.getSelectionModel().select(Currency.getInstance("USD"));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Set to original account.
|
accountNameField.setText(account.getName());
|
||||||
|
accountNumberField.setText(account.getAccountNumber());
|
||||||
|
accountTypeChoiceBox.getSelectionModel().select(account.getType());
|
||||||
|
accountCurrencyComboBox.getSelectionModel().select(account.getCurrency());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ public class MainViewController {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
AnchorPaneRouterView routerView = (AnchorPaneRouterView) router.getView();
|
AnchorPaneRouterView routerView = (AnchorPaneRouterView) router.getView();
|
||||||
mainContainer.setCenter(routerView.getAnchorPane());
|
mainContainer.setCenter(routerView.getAnchorPane());
|
||||||
routerView.getAnchorPane().setStyle("-fx-border-color: orange;");
|
|
||||||
// Set up a simple breadcrumb display in the top bar.
|
// Set up a simple breadcrumb display in the top bar.
|
||||||
BindingUtil.mapContent(
|
BindingUtil.mapContent(
|
||||||
breadcrumbHBox.getChildren(),
|
breadcrumbHBox.getChildren(),
|
||||||
|
@ -40,6 +39,7 @@ public class MainViewController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void goToAccounts() {
|
public void goToAccounts() {
|
||||||
|
router.getHistory().clear();
|
||||||
router.navigate("accounts");
|
router.navigate("accounts");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@ import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface AccountRepository {
|
public interface AccountRepository extends AutoCloseable {
|
||||||
long insert(Account account);
|
long insert(Account account);
|
||||||
List<Account> findAll();
|
List<Account> findAll();
|
||||||
Optional<Account> findById(long id);
|
Optional<Account> findById(long id);
|
||||||
BigDecimal deriveCurrentBalance(long id);
|
BigDecimal deriveCurrentBalance(long id);
|
||||||
|
void update(Account account);
|
||||||
|
void delete(Account account);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,7 @@ package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
public interface DataSource {
|
public interface DataSource {
|
||||||
AccountRepository getAccountRepository();
|
AccountRepository getAccountRepository();
|
||||||
|
default void useAccountRepository(ThrowableConsumer<AccountRepository> repoConsumer) {
|
||||||
|
DbUtil.useClosable(this::getAccountRepository, repoConsumer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public final class DbUtil {
|
public final class DbUtil {
|
||||||
private DbUtil() {}
|
private DbUtil() {}
|
||||||
|
@ -90,4 +91,12 @@ public final class DbUtil {
|
||||||
public static LocalDateTime utcLDTFromTimestamp(Timestamp ts) {
|
public static LocalDateTime utcLDTFromTimestamp(Timestamp ts) {
|
||||||
return ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime();
|
return ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T extends AutoCloseable> void useClosable(Supplier<T> supplier, ThrowableConsumer<T> consumer) {
|
||||||
|
try (T t = supplier.get()) {
|
||||||
|
consumer.accept(t);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SqlRunnable {
|
||||||
|
void run() throws SQLException;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ThrowableConsumer<T> {
|
||||||
|
void accept(T value) throws Exception;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package com.andrewlalis.perfin.data.impl;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.AccountRepository;
|
import com.andrewlalis.perfin.data.AccountRepository;
|
||||||
import com.andrewlalis.perfin.data.DbUtil;
|
import com.andrewlalis.perfin.data.DbUtil;
|
||||||
|
import com.andrewlalis.perfin.data.UncheckedSqlException;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.AccountType;
|
import com.andrewlalis.perfin.model.AccountType;
|
||||||
|
|
||||||
|
@ -46,6 +47,32 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
|
||||||
return BigDecimal.valueOf(0, 4);
|
return BigDecimal.valueOf(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Account account) {
|
||||||
|
DbUtil.updateOne(
|
||||||
|
conn,
|
||||||
|
"UPDATE account SET name = ?, account_number = ?, currency = ?, account_type = ? WHERE id = ?",
|
||||||
|
List.of(
|
||||||
|
account.getName(),
|
||||||
|
account.getAccountNumber(),
|
||||||
|
account.getCurrency().getCurrencyCode(),
|
||||||
|
account.getType().name(),
|
||||||
|
account.getId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Account account) {
|
||||||
|
try (var stmt = conn.prepareStatement("DELETE FROM account WHERE id = ?")) {
|
||||||
|
stmt.setLong(1, account.getId());
|
||||||
|
int rows = stmt.executeUpdate();
|
||||||
|
if (rows != 1) throw new SQLException("Affected " + rows + " rows instead of expected 1.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new UncheckedSqlException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Account parseAccount(ResultSet rs) throws SQLException {
|
private static Account parseAccount(ResultSet rs) throws SQLException {
|
||||||
long id = rs.getLong("id");
|
long id = rs.getLong("id");
|
||||||
LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at"));
|
LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at"));
|
||||||
|
@ -55,4 +82,9 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
|
||||||
Currency currency = Currency.getInstance(rs.getString("currency"));
|
Currency currency = Currency.getInstance(rs.getString("currency"));
|
||||||
return new Account(id, createdAt, type, accountNumber, name, currency);
|
return new Account(id, createdAt, type, accountNumber, name, currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,22 @@ public class Account {
|
||||||
return currency;
|
return currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setType(AccountType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccountNumber(String accountNumber) {
|
||||||
|
this.accountNumber = accountNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(Currency currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
package com.andrewlalis.perfin.model;
|
package com.andrewlalis.perfin.model;
|
||||||
|
|
||||||
public enum AccountType {
|
public enum AccountType {
|
||||||
CHECKING,
|
CHECKING("Checking"),
|
||||||
SAVINGS,
|
SAVINGS("Savings"),
|
||||||
CREDIT_CARD;
|
CREDIT_CARD("Credit Card");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
AccountType(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
public static AccountType parse(String s) {
|
public static AccountType parse(String s) {
|
||||||
s = s.strip().toUpperCase();
|
s = s.strip().toUpperCase();
|
||||||
|
|
|
@ -8,13 +8,12 @@
|
||||||
fx:controller="com.andrewlalis.perfin.control.AccountViewController"
|
fx:controller="com.andrewlalis.perfin.control.AccountViewController"
|
||||||
stylesheets="@style/account-view.css"
|
stylesheets="@style/account-view.css"
|
||||||
styleClass="main-container"
|
styleClass="main-container"
|
||||||
style="-fx-border-color: green;"
|
|
||||||
>
|
>
|
||||||
<top>
|
<top>
|
||||||
<Label fx:id="titleLabel"/>
|
<Label fx:id="titleLabel"/>
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<VBox style="-fx-border-color: blue;" >
|
<VBox>
|
||||||
<VBox styleClass="account-property-box">
|
<VBox styleClass="account-property-box">
|
||||||
<Label text="Name"/>
|
<Label text="Name"/>
|
||||||
<TextField fx:id="accountNameField" editable="false"/>
|
<TextField fx:id="accountNameField" editable="false"/>
|
||||||
|
@ -34,9 +33,10 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
</center>
|
</center>
|
||||||
<right>
|
<right>
|
||||||
<VBox style="-fx-border-color: red;">
|
<VBox styleClass="actions-box">
|
||||||
<Button text="Edit"/>
|
<Label text="Actions" style="-fx-font-weight: bold;"/>
|
||||||
<Button text="Delete"/>
|
<Button text="Edit" onAction="#goToEditPage"/>
|
||||||
|
<Button text="Delete" onAction="#deleteAccount"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</right>
|
</right>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
fx:id="mainContainer"
|
fx:id="mainContainer"
|
||||||
fx:controller="com.andrewlalis.perfin.control.MainViewController"
|
fx:controller="com.andrewlalis.perfin.control.MainViewController"
|
||||||
stylesheets="@style/main-view.css"
|
stylesheets="@style/main-view.css"
|
||||||
style="-fx-border-color: purple;"
|
|
||||||
>
|
>
|
||||||
<top>
|
<top>
|
||||||
<VBox>
|
<VBox>
|
||||||
|
|
|
@ -24,3 +24,11 @@
|
||||||
#accountNumberField {
|
#accountNumberField {
|
||||||
-fx-font-family: monospace;
|
-fx-font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actions-box {
|
||||||
|
-fx-spacing: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-box > Button {
|
||||||
|
-fx-max-width: 500px;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue