Skip to content
Snippets Groups Projects
Forked from Surya Bahadur Kathayat / idatt1002
This fork has diverged from the upstream repository.
BudgetController.java 13.00 KiB
package no.ntnu.idatt1002.demo.controller;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.text.Text;
import javafx.stage.Modality;
import javafx.stage.Stage;
import no.ntnu.idatt1002.demo.data.Budget.BudgetItem;
import no.ntnu.idatt1002.demo.data.Budget.FileHandlingBudget;
import no.ntnu.idatt1002.demo.data.Budget.GeneralBudget;
import no.ntnu.idatt1002.demo.data.Economics.Expense;
import no.ntnu.idatt1002.demo.data.Economics.ExpenseCategory;

import java.io.IOException;
import java.util.Optional;

/**
 * Controller for budget scene in the application. This controller manages all actions that relates to the budget tableview (add, edit and delete), the switching
 * of scenes from the budget scene to another scene, and the saving of the table, whenever the user switches to another scene.
 *
 * @author Anders Emil Bergan
 * @since 24.3.2023
 */
public class BudgetController implements FinanceController {

    private GeneralBudget general;

    @FXML
    private Button addBtn;

    @FXML
    private Button editBtn;

    @FXML
    private Button deleteBtn;

    @FXML
    private Button returnBtn;

    @FXML
    private Button backBtn;

    @FXML
    private Button continueBtn;

    @FXML
    private TableColumn<BudgetItem, Double> amountCol;

    @FXML
    private TableView<BudgetItem> budgetTableView = new TableView<>();

    @FXML
    private TableColumn<BudgetItem, ExpenseCategory> categoryCol;

    @FXML
    private TableColumn<BudgetItem, String> descriptionCol;

    @FXML
    private Text sum;

    @FXML
    private DatePicker date;

    @FXML
    private ObservableList<BudgetItem> budgetList;

    @FXML
    private PieChart budgetPieChart;


    /**
     * Initializes the budget register, the observable budget list and the tableview, along with the values of the dropbox used for filtering the tableview.
     */

