From 141b0e21a9f511cf8f27c82dda50276217bd42c8 Mon Sep 17 00:00:00 2001 From: EspenTinius Date: Wed, 27 May 2026 02:30:53 +0200 Subject: [PATCH] =?UTF-8?q?sell=20portfolio=20og=20sell=20portfolio,=20sav?= =?UTF-8?q?e=20og=20quit.=20+=20=C3=B8kt=20min=20with?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ntnu/idi/idatt2003/g40/mappe/Main.java | 43 +++---- .../g40/mappe/utils/ConfigValues.java | 4 +- .../view/ingame/quit/QuitDialogActions.java | 13 ++- .../ingame/quit/QuitDialogController.java | 107 +++++++++++++++++- .../view/ingame/quit/QuitDialogView.java | 31 +++-- .../view/widgets/stats/StatsActions.java | 11 +- .../view/widgets/stats/StatsController.java | 39 +++++++ .../mappe/view/widgets/stats/StatsView.java | 18 ++- .../transactions/TransactionsController.java | 13 +-- .../transactions/TransactionsView.java | 66 +++++++++-- src/main/resources/styles.css | 38 ++++++- 11 files changed, 324 insertions(+), 59 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index fef87da..186a096 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -178,6 +178,29 @@ public void start(final Stage stage) throws Exception { gameEngineController, clickerGame, findStockGame, timeInputsGame ); + // In-game quit confirmation dialog: replaces the previous immediate + // "auto-save and return to main menu" behaviour with a popup that + // lets the player choose Continue / Save / Save and quit to main + // menu / Save and quit game / Sell portfolio, save and quit. + // + // Constructed up here (before the STATE_RESET subscriber below) so + // the subscriber can rebind it to the freshly loaded player/exchange + // alongside the other controllers. + QuitDialogView quitDialogView = new QuitDialogView(); + QuitDialogController quitDialogController = + new QuitDialogController(quitDialogView, eventManager, inGameView, + gameStateLoader, saveGameService, player, exchange); + quitDialogController.setOnExitApplication(() -> { + try { + javafx.application.Platform.exit(); + } finally { + // Fallback in case there are non-daemon background threads + // keeping the JVM alive after JavaFX shuts down. + System.exit(0); + } + }); + topBarController.setOnQuitRequested(quitDialogController::show); + // Adds a generic event subscriber to the event manager // that handles STATE_RESET events. These events are // triggered when a new save file is loaded. @@ -209,6 +232,7 @@ public void handleEvent(final EventData eventData) { dashBoardController.handleStockPoolUpdate(dynamicStocks); statsController.handleContextUpdate(Main.this.exchange, Main.this.player); marketController.handleStockPoolUpdate(Main.this.exchange, Main.this.player, dynamicStocks); + quitDialogController.handleContextUpdate(Main.this.exchange, Main.this.player); if (!dynamicStocks.isEmpty()) { miniGamesController.setActiveStock(dynamicStocks.getFirst()); @@ -222,25 +246,6 @@ public void handleEvent(final EventData eventData) { new InGameSettingsController(inGameSettingsView, eventManager, inGameView); topBarController.setSettingsAction(inGameSettingsController::show); - // In-game quit confirmation dialog: replaces the previous immediate - // "auto-save and return to main menu" behaviour with a popup that - // lets the player choose Continue / Save / Save and quit to main - // menu / Save and quit game. - QuitDialogView quitDialogView = new QuitDialogView(); - QuitDialogController quitDialogController = - new QuitDialogController(quitDialogView, eventManager, inGameView, - gameStateLoader, saveGameService); - quitDialogController.setOnExitApplication(() -> { - try { - javafx.application.Platform.exit(); - } finally { - // Fallback in case there are non-daemon background threads - // keeping the JVM alive after JavaFX shuts down. - System.exit(0); - } - }); - topBarController.setOnQuitRequested(quitDialogController::show); - topBarController.setOnQuitToMainMenu(() -> { System.out.println("[auto-save] Quit triggered, attempting snapshot..."); SaveGame snapshot = gameStateLoader.snapshotActiveSave(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/ConfigValues.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/ConfigValues.java index 504e90e..27432c2 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/ConfigValues.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/ConfigValues.java @@ -7,7 +7,7 @@ public enum ConfigValues { /** * Standard viewport width. */ - VIEWPORT_WIDTH(800), + VIEWPORT_WIDTH(1200), /** * Standard viewport height. @@ -17,7 +17,7 @@ public enum ConfigValues { /** * Minimum viewport width. The window cannot be resized smaller than this. */ - MIN_VIEWPORT_WIDTH(800), + MIN_VIEWPORT_WIDTH(1200), /** * Minimum viewport height. The window cannot be resized smaller than this. diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java index 7de92ca..cc9c7af 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogActions.java @@ -5,8 +5,9 @@ * *

