Added create-balance-record.fxml and associated logic.

This commit is contained in:
Andrew Lalis 2023-12-31 11:48:59 -05:00
parent e42f9507db
commit b477e9ab3c
16 changed files with 277 additions and 85 deletions

View File

@ -26,9 +26,9 @@
inkscape:pagecheckerboard="1" inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050" inkscape:deskcolor="#505050"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:zoom="5.6568543" inkscape:zoom="4"
inkscape:cx="3.2703689" inkscape:cx="-0.99999999"
inkscape:cy="25.102291" inkscape:cy="38.25"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1025" inkscape:window-height="1025"
inkscape:window-x="1080" inkscape:window-x="1080"
@ -54,25 +54,25 @@
inkscape:label="Layer 1" inkscape:label="Layer 1"
inkscape:groupmode="layer" inkscape:groupmode="layer"
id="layer1"> id="layer1">
<rect <g
id="logo-group">
<path
id="top-right-background"
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round" style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
id="rect1" d="M 4.2333332 0 C 1.8880691 0 2.6202161e-17 1.8880691 0 4.2333332 L 0 12.7 C -1.2533172e-15 15.045264 1.8880691 16.933333 4.2333332 16.933333 L 12.7 16.933333 C 15.045264 16.933333 16.933333 15.045264 16.933333 12.7 L 16.933333 4.2333332 C 16.933333 1.8880691 15.045264 2.6202161e-17 12.7 0 L 4.2333332 0 z " />
width="16.933332"
height="16.933332"
x="-1.7763568e-15"
y="1.7763568e-15" />
<path <path
id="bottom-right-background"
style="fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round" style="fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
d="M -1.3919068e-7,16.933333 16.933333,3.7161866e-7 V 16.933333 H -1.3919068e-7" d="M 15.696199 1.2371338 L 1.2371338 15.696199 C 2.002308 16.461373 3.0607012 16.933333 4.2333332 16.933333 L 12.7 16.933333 C 15.045264 16.933333 16.933333 15.045264 16.933333 12.7 L 16.933333 4.2333332 C 16.933333 3.0607012 16.461373 2.002308 15.696199 1.2371338 z " />
id="path2" />
<path <path
id="path1" id="lower-dollar"
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round" style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
d="m 13.000241,3.9330926 -1.304313,1.304313 c 0.0076,0.032651 0.01631,0.063696 0.02325,0.097152 L 13.398662,5.0162308 C 13.290974,4.6118006 13.157768,4.2511887 13.000237,3.9330929 Z M 9.537919,7.3954134 7.984009,8.9493237 V 13.384195 C 7.108943,13.339155 6.414126,13.117013 5.899382,12.718086 5.591151,12.475354 5.346639,12.158421 5.162993,11.77034 l -1.204061,1.204061 c 0.792283,1.097222 2.132722,1.678716 4.025077,1.741495 v 1.553911 h 1.235584 v -1.553911 c 1.441285,-0.05791 2.548094,-0.389429 3.320211,-0.994254 0.772117,-0.611259 1.158069,-1.437594 1.158069,-2.479952 0,-0.514745 -0.07422,-0.96528 -0.222209,-1.3513385 C 13.327675,9.4978588 13.10605,9.1533177 12.810072,8.8573396 12.514093,8.5613615 12.117989,8.2978085 11.622547,8.0661735 11.178148,7.8584026 10.474116,7.6342452 9.537919,7.3954134 Z M 9.219593,8.8573396 c 0.8815,0.2252008 1.482826,0.4314552 1.804541,0.6180501 0.321715,0.1801606 0.569381,0.4085543 0.743107,0.6852293 0.180161,0.276675 0.270268,0.627468 0.270268,1.052132 0,0.649865 -0.23803,1.164468 -0.714168,1.544092 -0.476139,0.37319 -1.177208,0.588563 -2.103748,0.646472 z" /> d="m 13.000241,3.9330926 -1.304313,1.304313 c 0.0076,0.032651 0.01631,0.063696 0.02325,0.097152 L 13.398662,5.0162308 C 13.290974,4.6118006 13.157768,4.2511887 13.000237,3.9330929 Z M 9.537919,7.3954134 7.984009,8.9493237 V 13.384195 C 7.108943,13.339155 6.414126,13.117013 5.899382,12.718086 5.591151,12.475354 5.346639,12.158421 5.162993,11.77034 l -1.204061,1.204061 c 0.792283,1.097222 2.132722,1.678716 4.025077,1.741495 v 1.553911 h 1.235584 v -1.553911 c 1.441285,-0.05791 2.548094,-0.389429 3.320211,-0.994254 0.772117,-0.611259 1.158069,-1.437594 1.158069,-2.479952 0,-0.514745 -0.07422,-0.96528 -0.222209,-1.3513385 C 13.327675,9.4978588 13.10605,9.1533177 12.810072,8.8573396 12.514093,8.5613615 12.117989,8.2978085 11.622547,8.0661735 11.178148,7.8584026 10.474116,7.6342452 9.537919,7.3954134 Z M 9.219593,8.8573396 c 0.8815,0.2252008 1.482826,0.4314552 1.804541,0.6180501 0.321715,0.1801606 0.569381,0.4085543 0.743107,0.6852293 0.180161,0.276675 0.270268,0.627468 0.270268,1.052132 0,0.649865 -0.23803,1.164468 -0.714168,1.544092 -0.476139,0.37319 -1.177208,0.588563 -2.103748,0.646472 z" />
<path <path
id="text3" id="upper-dollar"
style="font-size:8px;line-height:8.64px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect3);fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round" style="font-size:8px;line-height:8.64px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect3);fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round"
d="M 25.689419 18.184344 L 25.689419 18.692161 C 25.145149 18.710391 24.729758 18.828758 24.4433 19.047508 C 24.159446 19.263654 24.017678 19.565737 24.017678 19.953758 C 24.017678 20.198549 24.064478 20.409564 24.158228 20.586647 C 24.254582 20.763731 24.404358 20.916037 24.607483 21.043641 C 24.813212 21.168641 25.134795 21.284479 25.572295 21.39125 L 25.689419 21.422622 L 25.689419 21.537864 L 26.318335 20.908948 C 26.274814 20.897846 26.235644 20.886962 26.189498 20.875694 L 26.189498 19.211691 C 26.473352 19.229921 26.698597 19.309289 26.865264 19.449914 C 27.023755 19.583641 27.132164 19.779351 27.191748 20.035536 L 27.719644 19.50764 C 27.611975 19.290229 27.475742 19.122396 27.310545 19.004632 C 27.047525 18.814528 26.673873 18.710391 26.189498 18.692161 L 26.189498 18.184344 L 25.689419 18.184344 z M 25.689419 19.203744 L 25.689419 20.817131 C 25.379523 20.736402 25.165978 20.663464 25.048791 20.59836 C 24.931603 20.530652 24.841696 20.447383 24.779196 20.348425 C 24.7193 20.246863 24.68947 20.121855 24.68947 19.973418 C 24.68947 19.736439 24.774108 19.552723 24.943379 19.422515 C 25.112649 19.289703 25.361294 19.216765 25.689419 19.203744 z M 24.431587 22.324898 L 23.767534 22.469421 C 23.822782 22.741711 23.9209 22.973823 24.060345 23.166938 L 24.547666 22.679617 C 24.497867 22.574382 24.458752 22.45654 24.431587 22.324898 z " d="M 25.689419 18.184344 L 25.689419 18.692161 C 25.145149 18.710391 24.729758 18.828758 24.4433 19.047508 C 24.159446 19.263654 24.017678 19.565737 24.017678 19.953758 C 24.017678 20.198549 24.064478 20.409564 24.158228 20.586647 C 24.254582 20.763731 24.404358 20.916037 24.607483 21.043641 C 24.813212 21.168641 25.134795 21.284479 25.572295 21.39125 L 25.689419 21.422622 L 25.689419 21.537864 L 26.318335 20.908948 C 26.274814 20.897846 26.235644 20.886962 26.189498 20.875694 L 26.189498 19.211691 C 26.473352 19.229921 26.698597 19.309289 26.865264 19.449914 C 27.023755 19.583641 27.132164 19.779351 27.191748 20.035536 L 27.719644 19.50764 C 27.611975 19.290229 27.475742 19.122396 27.310545 19.004632 C 27.047525 18.814528 26.673873 18.710391 26.189498 18.692161 L 26.189498 18.184344 L 25.689419 18.184344 z M 25.689419 19.203744 L 25.689419 20.817131 C 25.379523 20.736402 25.165978 20.663464 25.048791 20.59836 C 24.931603 20.530652 24.841696 20.447383 24.779196 20.348425 C 24.7193 20.246863 24.68947 20.121855 24.68947 19.973418 C 24.68947 19.736439 24.774108 19.552723 24.943379 19.422515 C 25.112649 19.289703 25.361294 19.216765 25.689419 19.203744 z M 24.431587 22.324898 L 23.767534 22.469421 C 23.822782 22.741711 23.9209 22.973823 24.060345 23.166938 L 24.547666 22.679617 C 24.497867 22.574382 24.458752 22.45654 24.431587 22.324898 z "
transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)" /> transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)" />
</g> </g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -44,6 +44,7 @@ public class PerfinApp extends Application {
splashScreen.showAndWait(); splashScreen.showAndWait();
if (splashScreen.isStartupSuccessful()) { if (splashScreen.isStartupSuccessful()) {
stage.show(); stage.show();
stage.setMaximized(true);
} }
} }
@ -70,6 +71,7 @@ public class PerfinApp extends Application {
mapResourceRoute("edit-account", "/edit-account.fxml"); mapResourceRoute("edit-account", "/edit-account.fxml");
mapResourceRoute("transactions", "/transactions-view.fxml"); mapResourceRoute("transactions", "/transactions-view.fxml");
mapResourceRoute("create-transaction", "/create-transaction.fxml"); mapResourceRoute("create-transaction", "/create-transaction.fxml");
mapResourceRoute("create-balance-record", "/create-balance-record.fxml");
}); });
} }

