Added tags view, and cleaned up some other parts of the app.
This commit is contained in:
parent
aaa1081ddf
commit
39794e36a2
|
@ -95,6 +95,7 @@ public class PerfinApp extends Application {
|
|||
router.map("edit-vendor", PerfinApp.class.getResource("/edit-vendor.fxml"));
|
||||
router.map("categories", PerfinApp.class.getResource("/categories-view.fxml"));
|
||||
router.map("edit-category", PerfinApp.class.getResource("/edit-category.fxml"));
|
||||
router.map("tags", PerfinApp.class.getResource("/tags-view.fxml"));
|
||||
|
||||
// Help pages.
|
||||
helpRouter.map("home", PerfinApp.class.getResource("/help-pages/home.fxml"));
|
||||
|
|
|
@ -11,6 +11,8 @@ import javafx.collections.ObservableList;
|
|||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class CategoriesViewController implements RouteSelectionListener {
|
||||
@FXML public VBox categoriesVBox;
|
||||
private final ObservableList<TransactionCategoryRepository.CategoryTreeNode> categoryTreeNodes = FXCollections.observableArrayList();
|
||||
|
@ -24,6 +26,10 @@ public class CategoriesViewController implements RouteSelectionListener {
|
|||
refreshCategories();
|
||||
}
|
||||
|
||||
@FXML public void addCategory() {
|
||||
router.navigate("edit-category");
|
||||
}
|
||||
|
||||
private void refreshCategories() {
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
TransactionCategoryRepository.class,
|
||||
|
|
|
@ -92,10 +92,10 @@ public class EditTransactionController implements RouteSelectionListener {
|
|||
).validatedInitially().attach(descriptionField, descriptionField.textProperty());
|
||||
var linkedAccountsValid = initializeLinkedAccountsValidationUi();
|
||||
initializeTagSelectionUi();
|
||||
// Setup hyperlinks.
|
||||
|
||||
vendorsHyperlink.setOnAction(event -> router.navigate("vendors"));
|
||||
categoriesHyperlink.setOnAction(event -> router.navigate("categories"));
|
||||
// tagsHyperlink.setOnAction(event -> router.navigate("tags"));
|
||||
tagsHyperlink.setOnAction(event -> router.navigate("tags"));
|
||||
|
||||
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
|
||||
saveButton.disableProperty().bind(formValid.not());
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.TransactionRepository;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.view.BindingUtil;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class TagsViewController implements RouteSelectionListener {
|
||||
@FXML public VBox tagsVBox;
|
||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||
|
||||
@FXML public void initialize() {
|
||||
BindingUtil.mapContent(tagsVBox.getChildren(), tags, this::buildTagTile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
refreshTags();
|
||||
}
|
||||
|
||||
private void refreshTags() {
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
TransactionRepository.class,
|
||||
TransactionRepository::findAllTags
|
||||
).thenAccept(strings -> Platform.runLater(() -> tags.setAll(strings)));
|
||||
}
|
||||
|
||||
private Node buildTagTile(String name) {
|
||||
BorderPane tile = new BorderPane();
|
||||
tile.getStyleClass().addAll("tile");
|
||||
Label nameLabel = new Label(name);
|
||||
nameLabel.getStyleClass().addAll("bold-text");
|
||||
Label usagesLabel = new Label();
|
||||
usagesLabel.getStyleClass().addAll("small-font", "secondary-color-text-fill");
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
TransactionRepository.class,
|
||||
repo -> repo.countTagUsages(name)
|
||||
).thenAccept(count -> Platform.runLater(() -> usagesLabel.setText("Tagged transactions: " + count)));
|
||||
VBox contentBox = new VBox(nameLabel, usagesLabel);
|
||||
tile.setLeft(contentBox);
|
||||
Button removeButton = new Button("Remove");
|
||||
removeButton.setOnAction(event -> {
|
||||
boolean confirm = Popups.confirm(removeButton, "Are you sure you want to remove this tag? It will be removed from any transactions. This cannot be undone.");
|
||||
if (confirm) {
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionRepository.class,
|
||||
repo -> repo.deleteTag(name)
|
||||
);
|
||||
refreshTags();
|
||||
}
|
||||
});
|
||||
tile.setRight(removeButton);
|
||||
return tile;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,8 @@ public interface TransactionRepository extends Repository, AutoCloseable {
|
|||
List<Attachment> findAttachments(long transactionId);
|
||||
List<String> findTags(long transactionId);
|
||||
List<String> findAllTags();
|
||||
void deleteTag(String name);
|
||||
long countTagUsages(String name);
|
||||
void delete(long transactionId);
|
||||
void update(
|
||||
long id,
|
||||
|
|
|
@ -246,6 +246,27 @@ public record JdbcTransactionRepository(Connection conn, Path contentDir) implem
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTag(String name) {
|
||||
DbUtil.update(
|
||||
conn,
|
||||
"DELETE FROM transaction_tag WHERE name = ?",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countTagUsages(String name) {
|
||||
return DbUtil.count(
|
||||
conn,
|
||||
"""
|
||||
SELECT COUNT(transaction_id)
|
||||
FROM transaction_tag_join
|
||||
WHERE tag_id = (SELECT id FROM transaction_tag WHERE name = ?)""",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(long transactionId) {
|
||||
DbUtil.doTransaction(conn, () -> {
|
||||
|
|
|
@ -18,7 +18,7 @@ public class CategoryTile extends VBox {
|
|||
TransactionCategoryRepository.CategoryTreeNode treeNode,
|
||||
Runnable categoriesRefresh
|
||||
) {
|
||||
this.getStyleClass().addAll("tile", "hand-cursor");
|
||||
this.getStyleClass().addAll("tile", "spacing-extra", "hand-cursor");
|
||||
this.setStyle("-fx-border-width: 1px; -fx-border-color: grey;");
|
||||
this.setOnMouseClicked(event -> {
|
||||
event.consume();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.CategoriesViewController"
|
||||
|
@ -15,8 +16,14 @@
|
|||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<StyledText maxWidth="500" styleClass="std-padding">
|
||||
Categories are used to group your transactions based on their
|
||||
purpose. It's helpful to categorize transactions in order to get
|
||||
a better view of your spending habits, and it makes it easier to
|
||||
lookup transactions later.
|
||||
</StyledText>
|
||||
<HBox styleClass="std-padding, std-spacing" VBox.vgrow="NEVER">
|
||||
<Button text="Add Category"/>
|
||||
<Button text="Add Category" onAction="#addCategory"/>
|
||||
</HBox>
|
||||
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
||||
<VBox fx:id="categoriesVBox" styleClass="tile-container"/>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.TagsViewController"
|
||||
>
|
||||
<top>
|
||||
<Label text="Transaction Tags" styleClass="large-font,bold-text,std-padding"/>
|
||||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<StyledText maxWidth="500" styleClass="std-padding">
|
||||
Transaction tags are just bits of text that can be applied to a
|
||||
transaction to give it additional meaning or make searching for
|
||||
certain transactions easier.
|
||||
--
|
||||
Tags are automatically created if you add a new one to a
|
||||
transaction, and they'll show up here. When you remove a tag,
|
||||
it will be permanently removed from **all** transactions that it
|
||||
was previously associated with.
|
||||
</StyledText>
|
||||
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
||||
<VBox fx:id="tagsVBox" styleClass="tile-container"/>
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
|
@ -4,6 +4,7 @@
|
|||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.VendorsViewController"
|
||||
|
@ -13,6 +14,13 @@
|
|||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<StyledText maxWidth="500" styleClass="std-padding">
|
||||
Vendors are businesses or other financial entities with which
|
||||
you do transactions. By tagging a vendor on your transactions,
|
||||
it becomes easier to find out just how much money you're
|
||||
spending at certain shops, and how often. It can also make it a
|
||||
lot easier to look up past transactions.
|
||||
</StyledText>
|
||||
<HBox styleClass="std-padding,std-spacing" VBox.vgrow="NEVER">
|
||||
<Button text="Add Vendor" onAction="#addVendor"/>
|
||||
</HBox>
|
||||
|
|
Loading…
Reference in New Issue