    @FXML
    public void initialize() {
        //TODO specify error messgage for when amount is exceeded / duplicate exists
        //todo properly close screen so things are saved
        //Initialize table columns
        categoryCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, ExpenseCategory>("budgetCategory"));
        amountCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, Double>("budgetAmount"));
        descriptionCol.setCellValueFactory(new PropertyValueFactory<BudgetItem, String>("budgetDescription"));

        try {
            general = loadBudgetDataFromFile("Budget");
            budgetList = FXCollections.observableArrayList(general.getBudgetItems());
            budgetTableView.setItems(budgetList);
            if (FileHandlingBudget.isNewBudget("Budget")) {
                returnBtn.setOpacity(0);
                returnBtn.setDisable(true);
            } else {
                refreshPieChart();
                //addBtn.setDisable(true);
                //editBtn.setDisable(true);
                //deleteBtn.setDisable(true);
                backBtn.setDisable(true);
                continueBtn.setDisable(true);
                backBtn.setOpacity(0);
                continueBtn.setOpacity(0);
            }
        } catch(IOException ioe) {
            showErrorDialogBox("File reading error", "Error in reading file", "Could not"
                + "read from the Budget file");
        }
        formatDatePicker();
    }

    private ObservableList<PieChart.Data> createBudgetPieChart() throws IllegalArgumentException { //
        ObservableList<PieChart.Data> budgetData = FXCollections.observableArrayList();
        List<ExpenseCategory> chosenCategories = general.getChosenBudgetCategories();

        //Only adds the budget data for chosen categories to the pie chart
        for (ExpenseCategory category : chosenCategories) {
            budgetData.add(new Data(category.toString().substring(0, 1).toUpperCase().
                concat(category.toString().substring(1)),
                general.getBudgetItem(category).getBudgetAmount()));
        }
        return budgetData;
    }

    /*private ObservableList<PieChart.Data> createBudgetPieChart() {
        return FXCollections.observableArrayList(
            new Data("Food", general.getBudgetItem(ExpenseCategory.FOOD).getBudgetAmount()),
            new Data("Books", general.getBudgetItem(ExpenseCategory.BOOKS).getBudgetAmount()),
            new Data("Clothes", general.getBudgetItem(ExpenseCategory.CLOTHES).getBudgetAmount()),
            new Data("Other", general.getBudgetItem(ExpenseCategory.OTHER).getBudgetAmount())
        );
    }*/

    private void refreshPieChart() {
        this.budgetPieChart.setData(createBudgetPieChart());
    }

    /**
     * Method for disabling the date picker, yet having its opacity at max.
     */
    private void formatDatePicker() {
        date.setValue(LocalDate.now());
        date.setDisable(true);
        date.setStyle("-fx-opacity: 1");
        date.getEditor().setStyle("-fx-opacity: 1");
    }

    @Override
    public void handleAddBtn(ActionEvent event) {
        handleEditBtn(event);
    }
    /**
     * Adds or edits a budget item, depending on what mode the DialogMode enum is at. The method brings up a dialog box popup in which the user can fill and choose
     * values that the budget item will have. Open exiting the popup, the changes the saved to the tableview.
     * @param event A button click on either the add or delete button.
     */
    @Override
    public void handleEditBtn(ActionEvent event) {
        BudgetItem item = null;
        String dialogTitle = "";
        DialogMode dialogMode;

        FXMLLoader loader = new FXMLLoader(SceneController.class.getResource("/view/AddBudgetNew.fxml"));
        Dialog<BudgetItem> dialog = new Dialog<>();
        dialog.initModality(Modality.APPLICATION_MODAL);

        //Try to load the FXML file onto another dialogPane
        try{
            dialog.getDialogPane().setContent(loader.load());
        } catch(IOException e) {
            showErrorDialogBox("Loading error", "Error in loading dialog", "An error occurred"
                + "when loading the AddBudget window");
        }

        //Loads the controller for the dialog box that appears whenever one adds or edits a budget item
        AddBudgetController budgetController = loader.getController();

        //Sets the title of the dialog box
        if(event.getSource().equals(addBtn)){
            dialogMode = DialogMode.ADD;
            dialogTitle = "New Budget";
        }
        else if (event.getSource().equals(editBtn) && budgetTableView.getSelectionModel().getSelectedItem() != null) {
            dialogMode = DialogMode.EDIT;
            dialogTitle = "Edit expense";
            //Gets the selected item from the table
            item = budgetTableView.getSelectionModel().getSelectedItem();
            //Binds the selected item to another item which is defined in the budgetcontroller
            budgetController.setBudget(item);
        } else {
            return;
        }

        dialog.setTitle(dialogTitle);
        // Show the Dialog and wait for the user to close it
        dialog.showAndWait();
        //Adds the new item to the register
        item = budgetController.getNewBudgetItem();
        if(item != null && dialogMode == DialogMode.ADD){
            try {
                general.addToBudgetBudgetItem(item);
            } catch(IllegalArgumentException e) {
                showErrorDialogBox(e.getMessage(), e.getMessage(), e.getMessage());
            }
        }
        //Updates the tableview using the register
        refreshTableView();
        refreshPieChart();
    }


    /**
     * Deletes an entry from the tableview, if an entry has been selected. The method brings up a popup window, asking for confirmation for deleting the entry.
     * @param event A button click on the delete button
     */
    @FXML
    public void handleDeleteBtn(ActionEvent event) {
        //Gets the selected item from the tableview
        BudgetItem item = budgetTableView.getSelectionModel().getSelectedItem();
        //Exits the method if nothing is selected
        if (item == null) {
            return;
        }
        //Brings up a confirmation popup
        String title = "Confirm Delete" ;
        String header = "Delete Confirmation";
        String content = "Are you sure you would like to delete the selected entry?";
        Optional<ButtonType> isConfirmed = showConfirmationDialog(title, header, content);
        if (isConfirmed.isPresent() && isConfirmed.get() == ButtonType.OK) {
            general.deleteItemFromBudget(item.getBudgetCategory());
            refreshTableView();
            refreshPieChart();
        }
    }

    /**
     * Method for synching the register with the tableview. The observable list to which the tableview is set, is being refilled with all the entries
     * in the register, keeping it updated with new changes.
     */
    public void refreshTableView(){
        this.budgetList.setAll(general.getBudgetItems());
        //Refreshing the sum of the amounts of the budget
        //this.sum.setText(String.valueOf(general.totalSum()));
    }

    /**
     * Returns an optional, which is a popup alert box, asking for confirmation for deleting an entry.
     * @return An alert box, asking for confirmation for deleting the selected entry of the tableview.
     */
    @Override
    public Optional<ButtonType> showConfirmationDialog(String title, String header, String content) {
        Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
        alert.setTitle("Confirm Delete");
        alert.setHeaderText("Delete Confirmation");
        alert.setContentText("Are you sure you would like to delete the selected entry?");

        return alert.showAndWait();
    }

    /**
     * Saves the changes made to the tableview by writing the information to a file.
     *
     * @throws IOException If an error occurs while writing to the file.
     */
    @Override
    public void saveDataToFile() throws IOException {
        FileHandlingBudget.writeGeneralBudgetToFile("Budget", general);
    }

    /**
     * Displays an alert box of type error, informing of a custom error.
     */
    private void showErrorDialogBox(String title, String header, String content) {
        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }

    /**
     * Method that either reads data from a file with which it fills a budget register, if this is an old budget, or instantiates a budget register if this is a new budget.
     * @param fileName The name of the file that is being read from.
     * @return An object of type GeneralBudget.
     * @throws IOException If an error occurs while reading from the file.
     */
    public GeneralBudget loadBudgetDataFromFile(String fileName) throws IOException {
        //Instantiate new budget
        if (FileHandlingBudget.isEmpty(fileName)) {
            general = new GeneralBudget(1000);
            //throws new IOException("Not valid budget")
        } else { //Load previous budget
            try {
                general = FileHandlingBudget.readGeneralBudgetFromFile(fileName);
            } catch (IOException e) {
                showErrorDialogBox("File error", "Error in reading from fil", "An error occurred"
                    + "when reading GeneralBudget from the file");
            }
        }
        return general;
    }

    /**
     * Switches scene, by loading a new FXML file and setting the scene to this location.
     * @param event A button click on the return to main menu button
     */
    @FXML
    public void switchScene(ActionEvent event) {
        FXMLLoader loader = new FXMLLoader();
        try {
            if (event.getSource() == returnBtn || event.getSource() == continueBtn) {
                //Adds unused categories to the table
                general.addUnusedCategories();
                //Always saving the data when switching scenes
                saveDataToFile();
                loader.setLocation(getClass().getResource("/view/MainMenuNew.fxml"));
            } else if (event.getSource() == backBtn) {
                loader.setLocation(getClass().getResource("/view/dualList.fxml"));
            }
            Parent root = loader.load();
            Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();

        } catch(Exception ioe) {
            showErrorDialogBox("Loading error", "Error in loading", "Could load"
                + "to FXML file");
        }
    }
}