Skip to content

105 create transactions page #106

Merged
merged 4 commits into from
May 19, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarView;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsController;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions.TransactionsView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
Expand Down Expand Up @@ -116,6 +118,12 @@ public void start(final Stage stage) throws Exception {
exchange,
stocksInFile);

TransactionsView transactionsView = new TransactionsView();
TransactionsController transactionsController = new TransactionsController(
transactionsView,
eventManager,
player.getTransactionArchive());

// In-game (Change "topBarView" to "topBarView2" if no summary section).
// Dashboard er default center-view.
InGameView inGameView = new InGameView(topBarView, dashBoardView.getRootPane());
Expand All @@ -124,7 +132,9 @@ public void start(final Stage stage) throws Exception {
topBarController.setMarketIntegration(
inGameView::changeCenterView,
dashBoardView.getRootPane(),
marketView.getRootPane()
marketView.getRootPane(),
transactionsView.getRootPane(),
transactionsController::refresh
);

// Register all views
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ public List<Transaction> getTransactions(final int week) {
return result;
}

/**
* Returns all transactions.
*
* @return list of transactions from the given week
*/
public List<Transaction> getTransactions() {
return transactions;
}

/**
* Returns all purchase transactions from a given week.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventSubscriber;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import javafx.scene.Scene;
import javafx.stage.Stage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ protected void initInteractions() {
getViewElement().setCurrentStock(stockList.getFirst(), 0);
getViewElement().updateGraph(selectedTimeRange);
getViewElement().setOnAction(DashBoardActions.BUY_SHARES, () -> {
if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText())) {
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(),
Expand All @@ -143,7 +144,8 @@ protected void initInteractions() {
});

getViewElement().setOnAction(DashBoardActions.SELL_SHARES, () -> {
if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText())) {
if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText())
&& Float.parseFloat(getViewElement().getQuantityInputField().getText()) > 0) {
List<Transaction> transactions = exchange.sell(
new BigDecimal(getViewElement().getQuantityInputField().getText()),
getViewElement().getCurrentStock().getSymbol(),
Expand Down Expand Up @@ -190,6 +192,12 @@ protected void initInteractions() {
return null;
}));

getViewElement().getQuantityInputField().focusedProperty().addListener((observable, wasFocused, isNowFocused) -> {
if (!isNowFocused && getViewElement().getQuantityInputField().getText().trim().isEmpty()) {
getViewElement().getQuantityInputField().setText("1.0");
}
});

getViewElement().getSideBarSearchField().textProperty().addListener((observable, o, n) -> {
selectedFilter = n;
populateStockList(selectedFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ public void setCurrentStock(final Stock stock, final float amountOwned) {
selectedStockLabel.setText(stock.getSymbol());
stockFullNameLabel.setText(stock.getCompany());
ownedStocks = amountOwned;
ownedQuantityLabel.setText("Owned: " + ownedStocks);
ownedQuantityLabel.setText("Owned: " + formatShares(ownedStocks));
}

/**
Expand All @@ -392,7 +392,7 @@ public void setCurrentStock(final Stock stock, final float amountOwned) {
* */
public void addOwnedShares(final float val) {
ownedStocks += val;
ownedQuantityLabel.setText("Owned: " + ownedStocks);
ownedQuantityLabel.setText("Owned: " + formatShares(ownedStocks));
}

/**
Expand Down Expand Up @@ -553,4 +553,14 @@ public TextField getQuantityInputField() {
public ComboBox<DashBoardTimeRange> getTimeRangeSelector() {
return timeRangeSelector;
}

/**
* Helper method for formatting owned shares (avoid precision showing in UI).
*
* @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("\\.$", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum TopBarActions {
EXIT,
STATS,
MARKET,
SETTINGS;
SETTINGS,
TRANSACTIONS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ public class TopBarController extends ViewController<TopBarView> {
* */
private boolean inMarketView = false;

/**
* Whether the transactions screen is currently the active center-view.
*
* <p>When true, the quit/back button returns to the dashboard instead
* of exiting to the main menu.</p>
* */
private boolean inTransactionsView = false;

/**
* {@inheritDoc}.
*/
Expand Down Expand Up @@ -50,19 +58,22 @@ protected void initInteractions() {
* otherwise to the main menu.</li>
* </ul>
*
* @param centerSwitcher callback that swaps the center-view (typically
* {@code inGameView::changeCenterView}).
* @param dashboardCenter root pane of the dashboard widget.
* @param marketCenter root pane of the market widget.
* @param centerSwitcher callback that swaps the center-view (typically
* {@code inGameView::changeCenterView}).
* @param dashboardCenter root pane of the dashboard widget.
* @param marketCenter root pane of the market widget.
* @param transactionsCenter root pane of the transactions' widget.
* */
public void setMarketIntegration(final Consumer<Node> centerSwitcher,
final Node dashboardCenter,
final Node marketCenter) {
final Node marketCenter,
final Node transactionsCenter, final Runnable onTransactionUpdate) {
getViewElement().setOnAction(TopBarActions.EXIT, () -> {
if (inMarketView) {
if (inMarketView || inTransactionsView) {
centerSwitcher.accept(dashboardCenter);
getViewElement().setQuitText("Quit");
inMarketView = false;
inTransactionsView = false;
} else {
changeScene(ViewEnum.MAIN_MENU);
}
Expand All @@ -72,12 +83,22 @@ public void setMarketIntegration(final Consumer<Node> centerSwitcher,
centerSwitcher.accept(dashboardCenter);
getViewElement().setQuitText("Quit");
inMarketView = false;
inTransactionsView = false;
});

getViewElement().setOnAction(TopBarActions.MARKET, () -> {
centerSwitcher.accept(marketCenter);
getViewElement().setQuitText("Back");
inMarketView = true;
inTransactionsView = false;
});

getViewElement().setOnAction(TopBarActions.TRANSACTIONS, () -> {
centerSwitcher.accept(transactionsCenter);
onTransactionUpdate.run();
getViewElement().setQuitText("Back");
inMarketView = false;
inTransactionsView = true;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ public class TopBarView extends ViewElement<VBox, TopBarActions> {
private Button statsBtn;
private Button marketBtn;
private Button settingsBtn;
private Button transactionsBtn;
private SummaryView summaryView;


public TopBarView(final SummaryView summaryView) {
this.summaryView = summaryView;
super(new VBox(10), TopBarActions.class);
Expand Down Expand Up @@ -45,16 +47,18 @@ protected void initLayout() {
statsBtn = new Button("Stats");
marketBtn = new Button("Market");
settingsBtn = new Button("Settings");
transactionsBtn = new Button("Transactions");

Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn).forEach(b -> {
Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn).forEach(b -> {
HBox.setHgrow(b, Priority.ALWAYS);
});

navRow.getChildren().addAll(
quitBtn,
statsBtn,
marketBtn,
settingsBtn
settingsBtn,
transactionsBtn
);

if (summaryView != null) {
Expand All @@ -66,12 +70,13 @@ protected void initLayout() {
registerButton(TopBarActions.STATS, statsBtn);
registerButton(TopBarActions.MARKET, marketBtn);
registerButton(TopBarActions.SETTINGS, settingsBtn);
registerButton(TopBarActions.TRANSACTIONS, transactionsBtn);
}

@Override
protected void initStyling() {
getRootPane().getStyleClass().add("top-bar");
Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn)
Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn, transactionsBtn)
.forEach(b -> b.getStyleClass().add("menu-button"));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions;

/**
* Enum representing actions to be done in a {@link TransactionsView} element.
* */
public enum TransactionsActions {
// Empty, no interactable buttons in transactions page.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.transactions;

import edu.ntnu.idi.idatt2003.g40.mappe.engine.TransactionArchive;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController;
import java.util.List;

/**
* Controller for a {@link TransactionsView} element.
*
* <p>extends {@link ViewController}</p>
* */
public class TransactionsController extends ViewController<TransactionsView> {

private final TransactionArchive transactionArchive;
public TransactionsController(final TransactionsView viewElement,
final EventManager eventManager,
final TransactionArchive transactionArchive) {
this.transactionArchive = transactionArchive;
super(viewElement, eventManager);
getViewElement().clearCards();
}

@Override
protected void initInteractions() {


getViewElement().getWeekSelectBox().setOnAction(event -> {
try {
Integer selectedWeek = Integer.parseInt(getViewElement().getWeekSelectBox().getValue());
filterData(getViewElement().getSearchField().getText(), selectedWeek);
} catch (NumberFormatException _) {
filterData(getViewElement().getSearchField().getText(), 1);
}
});

getViewElement().getSearchField().textProperty().addListener((observable, oldValue, newValue) -> {
filterData(newValue, Integer.parseInt(getViewElement().getWeekSelectBox().getValue()));
});
}

/**
* Method used for updating the list of transactions based on a search and target week.
*
* @param searchKeyword the keyword to search for stocks with.
* @param weekTarget the week used when searching for transactions.
* */
private void filterData(final String searchKeyword, final Integer weekTarget) {
getViewElement().clearCards();
transactionArchive.getTransactions(weekTarget).forEach(t -> {
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().toString() + " NOK",
weekTarget.toString());
}
});
}

private List<Integer> getUniqueTransactionWeeks(final List<Transaction> transactionList) {
return transactionList.stream()
.map(Transaction::getWeek)
.distinct()
.sorted()
.toList();
}

public void refresh() {
List<Integer> activeWeeks = getUniqueTransactionWeeks(transactionArchive.getTransactions());
getViewElement().setWeekSelectBoxOptions(activeWeeks);

if (activeWeeks.contains(Integer.parseInt(getViewElement().getWeekSelectBox().getValue()))) {
getViewElement().getWeekSelectBox().setValue(getViewElement().getWeekSelectBox().getValue());
} else if (!activeWeeks.isEmpty()) {
getViewElement().getWeekSelectBox().setValue(activeWeeks.getFirst().toString());
}

filterData(
getViewElement().getSearchField().getText(),
Integer.parseInt(getViewElement().getWeekSelectBox().getValue())
);
}
}
Loading