View File

@ -54,6 +54,10 @@ public class AccountViewController implements RouteSelectionListener {
router.navigate("edit-account", account); router.navigate("edit-account", account);
} }
@FXML public void goToCreateBalanceRecord() {
router.navigate("create-balance-record", account);
}
@FXML @FXML
public void archiveAccount() { public void archiveAccount() {
var confirmResult = new Alert( var confirmResult = new Alert(

View File

@ -0,0 +1,68 @@
package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
import com.andrewlalis.perfin.data.util.CurrencyUtil;
import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.data.util.FileUtil;
import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.component.FileSelectionArea;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static com.andrewlalis.perfin.PerfinApp.router;
public class CreateBalanceRecordController implements RouteSelectionListener {
@FXML public TextField timestampField;
@FXML public TextField balanceField;
@FXML public VBox attachmentsVBox;
private FileSelectionArea attachmentSelectionArea;
private Account account;
@FXML public void initialize() {
attachmentSelectionArea = new FileSelectionArea(FileUtil::newAttachmentsFileChooser, () -> attachmentsVBox.getScene().getWindow());
attachmentSelectionArea.allowMultiple.set(true);
attachmentsVBox.getChildren().add(attachmentSelectionArea);
}
@Override
public void onRouteSelected(Object context) {
this.account = (Account) context;
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
BigDecimal value = repo.deriveCurrentBalance(account.getId());
Platform.runLater(() -> balanceField.setText(
CurrencyUtil.formatMoneyAsBasicNumber(value, account.getCurrency())
));
});
});
attachmentSelectionArea.clear();
}
@FXML public void save() {
// TODO: Add validation.
Profile.getCurrent().getDataSource().useBalanceRecordRepository(repo -> {
LocalDateTime localTimestamp = LocalDateTime.parse(timestampField.getText(), DateUtil.DEFAULT_DATETIME_FORMAT);
BigDecimal reportedBalance = new BigDecimal(balanceField.getText());
repo.insert(
DateUtil.localToUTC(localTimestamp),
account.getId(),
reportedBalance,
account.getCurrency(),
attachmentSelectionArea.getSelectedFiles()
);
});
router.navigateBackAndClear();
}
@FXML public void cancel() {
router.navigateBackAndClear();
}
}

