Added create-balance-record.fxml and associated logic.
This commit is contained in:
parent
e42f9507db
commit
b477e9ab3c
|
@ -26,9 +26,9 @@
|
|||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="3.2703689"
|
||||
inkscape:cy="25.102291"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="-0.99999999"
|
||||
inkscape:cy="38.25"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1025"
|
||||
inkscape:window-x="1080"
|
||||
|
@ -54,25 +54,25 @@
|
|||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||
id="rect1"
|
||||
width="16.933332"
|
||||
height="16.933332"
|
||||
x="-1.7763568e-15"
|
||||
y="1.7763568e-15" />
|
||||
<path
|
||||
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"
|
||||
id="path2" />
|
||||
<path
|
||||
id="path1"
|
||||
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" />
|
||||
<path
|
||||
id="text3"
|
||||
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 "
|
||||
transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)" />
|
||||
<g
|
||||
id="logo-group">
|
||||
<path
|
||||
id="top-right-background"
|
||||
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||
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 " />
|
||||
<path
|
||||
id="bottom-right-background"
|
||||
style="fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||
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 " />
|
||||
<path
|
||||
id="lower-dollar"
|
||||
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" />
|
||||
<path
|
||||
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"
|
||||
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)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -44,6 +44,7 @@ public class PerfinApp extends Application {
|
|||
splashScreen.showAndWait();
|
||||
if (splashScreen.isStartupSuccessful()) {
|
||||
stage.show();
|
||||
stage.setMaximized(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +71,7 @@ public class PerfinApp extends Application {
|
|||
mapResourceRoute("edit-account", "/edit-account.fxml");
|
||||
mapResourceRoute("transactions", "/transactions-view.fxml");
|
||||
mapResourceRoute("create-transaction", "/create-transaction.fxml");
|
||||
mapResourceRoute("create-balance-record", "/create-balance-record.fxml");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,10 @@ public class AccountViewController implements RouteSelectionListener {
|
|||
router.navigate("edit-account", account);
|
||||
}
|
||||
|
||||
@FXML public void goToCreateBalanceRecord() {
|
||||
router.navigate("create-balance-record", account);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void archiveAccount() {
|
||||
var confirmResult = new Alert(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -2,22 +2,17 @@ package com.andrewlalis.perfin.control;
|
|||
|
||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||
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.CreditAndDebitAccounts;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
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.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.time.DateTimeException;
|
||||
|
@ -45,9 +40,8 @@ public class CreateTransactionController implements RouteSelectionListener {
|
|||
@FXML public ComboBox<Account> linkCreditAccountComboBox;
|
||||
@FXML public Label linkedAccountsErrorLabel;
|
||||
|
||||
private final ObservableList<File> selectedAttachmentFiles = FXCollections.observableArrayList();
|
||||
@FXML public VBox selectedFilesVBox;
|
||||
@FXML public Label noSelectedFilesLabel;
|
||||
@FXML public VBox attachmentsVBox;
|
||||
private FileSelectionArea attachmentsSelectionArea;
|
||||
|
||||
@FXML public void initialize() {
|
||||
// Setup error field validation.
|
||||
|
@ -83,40 +77,13 @@ public class CreateTransactionController implements RouteSelectionListener {
|
|||
updateLinkAccountComboBoxes(newValue);
|
||||
});
|
||||
|
||||
// Show the "no files selected" label when the list is empty. And sync the vbox with the selected files.
|
||||
noSelectedFilesLabel.managedProperty().bind(noSelectedFilesLabel.visibleProperty());
|
||||
var filesListProp = new SimpleListProperty<>(selectedAttachmentFiles);
|
||||
noSelectedFilesLabel.visibleProperty().bind(filesListProp.emptyProperty());
|
||||
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"
|
||||
)
|
||||
// Initialize the file selection area.
|
||||
attachmentsSelectionArea = new FileSelectionArea(
|
||||
FileUtil::newAttachmentsFileChooser,
|
||||
() -> attachmentsVBox.getScene().getWindow()
|
||||
);
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(amountField.getScene().getWindow());
|
||||
if (files == null) return;
|
||||
for (var file : files) {
|
||||
if (selectedAttachmentFiles.stream().noneMatch(f -> !f.equals(file) && f.getName().equals(file.getName()))) {
|
||||
selectedAttachmentFiles.add(file);
|
||||
}
|
||||
}
|
||||
attachmentsSelectionArea.allowMultiple.set(true);
|
||||
attachmentsVBox.getChildren().add(attachmentsSelectionArea);
|
||||
}
|
||||
|
||||
@FXML public void save() {
|
||||
|
@ -136,7 +103,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
|||
Currency currency = currencyChoiceBox.getValue();
|
||||
String description = descriptionField.getText() == null ? null : descriptionField.getText().strip();
|
||||
CreditAndDebitAccounts linkedAccounts = getSelectedAccounts();
|
||||
List<Path> attachments = selectedAttachmentFiles.stream().map(File::toPath).toList();
|
||||
List<Path> attachments = attachmentsSelectionArea.getSelectedFiles();
|
||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||
repo.insert(
|
||||
utcTimestamp,
|
||||
|
@ -164,7 +131,7 @@ public class CreateTransactionController implements RouteSelectionListener {
|
|||
timestampField.setText(LocalDateTime.now().format(DateUtil.DEFAULT_DATETIME_FORMAT));
|
||||
amountField.setText("0");
|
||||
descriptionField.setText(null);
|
||||
selectedAttachmentFiles.clear();
|
||||
attachmentsSelectionArea.clear();
|
||||
Thread.ofVirtual().start(() -> {
|
||||
Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||
var currencies = repo.findAllUsedCurrencies().stream()
|
||||
|
|
|
@ -86,7 +86,10 @@ public class TransactionViewController {
|
|||
"Are you sure you want to delete this transaction? This will " +
|
||||
"permanently remove the transaction and its effects on any linked " +
|
||||
"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) {
|
||||
Profile.getCurrent().getDataSource().useTransactionRepository(repo -> {
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
package com.andrewlalis.perfin.data;
|
||||
|
||||
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||
import com.andrewlalis.perfin.model.AccountEntry;
|
||||
import com.andrewlalis.perfin.model.BalanceRecord;
|
||||
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AccountHistoryItemRepository extends AutoCloseable {
|
||||
void recordAccountEntry(LocalDateTime timestamp, long accountId, long entryId);
|
||||
void recordBalanceRecord(LocalDateTime timestamp, long accountId, long recordId);
|
||||
void recordText(LocalDateTime timestamp, long accountId, String text);
|
||||
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);
|
||||
AccountEntry getAccountEntryItem(long itemId);
|
||||
BalanceRecord getBalanceRecordItem(long itemId);
|
||||
|
|
|
@ -14,4 +14,9 @@ public class CurrencyUtil {
|
|||
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
|
||||
return nf.format(displayValue);
|
||||
}
|
||||
|
||||
public static String formatMoneyAsBasicNumber(BigDecimal amount, Currency currency) {
|
||||
BigDecimal displayValue = amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_UP);
|
||||
return displayValue.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.andrewlalis.perfin.data.util;
|
||||
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
|
@ -54,4 +56,17 @@ public class FileUtil {
|
|||
public static String getTypeSuffix(Path filePath) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,9 @@ public class AccountHistoryItemTile extends BorderPane {
|
|||
Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
|
||||
Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
|
||||
return new TextFlow(
|
||||
new Text("Entry added with value of "),
|
||||
amountText,
|
||||
new Text(", linked with "),
|
||||
transactionLink,
|
||||
new Text(".")
|
||||
new Text("posted as a " + entry.getType().name().toLowerCase() + " to this account, with a value of"),
|
||||
amountText
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,6 +53,7 @@
|
|||
<VBox styleClass="std-padding,std-spacing">
|
||||
<Label text="Actions" styleClass="bold-text"/>
|
||||
<Button text="Edit" onAction="#goToEditPage"/>
|
||||
<Button text="Record Balance" onAction="#goToCreateBalanceRecord"/>
|
||||
<Button text="Archive" onAction="#archiveAccount"/>
|
||||
<Button text="Delete" onAction="#deleteAccount"/>
|
||||
</VBox>
|
||||
|
|
|
@ -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>
|
|
@ -53,21 +53,12 @@
|
|||
</VBox>
|
||||
</HBox>
|
||||
<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>
|
||||
<!-- Container for attachments -->
|
||||
<VBox>
|
||||
<VBox fx:id="attachmentsVBox">
|
||||
<Label text="Attachments" styleClass="bold-text"/>
|
||||
<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"/>
|
||||
<Label text="No attachments selected." fx:id="noSelectedFilesLabel"/>
|
||||
<Button text="Select attachments" onAction="#selectAttachmentFile"/>
|
||||
<!-- FileSelectionArea inserted here! -->
|
||||
</VBox>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -47,7 +47,7 @@
|
|||
</ScrollPane>
|
||||
</VBox>
|
||||
<FlowPane styleClass="std-padding, std-spacing">
|
||||
<Button text="Delete" onAction="#deleteTransaction"/>
|
||||
<Button text="Delete this transaction" onAction="#deleteTransaction"/>
|
||||
</FlowPane>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
|
|
Loading…
Reference in New Issue