diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java index d77b7ad..e9eba2c 100644 --- a/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/TransactionsViewController.java @@ -5,21 +5,26 @@ import com.andrewlalis.perfin.data.pagination.Page; import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.pagination.Sort; import com.andrewlalis.perfin.data.util.Pair; +import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.Transaction; +import com.andrewlalis.perfin.view.AccountComboBoxCellFactory; import com.andrewlalis.perfin.view.SceneUtil; import com.andrewlalis.perfin.view.component.DataSourcePaginationControls; import com.andrewlalis.perfin.view.component.TransactionTile; +import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.Node; +import javafx.scene.control.ComboBox; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import java.util.List; +import java.util.Set; import static com.andrewlalis.perfin.PerfinApp.router; @@ -35,6 +40,7 @@ public class TransactionsViewController implements RouteSelectionListener { public record RouteContext(Long selectedTransactionId) {} @FXML public BorderPane transactionsListBorderPane; + @FXML public ComboBox filterByAccountComboBox; @FXML public VBox transactionsVBox; private DataSourcePaginationControls paginationControls; @@ -44,20 +50,40 @@ public class TransactionsViewController implements RouteSelectionListener { @FXML public void initialize() { // Initialize the left-hand paginated transactions list. + var accountCellFactory = new AccountComboBoxCellFactory("All"); + filterByAccountComboBox.setCellFactory(accountCellFactory); + filterByAccountComboBox.setButtonCell(accountCellFactory.call(null)); + filterByAccountComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { + paginationControls.setPage(1); + selectedTransaction.set(null); + }); + this.paginationControls = new DataSourcePaginationControls( transactionsVBox.getChildren(), new DataSourcePaginationControls.PageFetcherFunction() { @Override public Page fetchPage(PageRequest pagination) throws Exception { + Account accountFilter = filterByAccountComboBox.getValue(); try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) { - return repo.findAll(pagination).map(TransactionsViewController.this::makeTile); + Page result; + if (accountFilter == null) { + result = repo.findAll(pagination); + } else { + result = repo.findAllByAccounts(Set.of(accountFilter.id), pagination); + } + return result.map(TransactionsViewController.this::makeTile); } } @Override public int getTotalCount() throws Exception { + Account accountFilter = filterByAccountComboBox.getValue(); try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) { - return (int) repo.countAll(); + if (accountFilter == null) { + return (int) repo.countAll(); + } else { + return (int) repo.countAllByAccounts(Set.of(accountFilter.id)); + } } } } @@ -95,6 +121,21 @@ public class TransactionsViewController implements RouteSelectionListener { paginationControls.sorts.setAll(DEFAULT_SORTS); paginationControls.itemsPerPage.set(DEFAULT_ITEMS_PER_PAGE); + // Refresh account filter options. + Thread.ofVirtual().start(() -> { + Profile.getCurrent().getDataSource().useAccountRepository(repo -> { + List accounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items(); + accounts.add(null); + Platform.runLater(() -> { + filterByAccountComboBox.getItems().clear(); + filterByAccountComboBox.getItems().addAll(accounts); + filterByAccountComboBox.getSelectionModel().selectLast(); + filterByAccountComboBox.getButtonCell().updateIndex(accounts.size() - 1); + }); + }); + }); + + // If a transaction id is given in the route context, navigate to the page it's on and select it. if (context instanceof RouteContext ctx && ctx.selectedTransactionId != null) { Thread.ofVirtual().start(() -> { @@ -108,6 +149,7 @@ public class TransactionsViewController implements RouteSelectionListener { }); } else { paginationControls.setPage(1); + selectedTransaction.set(null); } } diff --git a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java index 0988441..4a4eecb 100644 --- a/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/TransactionRepository.java @@ -27,6 +27,7 @@ public interface TransactionRepository extends AutoCloseable { Page findAll(PageRequest pagination); long countAll(); long countAllAfter(long transactionId); + long countAllByAccounts(Set accountIds); Page findAllByAccounts(Set accountIds, PageRequest pagination); CreditAndDebitAccounts findLinkedAccounts(long transactionId); List findAttachments(long transactionId); diff --git a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java index 0bbe25e..9668aa0 100644 --- a/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java +++ b/src/main/java/com/andrewlalis/perfin/data/impl/JdbcTransactionRepository.java @@ -82,6 +82,18 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem ).orElse(0L); } + @Override + public long countAllByAccounts(Set accountIds) { + String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(",")); + String query = String.format(""" + SELECT COUNT(transaction.id) + FROM transaction + LEFT JOIN account_entry ON account_entry.transaction_id = transaction.id + WHERE account_entry.account_id IN (%s) + """, idsStr); + return DbUtil.findOne(conn, query, Collections.emptyList(), rs -> rs.getLong(1)).orElse(0L); + } + @Override public Page findAllByAccounts(Set accountIds, PageRequest pagination) { String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(",")); diff --git a/src/main/java/com/andrewlalis/perfin/view/AccountComboBoxCellFactory.java b/src/main/java/com/andrewlalis/perfin/view/AccountComboBoxCellFactory.java index f544218..f6111be 100644 --- a/src/main/java/com/andrewlalis/perfin/view/AccountComboBoxCellFactory.java +++ b/src/main/java/com/andrewlalis/perfin/view/AccountComboBoxCellFactory.java @@ -7,10 +7,22 @@ import javafx.scene.control.ListView; import javafx.util.Callback; public class AccountComboBoxCellFactory implements Callback, ListCell> { + private final String emptyCellText; + + public AccountComboBoxCellFactory(String emptyCellText) { + this.emptyCellText = emptyCellText; + } + + public AccountComboBoxCellFactory() { + this("None"); + } + public static class AccountListCell extends ListCell { private final Label label = new Label(); + private final String emptyCellText; - public AccountListCell() { + public AccountListCell(String emptyCellText) { + this.emptyCellText = emptyCellText; label.setStyle("-fx-text-fill: black;"); } @@ -18,7 +30,7 @@ public class AccountComboBoxCellFactory implements Callback, L protected void updateItem(Account item, boolean empty) { super.updateItem(item, empty); if (item == null || empty) { - label.setText("None"); + label.setText(emptyCellText); } else { label.setText(item.getName() + " (" + item.getAccountNumberSuffix() + ")"); } @@ -28,6 +40,6 @@ public class AccountComboBoxCellFactory implements Callback, L @Override public ListCell call(ListView param) { - return new AccountListCell(); + return new AccountListCell(emptyCellText); } } diff --git a/src/main/resources/transactions-view.fxml b/src/main/resources/transactions-view.fxml index 849ca39..217a3df 100644 --- a/src/main/resources/transactions-view.fxml +++ b/src/main/resources/transactions-view.fxml @@ -1,6 +1,9 @@ + + + + + + + + +