View File

@ -2,22 +2,17 @@ package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener; import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
import com.andrewlalis.perfin.data.util.DateUtil; import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.data.util.FileUtil;
import com.andrewlalis.perfin.model.Account; import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.CreditAndDebitAccounts; import com.andrewlalis.perfin.model.CreditAndDebitAccounts;
import com.andrewlalis.perfin.model.Profile; import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.AccountComboBoxCellFactory; import com.andrewlalis.perfin.view.AccountComboBoxCellFactory;
import com.andrewlalis.perfin.view.BindingUtil; import com.andrewlalis.perfin.view.component.FileSelectionArea;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.DateTimeException; import java.time.DateTimeException;
@ -45,9 +40,8 @@ public class CreateTransactionController implements RouteSelectionListener {
@FXML public ComboBox<Account> linkCreditAccountComboBox; @FXML public ComboBox<Account> linkCreditAccountComboBox;
@FXML public Label linkedAccountsErrorLabel; @FXML public Label linkedAccountsErrorLabel;
private final ObservableList<File> selectedAttachmentFiles = FXCollections.observableArrayList(); @FXML public VBox attachmentsVBox;
@FXML public VBox selectedFilesVBox; private FileSelectionArea attachmentsSelectionArea;
@FXML public Label noSelectedFilesLabel;
@FXML public void initialize() { @FXML public void initialize() {
// Setup error field validation. // Setup error field validation.
@ -83,40 +77,13 @@ public class CreateTransactionController implements RouteSelectionListener {
updateLinkAccountComboBoxes(newValue); updateLinkAccountComboBoxes(newValue);
}); });
// Show the "no files selected" label when the list is empty. And sync the vbox with the selected files. // Initialize the file selection area.
noSelectedFilesLabel.managedProperty().bind(noSelectedFilesLabel.visibleProperty()); attachmentsSelectionArea = new FileSelectionArea(
var filesListProp = new SimpleListProperty<>(selectedAttachmentFiles); FileUtil::newAttachmentsFileChooser,
noSelectedFilesLabel.visibleProperty().bind(filesListProp.emptyProperty()); () -> attachmentsVBox.getScene().getWindow()
BindingUtil.mapContent(selectedFilesVBox.getChildren(), selectedAttachmentFiles, file -> {
Label filenameLabel = new Label(file.getName());
Button removeButton = new Button("Remove");
removeButton.setOnAction(event -> {
selectedAttachmentFiles.remove(file);
});
AnchorPane fileBox = new AnchorPane(filenameLabel, removeButton);
AnchorPane.setLeftAnchor(filenameLabel, 0.0);
AnchorPane.setRightAnchor(removeButton, 0.0);
return fileBox;
});
}
@FXML public void selectAttachmentFile() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Transaction Attachment(s)");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter(
"Attachment Files",
"*.pdf", "*.docx", "*.odt", "*.html", "*.txt", "*.md", "*.xml", "*.json",
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.bmp", "*.tiff"
)
); );
List<File> files = fileChooser.showOpenMultipleDialog(amountField.getScene().getWindow()); attachmentsSelectionArea.allowMultiple.set(true);
if (files == null) return; attachmentsVBox.getChildren().add(attachmentsSelectionArea);
for (var file : files) {
if (selectedAttachmentFiles.stream().noneMatch(f -> !f.equals(file) && f.getName().equals(file.getName()))) {
selectedAttachmentFiles.add(file);
}
}
} }
@FXML public void save() { @FXML public void save() {
@ -136,7 +103,7 @@ public class CreateTransactionController implements RouteSelectionListener {
Currency currency = currencyChoiceBox.getValue(); Currency currency = currencyChoiceBox.getValue();
String description = descriptionField.getText() == null ? null : descriptionField.getText().strip(); String description = descriptionField.getText() == null ? null : descriptionField.getText().strip();
CreditAndDebitAccounts linkedAccounts = getSelectedAccounts(); CreditAndDebitAccounts linkedAccounts = getSelectedAccounts();
List<Path> attachments = selectedAttachmentFiles.stream().map(File::toPath).toList(); List<Path> attachments = attachmentsSelectionArea.getSelectedFiles();
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
repo.insert( repo.insert(
utcTimestamp, utcTimestamp,
@ -164,7 +131,7 @@ public class CreateTransactionController implements RouteSelectionListener {
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT)); timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
amountField.setText("0"); amountField.setText("0");
descriptionField.setText(null); descriptionField.setText(null);
selectedAttachmentFiles.clear(); attachmentsSelectionArea.clear();
Thread.ofVirtual().start(() -> { Thread.ofVirtual().start(() -> {
Profile.getCurrent().getDataSource().useAccountRepository(repo -> { Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
var currencies = repo.findAllUsedCurrencies().stream() var currencies = repo.findAllUsedCurrencies().stream()

View File

@ -86,7 +86,10 @@ public class TransactionViewController {
"Are you sure you want to delete this transaction? This will " + "Are you sure you want to delete this transaction? This will " +
"permanently remove the transaction and its effects on any linked " + "permanently remove the transaction and its effects on any linked " +
"accounts, as well as remove any attachments from storage within " + "accounts, as well as remove any attachments from storage within " +
"this app." "this app.\n\n" +
"Note that incorrect or missing transactions can cause your " +
"account's balance to be incorrectly reported in Perfin, because " +
"it's derived from the most recent balance-record, and transactions."
); );
if (confirm) { if (confirm) {
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> { Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {

View File

@ -1,17 +1,24 @@
package com.andrewlalis.perfin.data; package com.andrewlalis.perfin.data;
import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.model.AccountEntry; import com.andrewlalis.perfin.model.AccountEntry;
import com.andrewlalis.perfin.model.BalanceRecord; import com.andrewlalis.perfin.model.BalanceRecord;
import com.andrewlalis.perfin.model.history.AccountHistoryItem; import com.andrewlalis.perfin.model.history.AccountHistoryItem;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface AccountHistoryItemRepository extends AutoCloseable { public interface AccountHistoryItemRepository extends AutoCloseable {
void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId); void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId);
void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId); void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId);
void recordText(LocalDateTime timestamp, long accountId, String text); void recordText(LocalDateTime timestamp, long accountId, String text);
List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count); List<AccountHistoryItem> findMostRecentForAccount(long accountId, LocalDateTime utcTimestamp, int count);
default Optional<AccountHistoryItem> getMostRecentForAccount(long accountId) {
var items = findMostRecentForAccount(accountId, DateUtil.nowAsUTC(), 1);
if (items.isEmpty()) return Optional.empty();
return Optional.of(items.getFirst());
}
String getTextItem(long itemId); String getTextItem(long itemId);
AccountEntry getAccountEntryItem(long itemId); AccountEntry getAccountEntryItem(long itemId);
BalanceRecord getBalanceRecordItem(long itemId); BalanceRecord getBalanceRecordItem(long itemId);

View File

@ -14,4 +14,9 @@ public class CurrencyUtil {
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP); BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
return nf.format(displayValue); return nf.format(displayValue);
} }
public static String formatMoneyAsBasicNumber(BigDecimal amount, Currency currency) {
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
return displayValue.toString();
}
} }

View File

@ -1,5 +1,7 @@
package com.andrewlalis.perfin.data.util; package com.andrewlalis.perfin.data.util;
import javafx.stage.FileChooser;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
@ -54,4 +56,17 @@ public class FileUtil {
public static String getTypeSuffix(Path filePath) { public static String getTypeSuffix(Path filePath) {
return getTypeSuffix(filePath.getFileName().toString()); return getTypeSuffix(filePath.getFileName().toString());
} }
public static FileChooser newAttachmentsFileChooser() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Attachments");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter(
"Attachment Files",
"*.pdf", "*.docx", "*.odt", "*.html", "*.txt", "*.md", "*.xml", "*.json",
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.bmp", "*.tiff"
)
);
return fileChooser;
}
} }

View File

@ -42,11 +42,9 @@ public class AccountHistoryItemTile extends BorderPane {
Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency())); Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId()); Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
return new TextFlow( return new TextFlow(
new Text("Entry added with value of "),
amountText,
new Text(", linked with "),
transactionLink, transactionLink,
new Text(".") new Text("posted as a " + entry.getType().name().toLowerCase() + " to this account, with a value of"),
amountText
); );
} }

