Added primitive pagination controls to transaction view, with expandability for other paginated entities.
This commit is contained in:
parent
633cd60572
commit
01d08154e0
|
@ -0,0 +1,7 @@
|
||||||
|
package com.andrewlalis.perfin;
|
||||||
|
|
||||||
|
public record Pair<A, B>(A first, B second) {
|
||||||
|
public static <A, B> Pair<A, B> of(A first, B second) {
|
||||||
|
return new Pair<>(first, second);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,17 @@ import java.net.URL;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class SceneUtil {
|
public class SceneUtil {
|
||||||
|
public static <N, C> Pair<N, C> loadNodeAndController(String fxml) {
|
||||||
|
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
|
||||||
|
try {
|
||||||
|
N node = loader.load();
|
||||||
|
C controller = loader.getController();
|
||||||
|
return Pair.of(node, controller);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> Parent loadNode(String fxml, Consumer<T> controllerConfig) {
|
public static <T> Parent loadNode(String fxml, Consumer<T> controllerConfig) {
|
||||||
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
|
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.andrewlalis.perfin.control;
|
package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
|
||||||
import com.andrewlalis.perfin.data.CurrencyUtil;
|
import com.andrewlalis.perfin.data.CurrencyUtil;
|
||||||
import com.andrewlalis.perfin.data.DateUtil;
|
import com.andrewlalis.perfin.data.DateUtil;
|
||||||
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
||||||
|
@ -23,7 +22,7 @@ import java.util.List;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
public class TransactionViewController implements RouteSelectionListener {
|
public class TransactionViewController {
|
||||||
private Transaction transaction;
|
private Transaction transaction;
|
||||||
|
|
||||||
@FXML public Label amountLabel;
|
@FXML public Label amountLabel;
|
||||||
|
@ -37,9 +36,9 @@ public class TransactionViewController implements RouteSelectionListener {
|
||||||
@FXML public HBox attachmentsHBox;
|
@FXML public HBox attachmentsHBox;
|
||||||
private final ObservableList<TransactionAttachment> attachmentsList = FXCollections.observableArrayList();
|
private final ObservableList<TransactionAttachment> attachmentsList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
@Override
|
public void setTransaction(Transaction transaction) {
|
||||||
public void onRouteSelected(Object context) {
|
this.transaction = transaction;
|
||||||
this.transaction = (Transaction) context;
|
if (transaction == null) return;
|
||||||
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency()));
|
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency()));
|
||||||
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
||||||
descriptionLabel.setText(transaction.getDescription());
|
descriptionLabel.setText(transaction.getDescription());
|
||||||
|
@ -75,6 +74,7 @@ public class TransactionViewController implements RouteSelectionListener {
|
||||||
new Label(attachment.getFilename()),
|
new Label(attachment.getFilename()),
|
||||||
new Label(attachment.getContentType())
|
new Label(attachment.getContentType())
|
||||||
);
|
);
|
||||||
|
// TODO: Custom attachment preview element.
|
||||||
return vbox;
|
return vbox;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,82 @@
|
||||||
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.Pair;
|
||||||
|
import com.andrewlalis.perfin.SceneUtil;
|
||||||
|
import com.andrewlalis.perfin.control.component.DataSourcePaginationControls;
|
||||||
import com.andrewlalis.perfin.control.component.TransactionTile;
|
import com.andrewlalis.perfin.control.component.TransactionTile;
|
||||||
|
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.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import com.andrewlalis.perfin.model.Transaction;
|
||||||
import javafx.application.Platform;
|
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.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
public class TransactionsViewController implements RouteSelectionListener {
|
public class TransactionsViewController implements RouteSelectionListener {
|
||||||
@FXML
|
@FXML public BorderPane transactionsListBorderPane;
|
||||||
public VBox transactionsVBox;
|
@FXML public VBox transactionsVBox;
|
||||||
|
private DataSourcePaginationControls paginationControls;
|
||||||
|
|
||||||
|
|
||||||
|
@FXML public VBox detailPanel;
|
||||||
|
private final ObjectProperty<Transaction> selectedTransaction = new SimpleObjectProperty<>(null);
|
||||||
|
|
||||||
|
@FXML public void initialize() {
|
||||||
|
// Initialize the left-hand paginated transactions list.
|
||||||
|
this.paginationControls = new DataSourcePaginationControls(
|
||||||
|
transactionsVBox.getChildren(),
|
||||||
|
new DataSourcePaginationControls.PageFetcherFunction() {
|
||||||
|
@Override
|
||||||
|
public Page<? extends Node> fetchPage(PageRequest pagination) throws Exception {
|
||||||
|
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
||||||
|
return repo.findAll(pagination).map(TransactionsViewController.this::makeTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalCount() throws Exception {
|
||||||
|
try (var repo = Profile.getCurrent().getDataSource().getTransactionRepository()) {
|
||||||
|
return (int) repo.countAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
transactionsListBorderPane.setBottom(paginationControls);
|
||||||
|
|
||||||
|
// Initialize the right-hand transaction detail view.
|
||||||
|
HBox container = (HBox) detailPanel.getParent();
|
||||||
|
ObservableValue<Double> halfWidthProp = container.widthProperty().map(n -> n.doubleValue() * 0.5);
|
||||||
|
detailPanel.minWidthProperty().bind(halfWidthProp);
|
||||||
|
detailPanel.maxWidthProperty().bind(halfWidthProp);
|
||||||
|
detailPanel.prefWidthProperty().bind(halfWidthProp);
|
||||||
|
detailPanel.managedProperty().bind(detailPanel.visibleProperty());
|
||||||
|
detailPanel.visibleProperty().bind(selectedTransaction.isNotNull());
|
||||||
|
|
||||||
|
Pair<BorderPane, TransactionViewController> detailComponents = SceneUtil.loadNodeAndController("/transaction-view.fxml");
|
||||||
|
TransactionViewController transactionViewController = detailComponents.second();
|
||||||
|
BorderPane transactionDetailView = detailComponents.first();
|
||||||
|
transactionDetailView.managedProperty().bind(transactionDetailView.visibleProperty());
|
||||||
|
transactionDetailView.visibleProperty().bind(selectedTransaction.isNotNull());
|
||||||
|
detailPanel.getChildren().add(transactionDetailView);
|
||||||
|
selectedTransaction.addListener((observable, oldValue, newValue) -> {
|
||||||
|
transactionViewController.setTransaction(newValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRouteSelected(Object context) {
|
public void onRouteSelected(Object context) {
|
||||||
refreshTransactions();
|
paginationControls.sorts.setAll(Sort.desc("timestamp"));
|
||||||
|
this.paginationControls.setPage(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -25,13 +84,37 @@ public class TransactionsViewController implements RouteSelectionListener {
|
||||||
router.navigate("create-transaction");
|
router.navigate("create-transaction");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshTransactions() {
|
private TransactionTile makeTile(Transaction transaction) {
|
||||||
Thread.ofVirtual().start(() -> {
|
var tile = new TransactionTile(transaction);
|
||||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
tile.setOnMouseClicked(event -> {
|
||||||
var page = repo.findAll(PageRequest.unpaged(Sort.desc("timestamp")));
|
if (selectedTransaction.get() == null || selectedTransaction.get().getId() != transaction.getId()) {
|
||||||
var components = page.items().stream().map(transaction -> new TransactionTile(transaction, this::refreshTransactions)).toList();
|
selectedTransaction.set(transaction);
|
||||||
Platform.runLater(() -> transactionsVBox.getChildren().setAll(components));
|
} else {
|
||||||
});
|
selectedTransaction.set(null);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
tile.selected.bind(selectedTransaction.map(t -> t != null && t.getId() == transaction.getId()));
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private void refreshTransactions() {
|
||||||
|
// Thread.ofVirtual().start(() -> {
|
||||||
|
// Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||||
|
// var page = repo.findAll(PageRequest.unpaged(Sort.desc("timestamp")));
|
||||||
|
// var components = page.items().stream().map(transaction -> {
|
||||||
|
// var tile = new TransactionTile(transaction, this::refreshTransactions);
|
||||||
|
// tile.setOnMouseClicked(event -> {
|
||||||
|
// if (selectedTransaction.get() == null || selectedTransaction.get().getId() != transaction.getId()) {
|
||||||
|
// selectedTransaction.set(transaction);
|
||||||
|
// } else {
|
||||||
|
// selectedTransaction.set(null);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// tile.selected.bind(selectedTransaction.map(t -> t != null && t.getId() == transaction.getId()));
|
||||||
|
// return tile;
|
||||||
|
// }).toList();
|
||||||
|
// Platform.runLater(() -> transactionsVBox.getChildren().setAll(components));
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.andrewlalis.perfin.control.component;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.data.pagination.Page;
|
||||||
|
import com.andrewlalis.perfin.data.pagination.PageRequest;
|
||||||
|
import com.andrewlalis.perfin.data.pagination.Sort;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
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.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextFlow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pane that contains some controls for navigating a paginated data source.
|
||||||
|
* That includes going to the next/previous page, setting the preferred page
|
||||||
|
* size.
|
||||||
|
*/
|
||||||
|
public class DataSourcePaginationControls extends BorderPane {
|
||||||
|
public interface PageFetcherFunction {
|
||||||
|
Page<? extends Node> fetchPage(PageRequest pagination) throws Exception;
|
||||||
|
default int getTotalCount() throws Exception {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final IntegerProperty currentPage = new SimpleIntegerProperty(1);
|
||||||
|
public final IntegerProperty maxPages = new SimpleIntegerProperty(-1);
|
||||||
|
public final IntegerProperty itemsPerPage = new SimpleIntegerProperty(5);
|
||||||
|
public final ObservableList<Sort> sorts = FXCollections.observableArrayList();
|
||||||
|
private final BooleanProperty fetching = new SimpleBooleanProperty(false);
|
||||||
|
private final ObservableList<Node> target;
|
||||||
|
private final PageFetcherFunction fetcher;
|
||||||
|
|
||||||
|
public DataSourcePaginationControls(ObservableList<Node> target, PageFetcherFunction fetcher) {
|
||||||
|
this.target = target;
|
||||||
|
this.fetcher = fetcher;
|
||||||
|
|
||||||
|
Text currentPageLabel = new Text();
|
||||||
|
currentPageLabel.textProperty().bind(currentPage.asString());
|
||||||
|
Text maxPagesLabel = new Text();
|
||||||
|
maxPagesLabel.textProperty().bind(maxPages.asString());
|
||||||
|
TextFlow maxPagesText = new TextFlow(new Text(" / "), maxPagesLabel);
|
||||||
|
maxPagesText.managedProperty().bind(maxPagesText.visibleProperty());
|
||||||
|
maxPagesText.visibleProperty().bind(maxPages.isNotEqualTo(-1));
|
||||||
|
TextFlow pageText = new TextFlow(new Text("Page "), currentPageLabel, maxPagesText);
|
||||||
|
|
||||||
|
|
||||||
|
Button previousPageButton = new Button("Previous Page");
|
||||||
|
previousPageButton.disableProperty().bind(currentPage.lessThan(2).or(fetching));
|
||||||
|
previousPageButton.setOnAction(event -> setPage(currentPage.get() - 1));
|
||||||
|
Button nextPageButton = new Button("Next Page");
|
||||||
|
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,
|
||||||
|
pageText,
|
||||||
|
nextPageButton
|
||||||
|
);
|
||||||
|
setCenter(hbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPage(int page) {
|
||||||
|
try {
|
||||||
|
fetching.set(true);
|
||||||
|
PageRequest pagination = new PageRequest(page - 1, itemsPerPage.get(), sorts);
|
||||||
|
var p = fetcher.fetchPage(pagination);
|
||||||
|
int totalResults = fetcher.getTotalCount();
|
||||||
|
target.setAll(p.items());
|
||||||
|
if (totalResults != -1) {
|
||||||
|
int max = totalResults / itemsPerPage.get();
|
||||||
|
if (totalResults % itemsPerPage.get() != 0) {
|
||||||
|
max += 1;
|
||||||
|
}
|
||||||
|
maxPages.set(max);
|
||||||
|
}
|
||||||
|
currentPage.set(page);
|
||||||
|
} catch (Exception e) {
|
||||||
|
target.clear();
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
} finally {
|
||||||
|
fetching.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,20 +2,21 @@ package com.andrewlalis.perfin.control.component;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.data.CurrencyUtil;
|
import com.andrewlalis.perfin.data.CurrencyUtil;
|
||||||
import com.andrewlalis.perfin.data.DateUtil;
|
import com.andrewlalis.perfin.data.DateUtil;
|
||||||
import com.andrewlalis.perfin.model.*;
|
import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
|
||||||
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import com.andrewlalis.perfin.model.Transaction;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Hyperlink;
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
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.scene.paint.Color;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextFlow;
|
import javafx.scene.text.TextFlow;
|
||||||
import javafx.util.Pair;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@ -25,21 +26,32 @@ import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
* A tile that displays a transaction's basic information.
|
* A tile that displays a transaction's basic information.
|
||||||
*/
|
*/
|
||||||
public class TransactionTile extends BorderPane {
|
public class TransactionTile extends BorderPane {
|
||||||
public TransactionTile(Transaction transaction, Runnable refresh) {
|
public final BooleanProperty selected = new SimpleBooleanProperty(false);
|
||||||
setStyle("""
|
private static final String UNSELECTED_STYLE = """
|
||||||
-fx-border-color: lightgray;
|
-fx-border-color: lightgray;
|
||||||
-fx-border-width: 1px;
|
-fx-border-width: 1px;
|
||||||
-fx-border-style: solid;
|
-fx-border-style: solid;
|
||||||
-fx-border-radius: 5px;
|
-fx-border-radius: 5px;
|
||||||
-fx-padding: 5px;
|
-fx-padding: 5px;
|
||||||
-fx-max-width: 500px;
|
|
||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
""");
|
""";
|
||||||
|
private static final String SELECTED_STYLE = """
|
||||||
|
-fx-border-color: white;
|
||||||
|
-fx-border-width: 1px;
|
||||||
|
-fx-border-style: solid;
|
||||||
|
-fx-border-radius: 5px;
|
||||||
|
-fx-padding: 5px;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
""";
|
||||||
|
|
||||||
|
public TransactionTile(Transaction transaction) {
|
||||||
|
setStyle(UNSELECTED_STYLE);
|
||||||
|
|
||||||
setTop(getHeader(transaction));
|
setTop(getHeader(transaction));
|
||||||
setCenter(getBody(transaction));
|
setCenter(getBody(transaction));
|
||||||
setBottom(getFooter(transaction, refresh));
|
setBottom(getFooter(transaction));
|
||||||
addEventHandler(MouseEvent.MOUSE_CLICKED, event -> router.navigate("transaction", transaction));
|
|
||||||
|
styleProperty().bind(selected.map(value -> value ? SELECTED_STYLE : UNSELECTED_STYLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node getHeader(Transaction transaction) {
|
private Node getHeader(Transaction transaction) {
|
||||||
|
@ -64,31 +76,23 @@ public class TransactionTile extends BorderPane {
|
||||||
accounts.ifCredit(acc -> {
|
accounts.ifCredit(acc -> {
|
||||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||||
link.setOnAction(event -> router.navigate("account", acc));
|
link.setOnAction(event -> router.navigate("account", acc));
|
||||||
TextFlow text = new TextFlow(new Text("Credited from"), link);
|
Text prefix = new Text("Credited from");
|
||||||
Platform.runLater(() -> bodyVBox.getChildren().add(text));
|
prefix.setFill(Color.RED);
|
||||||
|
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||||
});
|
});
|
||||||
accounts.ifDebit(acc -> {
|
accounts.ifDebit(acc -> {
|
||||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||||
link.setOnAction(event -> router.navigate("account", acc));
|
link.setOnAction(event -> router.navigate("account", acc));
|
||||||
TextFlow text = new TextFlow(new Text("Debited to"), link);
|
Text prefix = new Text("Debited to");
|
||||||
Platform.runLater(() -> bodyVBox.getChildren().add(text));
|
prefix.setFill(Color.GREEN);
|
||||||
|
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return bodyVBox;
|
return bodyVBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node getFooter(Transaction transaction, Runnable refresh) {
|
private Node getFooter(Transaction transaction) {
|
||||||
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
|
||||||
Hyperlink deleteLink = new Hyperlink("Delete this transaction");
|
|
||||||
deleteLink.setOnAction(event -> {
|
|
||||||
var confirmResult = new Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to delete this transaction?").showAndWait();
|
|
||||||
if (confirmResult.isPresent() && confirmResult.get() == ButtonType.OK) {
|
|
||||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
|
||||||
repo.delete(transaction.getId());
|
|
||||||
});
|
|
||||||
refresh.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
HBox footerHBox = new HBox(
|
HBox footerHBox = new HBox(
|
||||||
timestampLabel
|
timestampLabel
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,6 +12,7 @@ public interface TransactionRepository extends AutoCloseable {
|
||||||
long insert(Transaction transaction, Map<Long, AccountEntry.Type> accountsMap);
|
long insert(Transaction transaction, Map<Long, AccountEntry.Type> accountsMap);
|
||||||
void addAttachments(long transactionId, List<TransactionAttachment> attachments);
|
void addAttachments(long transactionId, List<TransactionAttachment> attachments);
|
||||||
Page<Transaction> findAll(PageRequest pagination);
|
Page<Transaction> findAll(PageRequest pagination);
|
||||||
|
long countAll();
|
||||||
Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination);
|
Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination);
|
||||||
Map<AccountEntry, Account> findEntriesWithAccounts(long transactionId);
|
Map<AccountEntry, Account> findEntriesWithAccounts(long transactionId);
|
||||||
CreditAndDebitAccounts findLinkedAccounts(long transactionId);
|
CreditAndDebitAccounts findLinkedAccounts(long transactionId);
|
||||||
|
|
|
@ -91,6 +91,11 @@ public record JdbcTransactionRepository(Connection conn) implements TransactionR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long countAll() {
|
||||||
|
return DbUtil.findOne(conn, "SELECT COUNT(id) FROM transaction", Collections.emptyList(), rs -> rs.getLong(1)).orElse(0L);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination) {
|
public Page<Transaction> findAllByAccounts(Set<Long> accountIds, PageRequest pagination) {
|
||||||
String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
String idsStr = accountIds.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
package com.andrewlalis.perfin.data.pagination;
|
package com.andrewlalis.perfin.data.pagination;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public record Page<T>(List<T> items, PageRequest pagination) {}
|
public record Page<T>(List<T> items, PageRequest pagination) {
|
||||||
|
public Stream<T> stream() {
|
||||||
|
return items.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <U> Page<U> map(Function<T, U> mapper) {
|
||||||
|
return new Page<>(items.stream().map(mapper).toList(), pagination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -26,3 +26,24 @@
|
||||||
.std-spacing {
|
.std-spacing {
|
||||||
-fx-spacing: 3px;
|
-fx-spacing: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacing-extra {
|
||||||
|
-fx-spacing: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEBUG BORDERS */
|
||||||
|
.debug-border-1 {
|
||||||
|
-fx-border-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-border-2 {
|
||||||
|
-fx-border-color: lime;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-border-3 {
|
||||||
|
-fx-border-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-border-4 {
|
||||||
|
-fx-border-color: magenta;
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,8 @@
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="com.andrewlalis.perfin.control.TransactionViewController"
|
fx:controller="com.andrewlalis.perfin.control.TransactionViewController"
|
||||||
>
|
>
|
||||||
<top>
|
|
||||||
<HBox styleClass="std-padding,std-spacing">
|
|
||||||
<Label text="Transaction" styleClass="large-text,bold-text"/>
|
|
||||||
</HBox>
|
|
||||||
</top>
|
|
||||||
<center>
|
<center>
|
||||||
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
<VBox styleClass="std-padding,std-spacing">
|
<VBox styleClass="std-padding,std-spacing">
|
||||||
<VBox>
|
<VBox>
|
||||||
<Label text="Amount" styleClass="bold-text"/>
|
<Label text="Amount" styleClass="bold-text"/>
|
||||||
|
@ -25,7 +21,7 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox>
|
<VBox>
|
||||||
<Label text="Description" styleClass="bold-text"/>
|
<Label text="Description" styleClass="bold-text"/>
|
||||||
<Label fx:id="descriptionLabel" wrapText="true"/>
|
<Label fx:id="descriptionLabel" wrapText="true" style="-fx-min-height: 100px;" alignment="TOP_LEFT"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
<Separator/>
|
<Separator/>
|
||||||
<VBox>
|
<VBox>
|
||||||
|
@ -45,12 +41,11 @@
|
||||||
<HBox fx:id="attachmentsHBox" styleClass="std-padding,std-spacing"/>
|
<HBox fx:id="attachmentsHBox" styleClass="std-padding,std-spacing"/>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
</VBox>
|
<Separator/>
|
||||||
</center>
|
<FlowPane styleClass="std-padding, std-spacing">
|
||||||
<right>
|
|
||||||
<VBox styleClass="std-padding,std-spacing">
|
|
||||||
<Label text="Actions" styleClass="bold-text"/>
|
|
||||||
<Button text="Delete" onAction="#deleteTransaction"/>
|
<Button text="Delete" onAction="#deleteTransaction"/>
|
||||||
|
</FlowPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
</right>
|
</ScrollPane>
|
||||||
|
</center>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
|
||||||
<?import javafx.scene.control.ScrollPane?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
|
<?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.TransactionsViewController"
|
fx:controller="com.andrewlalis.perfin.control.TransactionsViewController"
|
||||||
>
|
>
|
||||||
<top>
|
<top>
|
||||||
<HBox style="-fx-padding: 3px;">
|
<HBox styleClass="std-padding,std-spacing">
|
||||||
<Button text="Add Transaction" onAction="#addTransaction"/>
|
<Button text="Add Transaction" onAction="#addTransaction"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<Label text="Center"/>
|
<HBox>
|
||||||
|
<BorderPane fx:id="transactionsListBorderPane" HBox.hgrow="ALWAYS">
|
||||||
|
<center>
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
<VBox fx:id="transactionsVBox" style="-fx-padding: 3px; -fx-spacing: 5px;"/>
|
<VBox fx:id="transactionsVBox" styleClass="std-padding,spacing-extra"/>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
</center>
|
</center>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
<VBox fx:id="detailPanel"/>
|
||||||
|
</HBox>
|
||||||
|
</center>
|
||||||
|
</BorderPane>
|
||||||
|
|
Loading…
Reference in New Issue