The quit dialog is shown when the player clicks the "Quit" button * from the in-game dashboard, and lets them choose between continuing, - * saving in place, saving and returning to the main menu, or saving - * and exiting the application.

+ * saving in place, saving and returning to the main menu, saving and + * exiting the application, or selling the entire portfolio before + * saving and returning to the main menu.

* */ public enum QuitDialogActions { /** Closes the dialog and returns the player to the game. */ @@ -19,5 +20,11 @@ public enum QuitDialogActions { SAVE_AND_QUIT_TO_MAIN_MENU, /** Saves the current game state and exits the application. */ - SAVE_AND_QUIT_GAME; + SAVE_AND_QUIT_GAME, + + /** + * Sells every share the player owns, then saves the game and returns + * to the main menu. + * */ + SELL_ALL_AND_QUIT; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java index 0d12399..051481d 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogController.java @@ -1,6 +1,9 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.quit; +import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; import edu.ntnu.idi.idatt2003.g40.mappe.service.GameStateLoader; import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService; import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData; @@ -11,10 +14,14 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; import edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView; +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + /** * Controller for {@link QuitDialogView}. * - *

Wires the four dialog buttons to the appropriate save / scene-change + *

Wires the five dialog buttons to the appropriate save / scene-change * / application-exit actions:

*
    *
  • {@code CONTINUE} - hides the overlay; the player stays in the @@ -26,6 +33,10 @@ * hides the overlay, and changes the scene back to the main menu.
  • *
  • {@code SAVE_AND_QUIT_GAME} - snapshots, writes to disk, then * invokes the configured exit-application runnable to close the JVM.
  • + *
  • {@code SELL_ALL_AND_QUIT} - liquidates every position in the + * player's portfolio (via the same exchange API the dashboard uses), + * then snapshots, writes to disk, hides the overlay, and changes the + * scene back to the main menu.
  • *
* *

The save logic mirrors the auto-save hook in {@code Main}: a missing @@ -45,6 +56,24 @@ public final class QuitDialogController /** Persists save games to disk. */ private final SaveGameService saveGameService; + /** + * The {@link Player} whose portfolio is liquidated by the + * "Sell portfolio, save and quit" button. + * + *

Not final because the active player can be swapped out when a + * save is loaded; {@link #handleContextUpdate} is the entry point + * the host application uses to rebind it.

+ * */ + private Player player; + + /** + * The {@link Exchange} used to execute the sell-all transactions. + * + *

Like {@link #player}, this is reassigned when a save is loaded + * via {@link #handleContextUpdate}.

+ * */ + private Exchange exchange; + /** Runnable invoked to close the application. */ private Runnable onExitApplication = () -> { }; @@ -58,6 +87,10 @@ public final class QuitDialogController * @param inGameView the in-game view that hosts this overlay. * @param gameStateLoader the loader used to snapshot the active save. * @param saveGameService the service used to persist saves to disk. + * @param player the active {@link Player}, whose portfolio + * is liquidated by the sell-all-and-quit flow. + * @param exchange the active {@link Exchange}, used to execute + * the sell transactions. * * @throws IllegalArgumentException if any constructor argument is null. * */ @@ -65,16 +98,22 @@ public QuitDialogController(final QuitDialogView view, final EventManager eventManager, final InGameView inGameView, final GameStateLoader gameStateLoader, - final SaveGameService saveGameService) + final SaveGameService saveGameService, + final Player player, + final Exchange exchange) throws IllegalArgumentException { this.inGameView = inGameView; this.gameStateLoader = gameStateLoader; this.saveGameService = saveGameService; + this.player = player; + this.exchange = exchange; super(view, eventManager); eventManager.addSubscriber(this, EventType.SHOW_QUIT_OPTIONS); if (inGameView == null || gameStateLoader == null - || saveGameService == null) { + || saveGameService == null + || player == null + || exchange == null) { throw new IllegalArgumentException( "Invalid QuitDialogController arguments!"); } @@ -124,6 +163,68 @@ protected void initInteractions() { onExitApplication.run(); } }); + + getViewElement().setOnAction(QuitDialogActions.SELL_ALL_AND_QUIT, () -> { + sellEntirePortfolio(); + if (performSave()) { + close(); + changeScene(ViewEnum.MAIN_MENU); + } + }); + } + + /** + * Sells every share the player currently owns, one ticker at a time. + * + *

Uses the same {@link Exchange#sell(BigDecimal, String, Player)} + * entry point as the dashboard's sell button and the stats widget's + * sell-all button, so commissions, taxes and net-worth listeners all + * fire exactly as if the player had sold each ticker manually.

+ * + *

If the portfolio is already empty this is a no-op.

+ * */ + private void sellEntirePortfolio() { + // Snapshot first so we don't iterate over a list that mutates + // while we sell. + Map totalsBySymbol = new LinkedHashMap<>(); + for (Share s : player.getPortfolio().getShares()) { + String symbol = s.getStock().getSymbol(); + totalsBySymbol.merge(symbol, s.getQuantity(), BigDecimal::add); + } + + for (Map.Entry entry : totalsBySymbol.entrySet()) { + if (entry.getValue().signum() <= 0) { + continue; + } + try { + exchange.sell(entry.getValue(), entry.getKey(), player); + } catch (IllegalArgumentException e) { + System.err.println("[quit-dialog] Sell-all skipped " + entry.getKey() + + ": " + e.getMessage()); + } + } + } + + /** + * Rebinds this controller to a new {@link Player} / {@link Exchange} + * pair when a save is loaded. + * + *

Mirrors the {@code handleContextUpdate} hook on + * {@code StatsController} - the host application reassigns its + * own player/exchange fields when a {@code STATE_RESET} event fires, + * and must call this so the sell-all-and-quit flow operates on the + * newly active save instead of the original starting state.

+ * + * @param updatedExchange the current active exchange engine. + * @param updatedPlayer the current active player context profile. + * */ + public void handleContextUpdate(final Exchange updatedExchange, + final Player updatedPlayer) { + if (updatedExchange == null || updatedPlayer == null) { + return; + } + this.exchange = updatedExchange; + this.player = updatedPlayer; } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java index ba9dce1..e2c759a 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ingame/quit/QuitDialogView.java @@ -16,12 +16,13 @@ * top bar. * *

The root of this view is a dimmer {@link StackPane} that covers the - * current center widget. Inside, a centered panel hosts four action buttons + * current center widget. Inside, a centered panel hosts five action buttons * that let the player choose how to leave (or stay in) the game.

* *

Layout: a header row with the title and a close (X) button, a short - * description, and a vertical stack of four buttons: Continue, Save, - * Save and quit to main menu, Save and quit game.

+ * description, and a vertical stack of five buttons: Continue, Save, + * Save and quit to main menu, Save and quit game, Sell portfolio save + * and quit.

* *

This view is purely presentational - the {@link QuitDialogController} * wires the buttons to the actual save/quit logic.

@@ -44,6 +45,11 @@ public final class QuitDialogView /** Saves and exits the application. */ private Button saveAndQuitGameButton; + /** + * Sells the entire portfolio, then saves and returns to the main menu. + * */ + private Button sellAllAndQuitButton; + /** Label used to communicate save status to the player. */ private Label statusLabel; @@ -107,18 +113,21 @@ protected void initLayout() { description.getStyleClass().add("quit-dialog-description"); description.setWrapText(true); - // The four action buttons. Each is full-width inside the panel so the + // The five action buttons. Each is full-width inside the panel so the // longer labels do not overflow. continueButton = new Button("Continue"); saveButton = new Button("Save"); saveAndQuitMainMenuButton = new Button("Save and quit to main menu"); saveAndQuitGameButton = new Button("Save and quit game"); + sellAllAndQuitButton = + new Button("Sell portfolio, save and quit"); continueButton.setFocusTraversable(false); saveButton.setFocusTraversable(false); saveAndQuitMainMenuButton.setFocusTraversable(false); saveAndQuitGameButton.setFocusTraversable(false); + sellAllAndQuitButton.setFocusTraversable(false); continueButton.getStyleClass().addAll( "quit-dialog-button", "quit-dialog-button-primary"); @@ -126,17 +135,21 @@ protected void initLayout() { saveAndQuitMainMenuButton.getStyleClass().add("quit-dialog-button"); saveAndQuitGameButton.getStyleClass().addAll( "quit-dialog-button", "quit-dialog-button-danger"); + sellAllAndQuitButton.getStyleClass().addAll( + "quit-dialog-button", "quit-dialog-button-danger"); continueButton.setMaxWidth(Double.MAX_VALUE); saveButton.setMaxWidth(Double.MAX_VALUE); saveAndQuitMainMenuButton.setMaxWidth(Double.MAX_VALUE); saveAndQuitGameButton.setMaxWidth(Double.MAX_VALUE); + sellAllAndQuitButton.setMaxWidth(Double.MAX_VALUE); VBox buttonColumn = new VBox(10, continueButton, saveButton, saveAndQuitMainMenuButton, - saveAndQuitGameButton); + saveAndQuitGameButton, + sellAllAndQuitButton); buttonColumn.setAlignment(Pos.CENTER); // Status label - hidden until the controller writes something into it. @@ -156,9 +169,9 @@ protected void initLayout() { // shrink when the status label appears/disappears, and so it stays // visually aligned with the settings overlay (which also has a // fixed height). - panel.setMinHeight(420); - panel.setPrefHeight(420); - panel.setMaxHeight(420); + panel.setMinHeight(480); + panel.setPrefHeight(480); + panel.setMaxHeight(480); panel.getStyleClass().add("quit-dialog-panel"); // Dimmer wrapper. @@ -172,6 +185,8 @@ protected void initLayout() { saveAndQuitMainMenuButton); registerButton(QuitDialogActions.SAVE_AND_QUIT_GAME, saveAndQuitGameButton); + registerButton(QuitDialogActions.SELL_ALL_AND_QUIT, + sellAllAndQuitButton); // The X button reuses the CONTINUE action so we don't need a // dedicated enum entry just for "close". closeButton.setOnAction(e -> continueButton.fire()); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java index 761e670..23f8afc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsActions.java @@ -3,11 +3,14 @@ /** * Enum representing all interactable actions in {@link StatsView}. * - *

The stats widget is mostly an informational dashboard, so it currently - * has no top-level buttons of its own. {@code SELECT_HOLDING} is reserved - * for future use when individual holdings rows become clickable.

+ *

{@code SELECT_HOLDING} is reserved for future use when individual + * holdings rows become clickable. {@code SELL_ENTIRE_PORTFOLIO} is the + * "sell everything" button shown at the bottom of the allocation panel.

* */ public enum StatsActions { /** Reserved for future holding-row click handling. */ - SELECT_HOLDING; + SELECT_HOLDING, + + /** Sells every share the player owns, across all tickers. */ + SELL_ENTIRE_PORTFOLIO; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java index 52e23bc..b934d50 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/stats/StatsController.java @@ -110,6 +110,45 @@ protected void initInteractions() { player.getNetWorthAsFloatProperty().addListener((observable, o, n) -> { pushSnapshot(); }); + + getViewElement().setOnAction( + StatsActions.SELL_ENTIRE_PORTFOLIO, this::sellEntirePortfolio); + } + + /** + * Sells every share the player currently owns, one ticker at a time. + * + *

Uses the same {@link Exchange#sell(BigDecimal, String, Player)} + * entry point as the dashboard's individual sell button, so the + * commission, tax and {@code networthAsFloatProperty} updates all + * fire exactly as if the player had sold each ticker manually. The + * existing player-property listeners then refresh the stats widget + * (and every other widget that listens on net-worth) automatically.

+ * + *

If the portfolio is already empty this is a no-op.

+ * */ + private void sellEntirePortfolio() { + // Snapshot the symbols and their totals first so we don't iterate + // over a list that mutates while we sell. + Map totalsBySymbol = new LinkedHashMap<>(); + for (Share s : player.getPortfolio().getShares()) { + String symbol = s.getStock().getSymbol(); + totalsBySymbol.merge(symbol, s.getQuantity(), BigDecimal::add); + } + + for (Map.Entry entry : totalsBySymbol.entrySet()) { + if (entry.getValue().signum() <= 0) { + continue; + } + try { + exchange.sell(entry.getValue(), entry.getKey(), player); + } catch (IllegalArgumentException e) { + // Skip symbols the player no longer owns (defensive - the + // snapshot above should already exclude these). + System.err.println("[stats] Sell-all skipped " + entry.getKey() + + ": " + e.getMessage()); + } + } } /** 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 8f35e97..a7a1c55 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 @@ -4,6 +4,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Group; +import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.ColumnConstraints; @@ -69,6 +70,9 @@ public class StatsView extends ViewElement { /** Allocation legend - one row per slice. */ private VBox allocLegend; + /** "Sell entire portfolio" button at the bottom of the allocation panel. */ + private Button sellAllButton; + /** Holdings list rows. */ private VBox holdingsList; @@ -253,7 +257,13 @@ private VBox buildAllocPanel() { wrap.setAlignment(Pos.CENTER_LEFT); VBox.setVgrow(wrap, Priority.ALWAYS); - VBox panel = new VBox(6, title, wrap); + sellAllButton = new Button("Sell entire portfolio"); + sellAllButton.setFocusTraversable(false); + sellAllButton.setMaxWidth(Double.MAX_VALUE); + sellAllButton.getStyleClass().add("stats-sell-all-button"); + registerButton(StatsActions.SELL_ENTIRE_PORTFOLIO, sellAllButton); + + VBox panel = new VBox(6, title, wrap, sellAllButton); panel.getStyleClass().add("stats-panel"); panel.setMaxHeight(Double.MAX_VALUE); return panel; @@ -454,6 +464,12 @@ private void renderAllocation() { allocPie.getChildren().clear(); allocLegend.getChildren().clear(); + // The sell-all button only makes sense when there are holdings to + // sell, so disable it whenever the portfolio is empty. + if (sellAllButton != null) { + sellAllButton.setDisable(sumValue(holdings).signum() <= 0); + } + BigDecimal invested = sumValue(holdings); BigDecimal total = invested.add(cash); if (total.signum() <= 0) { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java index fc4143e..dabc63b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsController.java @@ -58,22 +58,21 @@ private void filterData(final String searchKeyword, final Integer weekTarget) { if (t.getShare().getStock().getCompany().toLowerCase().contains(searchKeyword.toLowerCase()) || t.getShare().getStock().getSymbol().toLowerCase().contains(searchKeyword.toLowerCase())) { String transactionType = "Purchase"; - String sharePrefix = "+ "; - String moneyPrefix = "- "; if(t.getClass().equals(Sale.class)){ transactionType = "Sale"; - sharePrefix = "- "; - moneyPrefix = "+ "; } getViewElement().addTransactionCard( transactionType, t.getShare().getStock().getSymbol(), t.getShare().getStock().getCompany(), - sharePrefix + t.getShare().getQuantity().floatValue() + " shares", - moneyPrefix + t.getCalculator().calculateTotal().floatValue() + " NOK", - weekTarget.toString()); + t.getShare().getQuantity(), + t.getCalculator().calculateGross(), + t.getCalculator().calculateCommission(), + t.getCalculator().calculateTax(), + t.getCalculator().calculateTotal(), + weekTarget); } }); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsView.java index b55efd3..7edaae8 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/transactions/TransactionsView.java @@ -1,6 +1,8 @@ package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; + +import java.math.BigDecimal; import java.util.List; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; @@ -102,16 +104,23 @@ protected void initLayout() { * transaction. * @param shares the amount of shares gained or lost during this * transaction. - * @param nok the amount of money in NOK gained or lost during - * the transactions. + * @param gross the gross amount of NOK gained or lost upon + * transaction. + * @param commission the calculated commission fee for the transaction. + * @param tax the calculated tax for the transaction. + * @param totalChange the total amount of NOK gained or lost upon + * transaction. * @param week the week this transaction took place. */ public void addTransactionCard(final String type, final String symbol, final String companyName, - final String shares, - final String nok, - final String week) { + final BigDecimal shares, + final BigDecimal gross, + final BigDecimal commission, + final BigDecimal tax, + final BigDecimal totalChange, + final int week) { VBox card = new VBox(); // Header (SALE / PURCHASE) @@ -120,13 +129,49 @@ public void addTransactionCard(final String type, // Company Details Label companyLabel = new Label(symbol + ", " + companyName); - // Info Rows (+- shares, +- NOK) + // Card rows VBox infoBox = new VBox(); - Label textRow1 = new Label(shares); - Label textRow2 = new Label(nok); + Region spacer1 = new Region(); + HBox.setHgrow(spacer1, Priority.ALWAYS); + HBox row1 = new HBox( + new Label("Quantity: "), + spacer1, + new Label(String.format("%.3f", shares.floatValue())) + ); + Region spacer2 = new Region(); + HBox.setHgrow(spacer2, Priority.ALWAYS); + HBox row2 = new HBox( + new Label("Gross: "), + spacer2, + new Label(String.format("%.3f", gross.floatValue()) + "NOK") + ); + + Region spacer3 = new Region(); + HBox.setHgrow(spacer3, Priority.ALWAYS); + HBox row3 = new HBox( + new Label("Tax: "), + spacer3, + new Label(String.format("%.3f", tax.floatValue()) + "NOK") + ); + + Region spacer4 = new Region(); + HBox.setHgrow(spacer4, Priority.ALWAYS); + HBox row4 = new HBox( + new Label("Commission fee: "), + spacer4, + new Label(String.format("%.3f", commission.floatValue()) + "NOK") + ); + + Region spacer5 = new Region(); + HBox.setHgrow(spacer5, Priority.ALWAYS); + HBox row5 = new HBox( + new Label("Total: "), + spacer5, + new Label(String.format("%.3f", totalChange.floatValue()) + "NOK") + ); - infoBox.getChildren().addAll(textRow1, textRow2); + infoBox.getChildren().addAll(row1, row2, row3, row4, row5); Region spacer = new Region(); VBox.setVgrow(spacer, Priority.ALWAYS); @@ -147,8 +192,7 @@ public void addTransactionCard(final String type, typeLabel.getStyleClass().add("transactions-typeLabel"); infoBox.getStyleClass().add("transactions-infoBox"); companyLabel.getStyleClass().add("transactions-cardText"); - textRow1.getStyleClass().add("transactions-cardText"); - textRow2.getStyleClass().add("transactions-cardText"); + infoBox.getChildren().forEach(n -> n.getStyleClass().add("transactions-cardText")); cardWeekLabel.getStyleClass().add("transactions-cardText"); } diff --git a/src/main/resources/styles.css b/src/main/resources/styles.css index 60c21b6..8864002 100644 --- a/src/main/resources/styles.css +++ b/src/main/resources/styles.css @@ -1115,6 +1115,31 @@ -fx-padding: 20; } +.stats-sell-all-button { + -fx-background-color: rgba(20, 28, 46, 0.85); + -fx-background-radius: 8; + -fx-border-color: rgba(255, 99, 132, 0.55); + -fx-border-radius: 8; + -fx-border-width: 1.2; + -fx-padding: 9 16; + -fx-font-family: "Aptos"; + -fx-font-weight: bold; + -fx-font-size: 13px; + -fx-text-fill: #ffb3c1; + -fx-cursor: hand; +} + +.stats-sell-all-button:hover { + -fx-background-color: rgba(255, 99, 132, 0.15); + -fx-border-color: rgba(255, 99, 132, 0.85); + -fx-text-fill: #ffd9e1; +} + +.stats-sell-all-button:disabled { + -fx-opacity: 0.45; + -fx-cursor: default; +} + /* ======================================================== IN-GAME : TRANSACTIONS VIEW ======================================================== */ @@ -1207,7 +1232,6 @@ } .transactions-cardText { - -fx-font-family: "Consolas, Menlo, monospace"; -fx-font-size: 15px; -fx-text-fill: #f0f4ff; } @@ -2177,6 +2201,18 @@ -fx-text-fill: #2a3654; } +.light-mode .stats-sell-all-button { + -fx-background-color: #fdeaee; + -fx-border-color: rgba(204, 36, 76, 0.55); + -fx-text-fill: #b1284c; +} + +.light-mode .stats-sell-all-button:hover { + -fx-background-color: #f9d6dd; + -fx-border-color: #cc244c; + -fx-text-fill: #8a1e3b; +} + /* ---------- IN-GAME : TRANSACTIONS ---------- */ .light-mode .transactions-root { -fx-background-color: rgba(244, 247, 252, 0.65);