View File

@ -0,0 +1,96 @@
package com.andrewlalis.perfin.view.component;
import com.andrewlalis.perfin.view.BindingUtil;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import java.io.File;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
/**
* A pane within which a user can select one or more files.
*/
public class FileSelectionArea extends VBox {
public final BooleanProperty allowMultiple = new SimpleBooleanProperty(false);
private final ObservableList<Path> selectedFiles = FXCollections.observableArrayList();
public FileSelectionArea(Supplier<FileChooser> fileChooserSupplier, Supplier<Window> windowSupplier) {
getStyleClass().addAll("std-padding", "std-spacing");
VBox filesVBox = new VBox();
filesVBox.getStyleClass().addAll("std-padding", "std-spacing");
BindingUtil.mapContent(filesVBox.getChildren(), selectedFiles, this::buildFileItem);
ListProperty<Path> selectedFilesProperty = new SimpleListProperty<>(selectedFiles);
Label noFilesLabel = new Label("No files selected.");
noFilesLabel.managedProperty().bind(noFilesLabel.visibleProperty());
noFilesLabel.visibleProperty().bind(selectedFilesProperty.emptyProperty());
Button selectFilesButton = new Button("Select files");
selectFilesButton.setOnAction(event -> onSelectFileClicked(fileChooserSupplier.get(), windowSupplier.get()));
selectFilesButton.disableProperty().bind(
allowMultiple.not().and(selectedFilesProperty.emptyProperty().not())
);
getChildren().addAll(
filesVBox,
noFilesLabel,
selectFilesButton
);
}
public List<Path> getSelectedFiles() {
return Collections.unmodifiableList(selectedFiles);
}
public void clear() {
selectedFiles.clear();
}
private Node buildFileItem(Path path) {
Label filenameLabel = new Label(path.getFileName().toString());
filenameLabel.getStyleClass().addAll("mono-font");
Button removeButton = new Button("Remove");
removeButton.setOnAction(event -> selectedFiles.remove(path));
AnchorPane pane = new AnchorPane(filenameLabel, removeButton);
AnchorPane.setLeftAnchor(filenameLabel, 0.0);
AnchorPane.setTopAnchor(filenameLabel, 0.0);
AnchorPane.setBottomAnchor(filenameLabel, 0.0);
AnchorPane.setRightAnchor(removeButton, 0.0);
return pane;
}
private void onSelectFileClicked(FileChooser fileChooser, Window owner) {
if (allowMultiple.get()) {
var files = fileChooser.showOpenMultipleDialog(owner);
if (files != null) {
for (File file : files) {
Path path = file.toPath();
if (!selectedFiles.contains(path)) {
selectedFiles.add(path);
}
}
}
} else {
File file = fileChooser.showOpenDialog(owner);
if (file != null && !selectedFiles.contains(file.toPath())) {
selectedFiles.add(file.toPath());
}
}
}
}

