diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java index af058a2..10775cc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java @@ -298,7 +298,7 @@ public Transaction sell(BigDecimal amount, List matchingShares = player.getPortfolio().getShares(stockSymbol); if (matchingShares.isEmpty()) { - throw new IllegalArgumentException("Player does not own any shares of this stock!"); + throw new IllegalArgumentException("No owned shares found of this stock!"); } BigDecimal totalOwned = matchingShares.stream() diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java index ccd0d25..fabe231 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameController.java @@ -115,7 +115,7 @@ protected void initInteractions() { getViewElement().selectDefaultStocks(); } catch (IOException | IllegalArgumentException e) { - showAlert(AlertType.ERROR, "Feil ved lasting", "Kunne ikke laste standard aksjedata: " + e.getMessage()); + showAlert(AlertType.ERROR, "Error when loading", "Could not read default stock data: " + e.getMessage()); } }); @@ -185,9 +185,9 @@ private void handleCreateGame() { if (saveGameService.saveExists(name)) { showAlert(AlertType.WARNING, - "Save finnes allerede", - "Det finnes allerede en lagret fil med navnet \"" - + name + "\". Velg et annet filnavn."); + "Save already exists!", + "There already exists a save with the name \"" + + name + "\". Please choose another file name."); return; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java index 48550ac..2e256ff 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java @@ -148,7 +148,7 @@ public File getCustomStockFile() { public void selectDefaultStocks() { this.stockSelection = StockSelection.DEFAULT; this.customStockFile = null; - stockSelectionLabel.setText("Default aksje data valgt"); + stockSelectionLabel.setText("Default stock data chosen"); refreshStockButtonStyles(); refreshCreateButtonState(); } @@ -165,7 +165,7 @@ public void selectCustomStockFile(final File file) { } this.stockSelection = StockSelection.CUSTOM; this.customStockFile = file; - stockSelectionLabel.setText("Egen fil: " + file.getName()); + stockSelectionLabel.setText("Custom file: " + file.getName()); refreshStockButtonStyles(); refreshCreateButtonState(); } @@ -185,7 +185,7 @@ public void resetFields() { this.stockSelection = StockSelection.NONE; this.customStockFile = null; if (stockSelectionLabel != null) { - stockSelectionLabel.setText("Ingen aksje data valgt"); + stockSelectionLabel.setText("No stock data chosen"); } refreshStockButtonStyles(); refreshCreateButtonState(); @@ -198,15 +198,15 @@ protected void initLayout() { title.getStyleClass().add("create-game-title"); // Row 1 - filename - Label fileNameLabel = new Label("Filnavn"); + Label fileNameLabel = new Label("User name"); fileNameLabel.getStyleClass().add("create-game-label"); fileNameField = new TextField(); - fileNameField.setPromptText("Skriv inn filnavn..."); + fileNameField.setPromptText("Write username..."); fileNameField.getStyleClass().add("create-game-input"); VBox fileNameRow = new VBox(6, fileNameLabel, fileNameField); // Row 2 - starting capital - Label capitalLabel = new Label("Startkapital"); + Label capitalLabel = new Label("Start capital"); capitalLabel.getStyleClass().add("create-game-label"); startingCapitalChoice = new ChoiceBox<>(); startingCapitalChoice.getItems().addAll( @@ -221,11 +221,11 @@ protected void initLayout() { VBox capitalRow = new VBox(6, capitalLabel, startingCapitalChoice); // Row 3 - stock data choice (two buttons side by side) - Label stockLabel = new Label("Aksje data"); + Label stockLabel = new Label("Stock data"); stockLabel.getStyleClass().add("create-game-label"); - useDefaultStocksButton = new Button("Bruk default aksje data"); - chooseStockFileButton = new Button("Velg egen aksje fil"); + useDefaultStocksButton = new Button("Use default stock data"); + chooseStockFileButton = new Button("Choose custom stock data file"); useDefaultStocksButton.setMaxWidth(Double.MAX_VALUE); chooseStockFileButton.setMaxWidth(Double.MAX_VALUE); @@ -236,14 +236,14 @@ protected void initLayout() { useDefaultStocksButton, chooseStockFileButton); stockButtonsRow.setAlignment(Pos.CENTER); - stockSelectionLabel = new Label("Ingen aksje data valgt"); + stockSelectionLabel = new Label("No stock data chosen"); stockSelectionLabel.getStyleClass().add("create-game-selection-label"); VBox stockRow = new VBox(6, stockLabel, stockButtonsRow, stockSelectionLabel); // Bottom row - cancel (left) / create-game (right) - cancelButton = new Button("Avbryt"); + cancelButton = new Button("Cancel"); createGameButton = new Button("Create game"); Region bottomSpacer = new Region(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java index 0fa8f8c..52f005c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard; import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; +import edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException; import edu.ntnu.idi.idatt2003.g40.mappe.model.*; import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator; import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator; @@ -244,14 +245,19 @@ protected void initInteractions() { if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText()) && Float.parseFloat(getViewElement().getQuantityInputField().getText()) > 0) { BigDecimal amountToBuy = new BigDecimal(getViewElement().getQuantityInputField().getText()); - Transaction purchase = exchange.buy( - getViewElement().getCurrentStock().getSymbol(), - amountToBuy, - player - ); - if (purchase.isCommited()) { - getViewElement().addOwnedShares(purchase.getShare().getQuantity().floatValue()); - updatePreviews(); + try { + Transaction purchase = exchange.buy( + getViewElement().getCurrentStock().getSymbol(), + amountToBuy, + player + ); + if (purchase.isCommited()) { + getViewElement().addOwnedShares(purchase.getShare().getQuantity().floatValue()); + displayTransactionReport(purchase, "Purchase successful!", null); + updatePreviews(); + } + } catch (NotEnoughMoneyException e) { + displayTransactionReport(null, "Purchase failed!", e.getMessage()); } } }); @@ -259,14 +265,19 @@ protected void initInteractions() { getViewElement().setOnAction(DashBoardActions.SELL_SHARES, () -> { if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText()) && Float.parseFloat(getViewElement().getQuantityInputField().getText()) > 0) { - Transaction sale = exchange.sell( - new BigDecimal(getViewElement().getQuantityInputField().getText()), - getViewElement().getCurrentStock().getSymbol(), - player); + try { + Transaction sale = exchange.sell( + new BigDecimal(getViewElement().getQuantityInputField().getText()), + getViewElement().getCurrentStock().getSymbol(), + player); if(sale.isCommited()) { getViewElement().addOwnedShares(-sale.getShare().getQuantity().floatValue()); + displayTransactionReport(sale, "Sale successful!", null); updatePreviews(); + } + } catch (IllegalArgumentException e) { + displayTransactionReport(null, "Sale failed!", e.getMessage()); } } }); @@ -379,7 +390,33 @@ public void handleEvent(final EventData data) { player.getPortfolio().getTotalShareQuantityBySymbol(s.getSymbol()).floatValue() ); } - updatePreviews(); } + + /** + * Method for displaying a report after performing a transaction attempt (buy/sell). + * + * @param transaction the transaction that occurred, or null if an error stopped execution. + * @param title the title header of the dialog window. + * @param errorMsg the error message to show if the transaction failed. + * */ + public void displayTransactionReport(final Transaction transaction, final String title, final String errorMsg) { + if (transaction != null && transaction.isCommited()) { + var calc = transaction.getCalculator(); + float tax = calc.calculateTax().floatValue(); + + getViewElement().showTransactionReport( + title, + transaction.getShare().getStock().getSymbol(), + transaction.getShare().getQuantity().floatValue(), + calc.calculateGross().floatValue(), + calc.calculateCommission().floatValue(), + tax, + calc.calculateTotal().floatValue(), + null + ); + } else { + getViewElement().showTransactionReport(title, "", 0f, 0f, 0f, 0f, 0f, errorMsg); + } + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java index dd69d82..1079fcf 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardView.java @@ -12,7 +12,10 @@ import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; import javafx.scene.control.ComboBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.Separator; @@ -659,7 +662,68 @@ public ComboBox getTimeRangeSelector() { * @param amount the amount of shares to show. * */ private String formatShares(final float amount) { - String formatted = String.format(java.util.Locale.US, "%.3f", amount); - return formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); + String formatted = String.format(java.util.Locale.of("no", "NO"), "%.3f", amount); + return formatted.replaceAll("0+$", "").replaceAll(",$", ""); + } + + /** + * Displays a clean transaction report pop-up detailing the financial breakdown. + * + * @param title The title of the dialog box + * (e.g., "Purchase Confirmed"). + * @param symbol The stock ticker symbol. + * @param qty The amount of shares processed. + * @param gross The gross value of the transaction. + * @param commission The commission fee charged. + * @param tax The capital gains tax charged. + * @param total The net total amount. + * @param errorMessage Optional error message if transaction did not + * succesfully commit. + */ + public void showTransactionReport(final String title, + final String symbol, + final float qty, + final float gross, + final float commission, + final float tax, + final float total, + final String errorMessage) { + Dialog dialog = new Dialog<>(); + dialog.setTitle(title); + + DialogPane dialogPane = dialog.getDialogPane(); + dialogPane.getButtonTypes().add(ButtonType.CLOSE); + + //NOTE: Requires hard coding styling due to DialogPane behavior. + VBox container = new VBox(10); + container.setStyle("-fx-padding: 20; -fx-min-width: 320;"); + + if (errorMessage != null && !errorMessage.isEmpty()) { + Label errorLabel = new Label(errorMessage); + errorLabel.setStyle("-fx-text-fill: red; -fx-font-weight: bold; -fx-font-size: 14px;"); + container.getChildren().add(errorLabel); + } else { + Label headerLabel = new Label(symbol + " Transaction Summary"); + headerLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;"); + container.getChildren().addAll(headerLabel, new Separator()); + + String[][] rows = { + {"Quantity:", String.format("%.3f", qty)}, + {"Gross:", String.format("%.2f NOK", gross)}, + {"Tax:", String.format("%.2f NOK", tax)}, + {"Commission fee:", String.format("%.2f NOK", commission)}, + {"Total:", String.format("%.2f NOK", total)} + }; + + for (String[] rowData : rows) { + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + HBox row = new HBox(new Label(rowData[0]), spacer, new Label(rowData[1])); + container.getChildren().add(row); + } + } + + dialogPane.setContent(container); + dialog.showAndWait(); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java index e95e0e1..874bd2e 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/financialsummary/SummaryView.java @@ -89,7 +89,7 @@ protected void initLayout() { VBox balanceInfo = new VBox(); titleLabel = new Label("Balance/NetWorth"); - balanceLabel = new Label("0$"); + balanceLabel = new Label("0NOK"); statusLabel = new Label(PlayerStatus.NOOB.getDisplayName()); Region spacerL = new Region(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java index c6750d7..67420e2 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java @@ -307,7 +307,7 @@ private VBox buildStockCard(final Stock stock) { VBox tickerBox = new VBox(2, ticker, company); tickerBox.setAlignment(Pos.TOP_LEFT); - Label price = new Label(String.format("$%.2f", stock.getSalesPrice().doubleValue())); + Label price = new Label(String.format("%.2f NOK", stock.getSalesPrice().doubleValue())); price.getStyleClass().add("market-price"); Region headerSpacer = new Region(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsView.java index a7a1c55..b0c5bb3 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsView.java @@ -219,9 +219,17 @@ private VBox buildChartPanel() { balanceChartPane = new Pane(); balanceChartPane.setMinHeight(120); VBox.setVgrow(balanceChartPane, Priority.ALWAYS); - // Re-render whenever the area is resized. - balanceChartPane.widthProperty().addListener((obs, o, n) -> renderBalanceChart()); - balanceChartPane.heightProperty().addListener((obs, o, n) -> renderBalanceChart()); + // Re-render whenever the area is resized. (Ignores changes under 2 pixels) + balanceChartPane.widthProperty().addListener((obs, o, n) -> { + if (Math.abs(n.doubleValue() - o.doubleValue()) > 2.0) { + renderBalanceChart(); + } + }); + balanceChartPane.heightProperty().addListener((obs, o, n) -> { + if (Math.abs(n.doubleValue() - o.doubleValue()) > 2.0) { + renderBalanceChart(); + } + }); VBox panel = new VBox(6, title, balanceChartPane); panel.getStyleClass().add("stats-panel"); @@ -417,7 +425,7 @@ private void renderBalanceChart() { balanceChartPane.getChildren().add(grid); double v = min + range * i / yTicks; - Text yLabel = new Text(0, y + 3, "$" + (long) v); + Text yLabel = new Text(0, y + 3, "NOK" + (long) v); yLabel.getStyleClass().add("stats-chart-axis"); yLabel.setX(padL - 4 - yLabel.getLayoutBounds().getWidth()); balanceChartPane.getChildren().add(yLabel); @@ -580,7 +588,7 @@ private HBox buildHoldingRow(final HoldingData h) { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - Label value = new Label(String.format("$%.2f", h.getValue().doubleValue())); + Label value = new Label(String.format("%.2f NOK", h.getValue().doubleValue())); value.getStyleClass().add("stats-holding-value"); boolean up = h.getPnl().signum() >= 0; @@ -616,11 +624,11 @@ private static BigDecimal sumCost(final List holdings) { } private static String formatMoney(final BigDecimal v) { - return "$" + (long) v.doubleValue(); + return (long) v.doubleValue() + "NOK"; } private static String formatSigned(final double v) { - return (v >= 0 ? "+" : "") + "$" + (long) v; + return (v >= 0 ? "+" : "") + (long) v + "NOK"; } private static String formatSignedPct(final double v) {