Skip to content

Commit

Permalink
Merge pull request #106 from Team-40-IDATT2003/105-create-transaction…
Browse files Browse the repository at this point in the history
…s-page

105 create transactions page
  • Loading branch information
etsorens authored May 19, 2026
2 parents 9e60c5e + 2700002 commit 30a4bac
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 17 deletions.
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

0 comments on commit 30a4bac

Please sign in to comment.