View File

@ -53,6 +53,7 @@
<VBox styleClass="std-padding,std-spacing"> <VBox styleClass="std-padding,std-spacing">
<Label text="Actions" styleClass="bold-text"/> <Label text="Actions" styleClass="bold-text"/>
<Button text="Edit" onAction="#goToEditPage"/> <Button text="Edit" onAction="#goToEditPage"/>
<Button text="Record Balance" onAction="#goToCreateBalanceRecord"/>
<Button text="Archive" onAction="#archiveAccount"/> <Button text="Archive" onAction="#archiveAccount"/>
<Button text="Delete" onAction="#deleteAccount"/> <Button text="Delete" onAction="#deleteAccount"/>
</VBox> </VBox>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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.CreateBalanceRecordController"
>
<top>
<HBox styleClass="std-padding,std-spacing">
<Label text="Create New Balance Record" styleClass="large-text,bold-text"/>
</HBox>
</top>
<center>
<VBox styleClass="std-padding,std-spacing">
<VBox>
<Label text="Timestamp" labelFor="${timestampField}"/>
<TextField fx:id="timestampField"/>
</VBox>
<VBox>
<Label text="Balance" labelFor="${balanceField}"/>
<TextField fx:id="balanceField"/>
</VBox>
<VBox fx:id="attachmentsVBox">
<Label text="Attachments"/>
</VBox>
</VBox>
</center>
<bottom>
<HBox styleClass="std-padding,std-spacing">
<Button text="Save" onAction="#save"/>
<Button text="Cancel" onAction="#cancel"/>
</HBox>
</bottom>
</BorderPane>

