Added the ability to navigate to a specific transaction.
This commit is contained in:
		
							parent
							
								
									b477e9ab3c
								
							
						
					
					
						commit
						173204c61c
					
				| 
						 | 
				
			
			@ -54,14 +54,14 @@ public class TransactionViewController {
 | 
			
		|||
            Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
 | 
			
		||||
                CreditAndDebitAccounts accounts = repo.findLinkedAccounts(transaction.getId());
 | 
			
		||||
                Platform.runLater(() -> {
 | 
			
		||||
                    if (accounts.hasDebit()) {
 | 
			
		||||
                        debitAccountLink.setText(accounts.debitAccount().getShortName());
 | 
			
		||||
                        debitAccountLink.setOnAction(event -> router.navigate("account", accounts.debitAccount()));
 | 
			
		||||
                    }
 | 
			
		||||
                    if (accounts.hasCredit()) {
 | 
			
		||||
                        creditAccountLink.setText(accounts.creditAccount().getShortName());
 | 
			
		||||
                        creditAccountLink.setOnAction(event -> router.navigate("account", accounts.creditAccount()));
 | 
			
		||||
                    }
 | 
			
		||||
                    accounts.ifDebit(acc -> {
 | 
			
		||||
                        debitAccountLink.setText(acc.getShortName());
 | 
			
		||||
                        debitAccountLink.setOnAction(event -> router.navigate("account", acc));
 | 
			
		||||
                    });
 | 
			
		||||
                    accounts.ifCredit(acc -> {
 | 
			
		||||
                        creditAccountLink.setText(acc.getShortName());
 | 
			
		||||
                        creditAccountLink.setOnAction(event -> router.navigate("account", acc));
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -105,5 +105,6 @@ public class TransactionViewController {
 | 
			
		|||
        TextFlow parent = (TextFlow) link.getParent();
 | 
			
		||||
        parent.managedProperty().bind(parent.visibleProperty());
 | 
			
		||||
        parent.visibleProperty().bind(link.textProperty().isNotEmpty());
 | 
			
		||||
        link.setText(null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,13 @@
 | 
			
		|||
package com.andrewlalis.perfin.control;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.Pair;
 | 
			
		||||
import com.andrewlalis.perfin.view.SceneUtil;
 | 
			
		||||
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.Profile;
 | 
			
		||||
import com.andrewlalis.perfin.model.Transaction;
 | 
			
		||||
import com.andrewlalis.perfin.view.SceneUtil;
 | 
			
		||||
import com.andrewlalis.perfin.view.component.DataSourcePaginationControls;
 | 
			
		||||
import com.andrewlalis.perfin.view.component.TransactionTile;
 | 
			
		||||
import javafx.beans.property.ObjectProperty;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,9 +19,21 @@ import javafx.scene.layout.BorderPane;
 | 
			
		|||
import javafx.scene.layout.HBox;
 | 
			
		||||
import javafx.scene.layout.VBox;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controller for the view of all transactions in a user's profile.
 | 
			
		||||
 * Transactions are displayed in a paginated manner, and this controller
 | 
			
		||||
 * accepts as a route context a {@link PageRequest} to initialize the results
 | 
			
		||||
 * to a specific page.
 | 
			
		||||
 */
 | 
			
		||||
public class TransactionsViewController implements RouteSelectionListener {
 | 
			
		||||
    public static List<Sort> DEFAULT_SORTS = List.of(Sort.desc("timestamp"));
 | 
			
		||||
    public static int DEFAULT_ITEMS_PER_PAGE = 5;
 | 
			
		||||
    public record RouteContext(Long selectedTransactionId) {}
 | 
			
		||||
 | 
			
		||||
    @FXML public BorderPane transactionsListBorderPane;
 | 
			
		||||
    @FXML public VBox transactionsVBox;
 | 
			
		||||
    private DataSourcePaginationControls paginationControls;
 | 
			
		||||
| 
						 | 
				
			
			@ -80,8 +92,23 @@ public class TransactionsViewController implements RouteSelectionListener {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onRouteSelected(Object context) {
 | 
			
		||||
        paginationControls.sorts.setAll(Sort.desc("timestamp"));
 | 
			
		||||
        this.paginationControls.setPage(1);
 | 
			
		||||
        paginationControls.sorts.setAll(DEFAULT_SORTS);
 | 
			
		||||
        paginationControls.itemsPerPage.set(DEFAULT_ITEMS_PER_PAGE);
 | 
			
		||||
 | 
			
		||||
        // 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(() -> {
 | 
			
		||||
                Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
 | 
			
		||||
                    repo.findById(ctx.selectedTransactionId).ifPresent(tx -> {
 | 
			
		||||
                        long offset = repo.countAllAfter(tx.getId());
 | 
			
		||||
                        int pageNumber = (int) (offset / DEFAULT_ITEMS_PER_PAGE) + 1;
 | 
			
		||||
                        paginationControls.setPage(pageNumber).thenRun(() -> selectedTransaction.set(tx));
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            paginationControls.setPage(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
| 
						 | 
				
			
			@ -92,13 +119,13 @@ public class TransactionsViewController implements RouteSelectionListener {
 | 
			
		|||
    private TransactionTile makeTile(Transaction transaction) {
 | 
			
		||||
        var tile = new TransactionTile(transaction);
 | 
			
		||||
        tile.setOnMouseClicked(event -> {
 | 
			
		||||
            if (selectedTransaction.get() == null || selectedTransaction.get().getId() != transaction.getId()) {
 | 
			
		||||
            if (selectedTransaction.get() == null || !selectedTransaction.get().equals(transaction)) {
 | 
			
		||||
                selectedTransaction.set(transaction);
 | 
			
		||||
            } else {
 | 
			
		||||
                selectedTransaction.set(null);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        tile.selected.bind(selectedTransaction.map(t -> t != null && t.getId() == transaction.getId()));
 | 
			
		||||
        tile.selected.bind(selectedTransaction.map(t -> t != null && t.equals(transaction)));
 | 
			
		||||
        return tile;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import java.nio.file.Path;
 | 
			
		|||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Currency;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
public interface TransactionRepository extends AutoCloseable {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,8 +23,10 @@ public interface TransactionRepository extends AutoCloseable {
 | 
			
		|||
            CreditAndDebitAccounts linkedAccounts,
 | 
			
		||||
            List<Path> attachments
 | 
			
		||||
    );
 | 
			
		||||
    Optional<Transaction> findById(long id);
 | 
			
		||||
    Page<Transaction> findAll(PageRequest pagination);
 | 
			
		||||
    long countAll();
 | 
			
		||||
    long countAllAfter(long transactionId);
 | 
			
		||||
    Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination);
 | 
			
		||||
    CreditAndDebitAccounts findLinkedAccounts(long transactionId);
 | 
			
		||||
    List<Attachment> findAttachments(long transactionId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,10 +2,10 @@ package com.andrewlalis.perfin.data.impl;
 | 
			
		|||
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountEntryRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.AttachmentRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.data.TransactionRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.Page;
 | 
			
		||||
import com.andrewlalis.perfin.data.pagination.PageRequest;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DbUtil;
 | 
			
		||||
import com.andrewlalis.perfin.model.*;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
| 
						 | 
				
			
			@ -14,10 +14,7 @@ import java.sql.Connection;
 | 
			
		|||
import java.sql.ResultSet;
 | 
			
		||||
import java.sql.SQLException;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Currency;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public record JdbcTransactionRepository(Connection conn, Path contentDir) implements TransactionRepository {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +52,11 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Optional<Transaction> findById(long id) {
 | 
			
		||||
        return DbUtil.findById(conn, "SELECT * FROM transaction WHERE id = ?", id, JdbcTransactionRepository::parseTransaction);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Page<Transaction> findAll(PageRequest pagination) {
 | 
			
		||||
        return DbUtil.findAll(
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +72,16 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem
 | 
			
		|||
        return DbUtil.findOne(conn, "SELECT COUNT(id) FROM transaction", Collections.emptyList(), rs -> rs.getLong(1)).orElse(0L);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public long countAllAfter(long transactionId) {
 | 
			
		||||
        return DbUtil.findOne(
 | 
			
		||||
                conn,
 | 
			
		||||
                "SELECT COUNT(id) FROM transaction WHERE timestamp > (SELECT timestamp FROM transaction WHERE id = ?)",
 | 
			
		||||
                List.of(transactionId),
 | 
			
		||||
                rs -> rs.getLong(1)
 | 
			
		||||
        ).orElse(0L);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination) {
 | 
			
		||||
        String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(","));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,11 +11,11 @@ import java.util.Currency;
 | 
			
		|||
 * entries that apply this transaction's amount to one or more accounts.
 | 
			
		||||
 */
 | 
			
		||||
public class Transaction {
 | 
			
		||||
    private long id;
 | 
			
		||||
    private LocalDateTime timestamp;
 | 
			
		||||
    private final long id;
 | 
			
		||||
    private final LocalDateTime timestamp;
 | 
			
		||||
 | 
			
		||||
    private BigDecimal amount;
 | 
			
		||||
    private Currency currency;
 | 
			
		||||
    private final BigDecimal amount;
 | 
			
		||||
    private final Currency currency;
 | 
			
		||||
    private String description;
 | 
			
		||||
 | 
			
		||||
    public Transaction(long id, LocalDateTime timestamp, BigDecimal amount, Currency currency, String description) {
 | 
			
		||||
| 
						 | 
				
			
			@ -26,13 +26,6 @@ public class Transaction {
 | 
			
		|||
        this.description = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Transaction(LocalDateTime timestamp, BigDecimal amount, Currency currency, String description) {
 | 
			
		||||
        this.timestamp = timestamp;
 | 
			
		||||
        this.amount = amount;
 | 
			
		||||
        this.currency = currency;
 | 
			
		||||
        this.description = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getId() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -52,4 +45,9 @@ public class Transaction {
 | 
			
		|||
    public String getDescription() {
 | 
			
		||||
        return description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object other) {
 | 
			
		||||
        return other instanceof Transaction tx && id == tx.id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.control.TransactionsViewController;
 | 
			
		||||
import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.CurrencyUtil;
 | 
			
		||||
import com.andrewlalis.perfin.data.util.DateUtil;
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,8 @@ import javafx.scene.layout.BorderPane;
 | 
			
		|||
import javafx.scene.text.Text;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
import static com.andrewlalis.perfin.PerfinApp.router;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A tile that shows a brief bit of information about an account history item.
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +44,10 @@ public class AccountHistoryItemTile extends BorderPane {
 | 
			
		|||
    private Node buildAccountEntryItem(AccountEntry entry) {
 | 
			
		||||
        Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
 | 
			
		||||
        Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
 | 
			
		||||
        transactionLink.setOnAction(event -> router.navigate(
 | 
			
		||||
                "transactions",
 | 
			
		||||
                new TransactionsViewController.RouteContext(entry.getTransactionId())
 | 
			
		||||
        ));
 | 
			
		||||
        return new TextFlow(
 | 
			
		||||
                transactionLink,
 | 
			
		||||
                new Text("posted as a " + entry.getType().name().toLowerCase() + " to this account, with a value of "),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
package com.andrewlalis.perfin.view.component;
 | 
			
		||||
 | 
			
		||||
import com.andrewlalis.perfin.PerfinApp;
 | 
			
		||||
import com.andrewlalis.perfin.model.Attachment;
 | 
			
		||||
import com.andrewlalis.perfin.model.Profile;
 | 
			
		||||
import com.andrewlalis.perfin.view.ImageCache;
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +12,7 @@ import javafx.scene.paint.Color;
 | 
			
		|||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +60,8 @@ public class AttachmentPreview extends BorderPane {
 | 
			
		|||
        this.setCenter(stackPane);
 | 
			
		||||
        this.setOnMouseClicked(event -> {
 | 
			
		||||
            if (this.isHover()) {
 | 
			
		||||
                System.out.println("Opening attachment: " + attachment.getFilename());
 | 
			
		||||
                Path filePath = attachment.getPath(Profile.getContentDir(Profile.getCurrent().getName()));
 | 
			
		||||
                PerfinApp.instance.getHostServices().showDocument(filePath.toAbsolutePath().toUri().toString());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ import javafx.beans.property.IntegerProperty;
 | 
			
		|||
import javafx.beans.property.SimpleBooleanProperty;
 | 
			
		||||
import javafx.beans.property.SimpleIntegerProperty;
 | 
			
		||||
import javafx.collections.FXCollections;
 | 
			
		||||
import javafx.collections.ListChangeListener;
 | 
			
		||||
import javafx.collections.ObservableList;
 | 
			
		||||
import javafx.geometry.Pos;
 | 
			
		||||
import javafx.scene.Node;
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +19,8 @@ import javafx.scene.text.Text;
 | 
			
		|||
import javafx.scene.text.TextAlignment;
 | 
			
		||||
import javafx.scene.text.TextFlow;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A pane that contains some controls for navigating a paginated data source.
 | 
			
		||||
 * That includes going to the next/previous page, setting the preferred page
 | 
			
		||||
| 
						 | 
				
			
			@ -66,10 +67,6 @@ public class DataSourcePaginationControls extends BorderPane {
 | 
			
		|||
        nextPageButton.disableProperty().bind(fetching.or(currentPage.greaterThanOrEqualTo(maxPages)));
 | 
			
		||||
        nextPageButton.setOnAction(event -> setPage(currentPage.get() + 1));
 | 
			
		||||
 | 
			
		||||
//        sorts.addListener((ListChangeListener<Sort>) c -> {
 | 
			
		||||
//            setPage(1);
 | 
			
		||||
//        });
 | 
			
		||||
 | 
			
		||||
        HBox hbox = new HBox(
 | 
			
		||||
                previousPageButton,
 | 
			
		||||
                pageTextContainer,
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +76,8 @@ public class DataSourcePaginationControls extends BorderPane {
 | 
			
		|||
        setCenter(hbox);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPage(int page) {
 | 
			
		||||
    public CompletableFuture<Void> setPage(int page) {
 | 
			
		||||
        CompletableFuture<Void> cf = new CompletableFuture<>();
 | 
			
		||||
        fetching.set(true);
 | 
			
		||||
        PageRequest pagination = new PageRequest(page - 1, itemsPerPage.get(), sorts);
 | 
			
		||||
        Thread.ofVirtual().start(() -> {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,14 +95,17 @@ public class DataSourcePaginationControls extends BorderPane {
 | 
			
		|||
                    }
 | 
			
		||||
                    currentPage.set(page);
 | 
			
		||||
                    fetching.set(false);
 | 
			
		||||
                    cf.complete(null);
 | 
			
		||||
                });
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                e.printStackTrace(System.err);
 | 
			
		||||
                Platform.runLater(() -> {
 | 
			
		||||
                    target.clear();
 | 
			
		||||
                    fetching.set(false);
 | 
			
		||||
                    cf.complete(null);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return cf;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue