Add experimental "Advanced Search" features, may incorporate into the main search interface yet...

This commit is contained in:
Andrew Lalis 2024-07-08 22:52:45 -04:00
parent a88ebc8e13
commit 2dbb3d944d
2 changed files with 87 additions and 38 deletions

View File

@ -3,17 +3,18 @@ package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener; import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
import com.andrewlalis.perfin.data.AccountRepository; import com.andrewlalis.perfin.data.AccountRepository;
import com.andrewlalis.perfin.data.TransactionRepository; import com.andrewlalis.perfin.data.TransactionRepository;
import com.andrewlalis.perfin.data.TransactionVendorRepository;
import com.andrewlalis.perfin.data.impl.JdbcDataSource; import com.andrewlalis.perfin.data.impl.JdbcDataSource;
import com.andrewlalis.perfin.data.pagination.Page; import com.andrewlalis.perfin.data.pagination.Page;
import com.andrewlalis.perfin.data.pagination.PageRequest; import com.andrewlalis.perfin.data.pagination.PageRequest;
import com.andrewlalis.perfin.data.pagination.Sort; import com.andrewlalis.perfin.data.pagination.Sort;
import com.andrewlalis.perfin.data.search.JdbcTransactionSearcher; import com.andrewlalis.perfin.data.search.JdbcTransactionSearcher;
import com.andrewlalis.perfin.data.search.SearchFilter; import com.andrewlalis.perfin.data.search.SearchFilter;
import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.data.util.Pair; import com.andrewlalis.perfin.data.util.Pair;
import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.model.Transaction; import com.andrewlalis.perfin.model.Transaction;
import com.andrewlalis.perfin.model.TransactionVendor;
import com.andrewlalis.perfin.view.BindingUtil; import com.andrewlalis.perfin.view.BindingUtil;
import com.andrewlalis.perfin.view.SceneUtil; import com.andrewlalis.perfin.view.SceneUtil;
import com.andrewlalis.perfin.view.component.AccountSelectionBox; import com.andrewlalis.perfin.view.component.AccountSelectionBox;
@ -25,18 +26,16 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import java.io.File;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import static com.andrewlalis.perfin.PerfinApp.router; import static com.andrewlalis.perfin.PerfinApp.router;
@ -57,7 +56,15 @@ public class TransactionsViewController implements RouteSelectionListener {
@FXML public BorderPane transactionsListBorderPane; @FXML public BorderPane transactionsListBorderPane;
@FXML public TextField searchField; @FXML public TextField searchField;
@FXML public AccountSelectionBox filterByAccountComboBox; @FXML public AccountSelectionBox filterByAccountComboBox;
@FXML public CheckBox advancedSearchFeaturesCheckBox;
@FXML public VBox advancedSearchFeaturesVBox;
@FXML public VBox advancedSearchAccountChoicesVBox;
@FXML public VBox advancedSearchTagChoicesVBox;
@FXML public VBox advancedSearchVendorChoicesVBox;
@FXML public VBox transactionsVBox; @FXML public VBox transactionsVBox;
private DataSourcePaginationControls paginationControls; private DataSourcePaginationControls paginationControls;
@ -78,6 +85,15 @@ public class TransactionsViewController implements RouteSelectionListener {
selectedTransaction.set(null); selectedTransaction.set(null);
}); });
// Initialize advanced search feature toggling.
BindingUtil.bindManagedAndVisible(filterByAccountComboBox.getParent(), advancedSearchFeaturesCheckBox.selectedProperty().not());
BindingUtil.bindManagedAndVisible(advancedSearchFeaturesVBox, advancedSearchFeaturesCheckBox.selectedProperty());
advancedSearchFeaturesCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
initializeAdvancedSearchFeatures();
}
});
this.paginationControls = new DataSourcePaginationControls( this.paginationControls = new DataSourcePaginationControls(
transactionsVBox.getChildren(), transactionsVBox.getChildren(),
new DataSourcePaginationControls.PageFetcherFunction() { new DataSourcePaginationControls.PageFetcherFunction() {
@ -124,6 +140,42 @@ public class TransactionsViewController implements RouteSelectionListener {
}); });
} }
private void initializeAdvancedSearchFeatures() {
Profile.getCurrent().dataSource().useRepoAsync(AccountRepository.class, repo -> {
List<Account> allAccounts = repo.findAll(PageRequest.unpaged(Sort.asc("name"))).items();
Platform.runLater(() -> {
advancedSearchAccountChoicesVBox.getChildren().clear();
for (Account account : allAccounts) {
CheckBox checkBox = new CheckBox(account.getShortName());
checkBox.setSelected(false);
advancedSearchAccountChoicesVBox.getChildren().add(checkBox);
}
});
});
Profile.getCurrent().dataSource().useRepoAsync(TransactionRepository.class, repo -> {
List<String> tags = repo.findAllTags();
Platform.runLater(() -> {
advancedSearchTagChoicesVBox.getChildren().clear();
for (var tag : tags) {
CheckBox checkBox = new CheckBox(tag);
checkBox.setSelected(false);
advancedSearchTagChoicesVBox.getChildren().add(checkBox);
}
});
});
Profile.getCurrent().dataSource().useRepoAsync(TransactionVendorRepository.class, repo -> {
List<TransactionVendor> vendors = repo.findAll();
Platform.runLater(() -> {
advancedSearchVendorChoicesVBox.getChildren().clear();
for (var vendor : vendors) {
CheckBox checkBox = new CheckBox(vendor.getName());
checkBox.setSelected(false);
advancedSearchVendorChoicesVBox.getChildren().add(checkBox);
}
});
});
}
@Override @Override
public void onRouteSelected(Object context) { public void onRouteSelected(Object context) {
paginationControls.sorts.setAll(DEFAULT_SORTS); paginationControls.sorts.setAll(DEFAULT_SORTS);
@ -162,31 +214,7 @@ public class TransactionsViewController implements RouteSelectionListener {
} }
@FXML public void exportTransactions() { @FXML public void exportTransactions() {
FileChooser fileChooser = new FileChooser(); Popups.message(transactionsListBorderPane, "Exporting transactions is not yet supported.");
fileChooser.setTitle("Export Transactions");
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("CSV Files", ".csv"));
File file = fileChooser.showSaveDialog(detailPanel.getScene().getWindow());
if (file != null) {
try (
var repo = Profile.getCurrent().dataSource().getTransactionRepository();
var out = new PrintWriter(file, StandardCharsets.UTF_8)
) {
out.println("id,utc-timestamp,amount,currency,description");
List<Transaction> allTransactions = repo.findAll(PageRequest.unpaged(Sort.desc("timestamp"))).items();
for (Transaction tx : allTransactions) {
out.println("%d,%s,%s,%s,%s".formatted(
tx.id,
tx.getTimestamp().format(DateUtil.DEFAULT_DATETIME_FORMAT),
tx.getAmount().toPlainString(),
tx.getCurrency().getCurrencyCode(),
tx.getDescription() == null ? "" : tx.getDescription()
));
}
} catch (Exception e) {
Popups.error(transactionsListBorderPane, e);
}
}
} }
private List<SearchFilter> getCurrentSearchFilters() { private List<SearchFilter> getCurrentSearchFilters() {

View File

@ -2,11 +2,8 @@
<?import com.andrewlalis.perfin.view.component.AccountSelectionBox?> <?import com.andrewlalis.perfin.view.component.AccountSelectionBox?>
<?import com.andrewlalis.perfin.view.component.PropertiesPane?> <?import com.andrewlalis.perfin.view.component.PropertiesPane?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.control.TextField?>
<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.TransactionsViewController" fx:controller="com.andrewlalis.perfin.control.TransactionsViewController"
@ -14,20 +11,43 @@
<top> <top>
<HBox styleClass="std-padding,std-spacing"> <HBox styleClass="std-padding,std-spacing">
<Button text="Add Transaction" onAction="#addTransaction"/> <Button text="Add Transaction" onAction="#addTransaction"/>
<Button text="Export Transactions" onAction="#exportTransactions"/> <Button text="Export Transactions" onAction="#exportTransactions" disable="true"/>
</HBox> </HBox>
</top> </top>
<center> <center>
<!-- The main page content is an HBox with a list of transactions on the
left, and a detail panel on the right (which is hidden if no
transaction is selected). -->
<HBox> <HBox>
<BorderPane fx:id="transactionsListBorderPane" HBox.hgrow="ALWAYS"> <BorderPane fx:id="transactionsListBorderPane" HBox.hgrow="ALWAYS">
<top> <top>
<HBox styleClass="padding-extra,std-spacing"> <VBox styleClass="padding-extra,std-spacing">
<TextField fx:id="searchField" promptText="Search"/> <TextField fx:id="searchField" promptText="Search" maxWidth="300" prefWidth="300" minWidth="100"/>
<PropertiesPane hgap="5" vgap="5"> <PropertiesPane hgap="5" vgap="5">
<Label text="Filter by Account"/> <Label text="Filter by Account"/>
<AccountSelectionBox fx:id="filterByAccountComboBox" allowNone="true" showBalance="false"/> <AccountSelectionBox fx:id="filterByAccountComboBox" allowNone="true" showBalance="false"/>
</PropertiesPane> </PropertiesPane>
</HBox> <CheckBox fx:id="advancedSearchFeaturesCheckBox" text="Advanced Search"/>
<VBox fx:id="advancedSearchFeaturesVBox" styleClass="std-spacing">
<Label text="Advanced search features shown here!"/>
<PropertiesPane hgap="5" vgap="5">
<Label text="Filter by Accounts"/>
<ScrollPane maxHeight="100">
<VBox fx:id="advancedSearchAccountChoicesVBox" styleClass="std-spacing"/>
</ScrollPane>
<Label text="Filter by Tags"/>
<ScrollPane maxHeight="100">
<VBox fx:id="advancedSearchTagChoicesVBox" styleClass="std-spacing"/>
</ScrollPane>
<Label text="Filter by Vendor"/>
<ScrollPane maxHeight="100">
<VBox fx:id="advancedSearchVendorChoicesVBox" styleClass="std-spacing"/>
</ScrollPane>
</PropertiesPane>
</VBox>
</VBox>
</top> </top>
<center> <center>
<ScrollPane styleClass="tile-container-scroll"> <ScrollPane styleClass="tile-container-scroll">
@ -35,6 +55,7 @@
</ScrollPane> </ScrollPane>
</center> </center>
</BorderPane> </BorderPane>
<VBox fx:id="detailPanel"/> <VBox fx:id="detailPanel"/>
</HBox> </HBox>
</center> </center>