View File

@ -53,21 +53,12 @@
</VBox> </VBox>
</HBox> </HBox>
<Label fx:id="linkedAccountsErrorLabel" styleClass="error-text" wrapText="true"/> <Label fx:id="linkedAccountsErrorLabel" styleClass="error-text" wrapText="true"/>
<Label
text="Every transaction must be linked to one of your accounts. In the case of transfer between accounts, then both the sender and receiver accounts should be linked."
styleClass="small-text"
style="-fx-min-width: 100px; -fx-min-height: 100px; -fx-alignment: top-left"
wrapText="true"
VBox.vgrow="NEVER"
/>
</VBox> </VBox>
<!-- Container for attachments --> <!-- Container for attachments -->
<VBox> <VBox fx:id="attachmentsVBox">
<Label text="Attachments" styleClass="bold-text"/> <Label text="Attachments" styleClass="bold-text"/>
<Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-text" wrapText="true"/> <Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-text" wrapText="true"/>
<VBox fx:id="selectedFilesVBox" styleClass="std-padding,std-spacing" VBox.vgrow="NEVER"/> <!-- FileSelectionArea inserted here! -->
<Label text="No attachments selected." fx:id="noSelectedFilesLabel"/>
<Button text="Select attachments" onAction="#selectAttachmentFile"/>
</VBox> </VBox>
</VBox> </VBox>
</ScrollPane> </ScrollPane>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -47,7 +47,7 @@
</ScrollPane> </ScrollPane>
</VBox> </VBox>
<FlowPane styleClass="std-padding, std-spacing"> <FlowPane styleClass="std-padding, std-spacing">
<Button text="Delete" onAction="#deleteTransaction"/> <Button text="Delete this transaction" onAction="#deleteTransaction"/>
</FlowPane> </FlowPane>
</VBox> </VBox>
</ScrollPane> </ScrollPane>