Add Transaction Properties #15
|
@ -93,6 +93,8 @@ public class PerfinApp extends Application {
|
|||
router.map("balance-record", PerfinApp.class.getResource("/balance-record-view.fxml"));
|
||||
router.map("vendors", PerfinApp.class.getResource("/vendors-view.fxml"));
|
||||
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"));
|
||||
|
||||
// Help pages.
|
||||
helpRouter.map("home", PerfinApp.class.getResource("/help-pages/home.fxml"));
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.TransactionCategoryRepository;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.view.BindingUtil;
|
||||
import com.andrewlalis.perfin.view.component.CategoryTile;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class CategoriesViewController implements RouteSelectionListener {
|
||||
@FXML public VBox categoriesVBox;
|
||||
private final ObservableList<TransactionCategoryRepository.CategoryTreeNode> categoryTreeNodes = FXCollections.observableArrayList();
|
||||
|
||||
@FXML public void initialize() {
|
||||
BindingUtil.mapContent(categoriesVBox.getChildren(), categoryTreeNodes, node -> new CategoryTile(node, this::refreshCategories));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
refreshCategories();
|
||||
}
|
||||
|
||||
private void refreshCategories() {
|
||||
Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
TransactionCategoryRepository.class,
|
||||
TransactionCategoryRepository::findTree
|
||||
).thenAccept(nodes -> Platform.runLater(() -> categoryTreeNodes.setAll(nodes)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package com.andrewlalis.perfin.control;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
import com.andrewlalis.perfin.data.TransactionCategoryRepository;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.TransactionCategory;
|
||||
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ColorPicker;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class EditCategoryController implements RouteSelectionListener {
|
||||
public record CategoryRouteContext(TransactionCategory category) implements RouteContext {}
|
||||
public record AddSubcategoryRouteContext(TransactionCategory parent) implements RouteContext {}
|
||||
private sealed interface RouteContext permits AddSubcategoryRouteContext, CategoryRouteContext {}
|
||||
|
||||
private TransactionCategory category;
|
||||
private TransactionCategory parent;
|
||||
|
||||
@FXML public TextField nameField;
|
||||
@FXML public ColorPicker colorPicker;
|
||||
|
||||
@FXML public Button saveButton;
|
||||
|
||||
@FXML public void initialize() {
|
||||
var nameValid = new ValidationApplier<>(new PredicateValidator<String>()
|
||||
.addTerminalPredicate(s -> s != null && !s.isBlank(), "Name is required.")
|
||||
.addPredicate(s -> s.strip().length() <= TransactionCategory.NAME_MAX_LENGTH, "Name is too long.")
|
||||
.addAsyncPredicate(
|
||||
s -> {
|
||||
if (Profile.getCurrent() == null) return CompletableFuture.completedFuture(false);
|
||||
return Profile.getCurrent().dataSource().mapRepoAsync(
|
||||
TransactionCategoryRepository.class,
|
||||
repo -> {
|
||||
var categoryByName = repo.findByName(s).orElse(null);
|
||||
if (this.category != null) {
|
||||
return this.category.equals(categoryByName) || categoryByName == null;
|
||||
}
|
||||
return categoryByName == null;
|
||||
}
|
||||
);
|
||||
},
|
||||
"Category with this name already exists."
|
||||
)
|
||||
).validatedInitially().attachToTextField(nameField);
|
||||
|
||||
saveButton.disableProperty().bind(nameValid.not());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRouteSelected(Object context) {
|
||||
this.category = null;
|
||||
this.parent = null;
|
||||
if (context instanceof RouteContext ctx) {
|
||||
switch (ctx) {
|
||||
case CategoryRouteContext(var cat):
|
||||
this.category = cat;
|
||||
nameField.setText(cat.getName());
|
||||
colorPicker.setValue(cat.getColor());
|
||||
break;
|
||||
case AddSubcategoryRouteContext(var par):
|
||||
this.parent = par;
|
||||
nameField.setText(null);
|
||||
colorPicker.setValue(parent.getColor());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
nameField.setText(null);
|
||||
colorPicker.setValue(Color.WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML public void save() {
|
||||
final String name = nameField.getText().strip();
|
||||
final Color color = colorPicker.getValue();
|
||||
if (this.category == null && this.parent == null) {
|
||||
// New top-level category.
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionCategoryRepository.class,
|
||||
repo -> repo.insert(name, color)
|
||||
);
|
||||
} else if (this.category == null) {
|
||||
// New subcategory.
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionCategoryRepository.class,
|
||||
repo -> repo.insert(parent.id, name, color)
|
||||
);
|
||||
} else if (this.parent == null) {
|
||||
// Save edits to an existing category.
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionCategoryRepository.class,
|
||||
repo -> repo.update(category.id, name, color)
|
||||
);
|
||||
}
|
||||
router.replace("categories");
|
||||
}
|
||||
|
||||
@FXML public void cancel() {
|
||||
router.navigateBackAndClear();
|
||||
}
|
||||
}
|
|
@ -62,7 +62,9 @@ public class EditTransactionController implements RouteSelectionListener {
|
|||
@FXML public ComboBox<String> vendorComboBox;
|
||||
@FXML public Hyperlink vendorsHyperlink;
|
||||
@FXML public ComboBox<String> categoryComboBox;
|
||||
@FXML public Hyperlink categoriesHyperlink;
|
||||
@FXML public ComboBox<String> tagsComboBox;
|
||||
@FXML public Hyperlink tagsHyperlink;
|
||||
@FXML public Button addTagButton;
|
||||
@FXML public VBox tagsVBox;
|
||||
private final ObservableList<String> selectedTags = FXCollections.observableArrayList();
|
||||
|
@ -92,6 +94,8 @@ public class EditTransactionController implements RouteSelectionListener {
|
|||
initializeTagSelectionUi();
|
||||
// Setup hyperlinks.
|
||||
vendorsHyperlink.setOnAction(event -> router.navigate("vendors"));
|
||||
categoriesHyperlink.setOnAction(event -> router.navigate("categories"));
|
||||
// tagsHyperlink.setOnAction(event -> router.navigate("tags"));
|
||||
|
||||
var formValid = timestampValid.and(amountValid).and(descriptionValid).and(linkedAccountsValid);
|
||||
saveButton.disableProperty().bind(formValid.not());
|
||||
|
|
|
@ -5,15 +5,11 @@ import com.andrewlalis.perfin.data.TransactionVendorRepository;
|
|||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.TransactionVendor;
|
||||
import com.andrewlalis.perfin.view.BindingUtil;
|
||||
import com.andrewlalis.perfin.view.component.VendorTile;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -25,36 +21,7 @@ public class VendorsViewController implements RouteSelectionListener {
|
|||
private final ObservableList<TransactionVendor> vendors = FXCollections.observableArrayList();
|
||||
|
||||
@FXML public void initialize() {
|
||||
BindingUtil.mapContent(vendorsVBox.getChildren(), vendors, this::buildVendorTile);
|
||||
}
|
||||
|
||||
private Node buildVendorTile(TransactionVendor transactionVendor) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.getStyleClass().addAll("tile", "std-spacing");
|
||||
pane.setOnMouseClicked(event -> router.navigate("edit-vendor", transactionVendor));
|
||||
|
||||
Label nameLabel = new Label(transactionVendor.getName());
|
||||
nameLabel.getStyleClass().addAll("bold-text");
|
||||
Label descriptionLabel = new Label(transactionVendor.getDescription());
|
||||
descriptionLabel.setWrapText(true);
|
||||
VBox contentVBox = new VBox(nameLabel, descriptionLabel);
|
||||
contentVBox.getStyleClass().addAll("std-spacing");
|
||||
pane.setCenter(contentVBox);
|
||||
BorderPane.setAlignment(contentVBox, Pos.TOP_LEFT);
|
||||
|
||||
Button removeButton = new Button("Remove");
|
||||
removeButton.setOnAction(event -> {
|
||||
boolean confirm = Popups.confirm(removeButton, "Are you sure you want to remove this vendor? Any transactions with assigned to this vendor will have their vendor field cleared. This cannot be undone.");
|
||||
if (confirm) {
|
||||
Profile.getCurrent().dataSource().useRepo(TransactionVendorRepository.class, repo -> {
|
||||
repo.deleteById(transactionVendor.id);
|
||||
});
|
||||
refreshVendors();
|
||||
}
|
||||
});
|
||||
pane.setRight(removeButton);
|
||||
|
||||
return pane;
|
||||
BindingUtil.mapContent(vendorsVBox.getChildren(), vendors, vendor -> new VendorTile(vendor, this::refreshVendors));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,5 +13,9 @@ public interface TransactionCategoryRepository extends Repository, AutoCloseable
|
|||
List<TransactionCategory> findAll();
|
||||
long insert(long parentId, String name, Color color);
|
||||
long insert(String name, Color color);
|
||||
void update(long id, String name, Color color);
|
||||
void deleteById(long id);
|
||||
|
||||
record CategoryTreeNode(TransactionCategory category, List<CategoryTreeNode> children){}
|
||||
List<CategoryTreeNode> findTree();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import javafx.scene.paint.Color;
|
|||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -69,11 +70,62 @@ public record JdbcTransactionCategoryRepository(Connection conn) implements Tran
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long id, String name, Color color) {
|
||||
DbUtil.doTransaction(conn, () -> {
|
||||
TransactionCategory category = findById(id).orElseThrow();
|
||||
if (!category.getName().equals(name)) {
|
||||
DbUtil.updateOne(
|
||||
conn,
|
||||
"UPDATE transaction_category SET name = ? WHERE id = ?",
|
||||
name,
|
||||
id
|
||||
);
|
||||
}
|
||||
if (!category.getColor().equals(color)) {
|
||||
DbUtil.updateOne(
|
||||
conn,
|
||||
"UPDATE transaction_category SET color = ? WHERE id = ?",
|
||||
ColorUtil.toHex(color),
|
||||
id
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteById(long id) {
|
||||
DbUtil.updateOne(conn, "DELETE FROM transaction_category WHERE id = ?", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryTreeNode> findTree() {
|
||||
List<TransactionCategory> rootCategories = DbUtil.findAll(
|
||||
conn,
|
||||
"SELECT * FROM transaction_category WHERE parent_id IS NULL ORDER BY name ASC",
|
||||
JdbcTransactionCategoryRepository::parseCategory
|
||||
);
|
||||
List<CategoryTreeNode> rootNodes = new ArrayList<>(rootCategories.size());
|
||||
for (var category : rootCategories) {
|
||||
rootNodes.add(findTreeRecursive(category));
|
||||
}
|
||||
return rootNodes;
|
||||
}
|
||||
|
||||
private CategoryTreeNode findTreeRecursive(TransactionCategory root) {
|
||||
CategoryTreeNode node = new CategoryTreeNode(root, new ArrayList<>());
|
||||
List<TransactionCategory> childCategories = DbUtil.findAll(
|
||||
conn,
|
||||
"SELECT * FROM transaction_category WHERE parent_id = ? ORDER BY name ASC",
|
||||
List.of(root.id),
|
||||
JdbcTransactionCategoryRepository::parseCategory
|
||||
);
|
||||
for (var childCategory : childCategories) {
|
||||
node.children().add(findTreeRecursive(childCategory));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
conn.close();
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package com.andrewlalis.perfin.view.component;
|
||||
|
||||
import com.andrewlalis.perfin.control.EditCategoryController;
|
||||
import com.andrewlalis.perfin.control.Popups;
|
||||
import com.andrewlalis.perfin.data.TransactionCategoryRepository;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class CategoryTile extends VBox {
|
||||
public CategoryTile(
|
||||
TransactionCategoryRepository.CategoryTreeNode treeNode,
|
||||
Runnable categoriesRefresh
|
||||
) {
|
||||
this.getStyleClass().addAll("tile", "hand-cursor");
|
||||
this.setStyle("-fx-border-width: 1px; -fx-border-color: grey;");
|
||||
this.setOnMouseClicked(event -> {
|
||||
event.consume();
|
||||
router.navigate(
|
||||
"edit-category",
|
||||
new EditCategoryController.CategoryRouteContext(treeNode.category())
|
||||
);
|
||||
});
|
||||
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.getStyleClass().addAll("std-padding");
|
||||
Label nameLabel = new Label(treeNode.category().getName());
|
||||
nameLabel.getStyleClass().addAll("bold-text");
|
||||
Circle colorCircle = new Circle(10, treeNode.category().getColor());
|
||||
HBox contentBox = new HBox(colorCircle, nameLabel);
|
||||
contentBox.getStyleClass().addAll("std-spacing");
|
||||
borderPane.setLeft(contentBox);
|
||||
|
||||
Button addChildButton = new Button("Add Subcategory");
|
||||
addChildButton.setOnAction(event -> router.navigate(
|
||||
"edit-category",
|
||||
new EditCategoryController.AddSubcategoryRouteContext(treeNode.category())
|
||||
));
|
||||
Button removeButton = new Button("Remove");
|
||||
removeButton.setOnAction(event -> {
|
||||
boolean confirm = Popups.confirm(removeButton, "Are you sure you want to remove this category? It will permanently remove the category from all linked transactions, and all subcategories will also be removed. This cannot be undone.");
|
||||
if (confirm) {
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionCategoryRepository.class,
|
||||
repo -> repo.deleteById(treeNode.category().id)
|
||||
);
|
||||
categoriesRefresh.run();
|
||||
}
|
||||
});
|
||||
HBox buttonsBox = new HBox(addChildButton, removeButton);
|
||||
buttonsBox.getStyleClass().addAll("std-spacing");
|
||||
borderPane.setRight(buttonsBox);
|
||||
|
||||
this.getChildren().add(borderPane);
|
||||
for (var child : treeNode.children()) {
|
||||
this.getChildren().add(new CategoryTile(child, categoriesRefresh));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.andrewlalis.perfin.view.component;
|
||||
|
||||
import com.andrewlalis.perfin.control.Popups;
|
||||
import com.andrewlalis.perfin.data.TransactionVendorRepository;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.model.TransactionVendor;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class VendorTile extends BorderPane {
|
||||
public VendorTile(TransactionVendor vendor, Runnable vendorRefresh) {
|
||||
this.getStyleClass().addAll("tile", "std-spacing", "hand-cursor");
|
||||
this.setOnMouseClicked(event -> router.navigate("edit-vendor", vendor));
|
||||
|
||||
Label nameLabel = new Label(vendor.getName());
|
||||
nameLabel.getStyleClass().addAll("bold-text");
|
||||
Label descriptionLabel = new Label(vendor.getDescription());
|
||||
descriptionLabel.setWrapText(true);
|
||||
VBox contentVBox = new VBox(nameLabel, descriptionLabel);
|
||||
contentVBox.getStyleClass().addAll("std-spacing");
|
||||
this.setCenter(contentVBox);
|
||||
BorderPane.setAlignment(contentVBox, Pos.TOP_LEFT);
|
||||
|
||||
this.setRight(getRemoveButton(vendor, vendorRefresh));
|
||||
}
|
||||
|
||||
private Button getRemoveButton(TransactionVendor transactionVendor, Runnable vendorRefresh) {
|
||||
Button removeButton = new Button("Remove");
|
||||
removeButton.setOnAction(event -> {
|
||||
boolean confirm = Popups.confirm(removeButton, "Are you sure you want to remove this vendor? Any transactions assigned to this vendor will have their vendor field cleared. This cannot be undone.");
|
||||
if (confirm) {
|
||||
Profile.getCurrent().dataSource().useRepo(
|
||||
TransactionVendorRepository.class,
|
||||
repo -> repo.deleteById(transactionVendor.id)
|
||||
);
|
||||
vendorRefresh.run();
|
||||
}
|
||||
});
|
||||
return removeButton;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.CategoriesViewController"
|
||||
>
|
||||
<top>
|
||||
<Label text="Transaction Categories" styleClass="large-font,bold-text,std-padding"/>
|
||||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<HBox styleClass="std-padding, std-spacing" VBox.vgrow="NEVER">
|
||||
<Button text="Add Category"/>
|
||||
</HBox>
|
||||
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
||||
<VBox fx:id="categoriesVBox" styleClass="tile-container"/>
|
||||
</ScrollPane>
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.andrewlalis.perfin.view.component.PropertiesPane?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.EditCategoryController"
|
||||
>
|
||||
<top>
|
||||
<Label text="Edit Transaction Category" styleClass="bold-text,large-font,std-padding"/>
|
||||
</top>
|
||||
<center>
|
||||
<VBox>
|
||||
<PropertiesPane hgap="5" vgap="5" styleClass="std-padding" maxWidth="500">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER" halignment="LEFT" minWidth="150"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" halignment="RIGHT"/>
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="Name" labelFor="${nameField}"/>
|
||||
<TextField fx:id="nameField"/>
|
||||
|
||||
<Label text="Color" labelFor="${colorPicker}"/>
|
||||
<ColorPicker fx:id="colorPicker"/>
|
||||
</PropertiesPane>
|
||||
<HBox styleClass="std-padding, std-spacing">
|
||||
<Button text="Save" fx:id="saveButton" onAction="#save"/>
|
||||
<Button text="Cancel" onAction="#cancel"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
|
@ -68,13 +68,13 @@
|
|||
|
||||
<VBox>
|
||||
<Label text="Category" labelFor="${categoryComboBox}" styleClass="bold-text"/>
|
||||
<Hyperlink text="Manage categories" styleClass="small-font"/>
|
||||
<Hyperlink fx:id="categoriesHyperlink" text="Manage categories" styleClass="small-font"/>
|
||||
</VBox>
|
||||
<ComboBox fx:id="categoryComboBox" editable="true" maxWidth="Infinity"/>
|
||||
|
||||
<VBox>
|
||||
<Label text="Tags" labelFor="${tagsComboBox}" styleClass="bold-text"/>
|
||||
<Hyperlink text="Manage tags" styleClass="small-font"/>
|
||||
<Hyperlink fx:id="tagsHyperlink" text="Manage tags" styleClass="small-font"/>
|
||||
</VBox>
|
||||
<VBox maxWidth="Infinity">
|
||||
<HBox styleClass="std-spacing">
|
||||
|
|
|
@ -3,10 +3,7 @@
|
|||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.andrewlalis.perfin.control.VendorsViewController"
|
||||
|
|
Loading…
Reference in New Issue