Skip to content
Snippets Groups Projects
Commit dedbb5db authored by Jens Christian Aanestad's avatar Jens Christian Aanestad
Browse files

refactor/Integrated BudgetView.vue with backend endpoints

parent e4a1a2cf
No related branches found
No related tags found
1 merge request!53Refactor/integration budgetview
...@@ -2,64 +2,89 @@ ...@@ -2,64 +2,89 @@
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import Button1 from '@/components/Buttons/Button1.vue' import Button1 from '@/components/Buttons/Button1.vue'
import ExpenseBox from '@/components/Budget/ExpenseBox.vue' import ExpenseBox from '@/components/Budget/ExpenseBox.vue'
import router from '@/router' import { useRouter } from 'vue-router'
import { useBudgetStore } from '@/stores/BudgetStore'
// TODO Need endpoint in order to retrieve budget import type { BudgetResponseDTO, ExpenseRequestDTO, ExpenseResponseDTO } from '@/api'
// Mocked values import { UserService } from '@/api'
let title = ref('Mai 2024'); import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'
let budget = ref(10000); import ConfirmDeleteModal from '@/components/Budget/Modal/ConfirmDeleteModal.vue'
import ImportBudgetModal from '@/components/Budget/Modal/ImportBudgetModal.vue'
const router = useRouter();
// Reactive header values
let title = ref('');
let budget = ref(0);
let expenses = ref(0); let expenses = ref(0);
let balance = ref(budget.value - expenses.value); let balance = ref(0);
let expenseJSONObject = ref({ // Reactive error message and form value
"expenses": [ let errorMsg = ref('');
{ let renameFormRef = ref(null)
"title": "Ost", // Reactive expense list
"value": 30 let expenseDTOList = ref<ExpenseResponseDTO[]>([])
}, // Reactive import budget list
{ let budgetDTOList = ref<BudgetResponseDTO[]>([])
"title": "Skinke", // Reactive background variable
"value": 20 const iRef = ref<any>()
},
{
"title": "Bread",
"value": 15
}
]
});
// Initially updates the total expense display
for (let expense of expenseJSONObject.value.expenses) {
expenses.value += expense.value
}
// Reactive input values // Reactive input values
let budgetTitle = ref('') let budgetTitle = ref('')
let budgetValue = ref() let budgetValue = ref<any>()
let expenseDescription = ref('') let expenseDescription = ref('')
let expenseAmount = ref() let expenseAmount = ref<any>()
// Reactive background variable
const iRef = ref()
/** /**
* Checks the value of the balance and adjust background color depending * Executes necessary updates on component mount.
* on negative or positive value after rendering. * Updates the header, expenses, balance asynchronously,
* and gets budgets that are available to import.
*/ */
onMounted(() => { onMounted(async () => {
if (balance.value >= 0) { try {
iRef.value.style.backgroundColor = 'rgba(34, 231, 50, 0.43)'; await updateHeader();
await updateExpenses();
await updateBalance();
// Gets budgets which can be imported
budgetDTOList.value = await UserService.getBudgetsByUser();
budgetDTOList.value = budgetDTOList.value.filter(item => item.id !== useBudgetStore().getActiveBudgetId);
} catch (error) {
errorMsg.value = handleUnknownError(error);
} }
balance.value = budget.value - expenses.value
}) })
/** /**
* Updates the balance and background color based on the budget and expenses. * Updates the header information asynchronously based on the active budget.
* Fetches the budget details using the UserService and updates the title,
* budget amount, and expense amount accordingly.
*/ */
const updateBalance = () => { const updateHeader = async () => {
const budgetResponse: BudgetResponseDTO = await UserService.getBudget({budgetId: useBudgetStore().getActiveBudgetId});
if (budgetResponse.budgetName != null) {
title.value = budgetResponse.budgetName;
}
if (budgetResponse.budgetAmount != null) {
budget.value = budgetResponse.budgetAmount;
}
if (budgetResponse.expenseAmount != null) {
expenses.value = budgetResponse.expenseAmount;
}
}
/**
* Updates the list of expenses asynchronously based on the active budget.
* Fetches the expenses associated with the active budget using the UserService.
*/
const updateExpenses = async () => {
expenseDTOList.value = await UserService.getExpenses({budgetId: useBudgetStore().getActiveBudgetId});
// Resets expenses and then re-calculates it // Resets expenses and then re-calculates it
expenses.value = 0 expenses.value = 0;
for (let expense of expenseJSONObject.value.expenses) { for (let expenseDTO of expenseDTOList.value) {
expenses.value += expense.value expenses.value += Number(expenseDTO.amount);
} }
}
/**
* Updates the balance and the belonging background color based on the budget and expenses.
*/
const updateBalance = async () => {
// Updates balance value and background // Updates balance value and background
balance.value = budget.value - expenses.value balance.value = budget.value - expenses.value
if (balance.value >= 0) { if (balance.value >= 0) {
...@@ -70,69 +95,130 @@ const updateBalance = () => { ...@@ -70,69 +95,130 @@ const updateBalance = () => {
} }
/** /**
* Calculates a new budget and updates the balance. * Updates the budget information asynchronously with the provided new budget amount and name.
* Updates the local budget and title values, then sends a request to update the budget information
* using the UserService.
* *
* @param newBudget The new budget value. * @param {number} newBudget - The new budget amount to set.
* @param {string} newBudgetName - The new budget name to set.
*/ */
const calculateNewBudget = (newBudget: number) => { const updateBudget = async (newBudget: number, newBudgetName: string) => {
budget.value = newBudget try {
updateBalance() budget.value = newBudget;
title.value = newBudgetName;
// Prepare request body for updating budget
const request: BudgetResponseDTO = {
budgetName: title.value,
budgetAmount: budget.value,
expenseAmount: expenses.value
}
// Send request to update budget information
await UserService.updateBudget({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request})
} catch (error) {
errorMsg.value = handleUnknownError(error)
}
} }
/** /**
* TODO update javadoc when backend integration is done * Adds a new expense with the provided description and value to the active budget.
* Adds a new expense to the expense JSON object and updates the balance. * Sends a request to update the expense information using the UserService.
* Subsequently, triggers updates of the expenses, budget, and the balance.
* *
* @param expenseDescription The description of the expense. * @param {string} expenseDescription - The description of the new expense.
* @param expenseValue The value of the expense. * @param {number} expenseValue - The value of the new expense.
*/ */
const addNewExpense = (expenseDescription: string, expenseValue: number) => { const addNewExpense = async (expenseDescription: string, expenseValue: number) => {
expenseJSONObject.value.expenses.push({ try {
"title": expenseDescription, // Prepare request body for adding new expense
"value": expenseValue const request: ExpenseRequestDTO = {
}); description: expenseDescription,
updateBalance() amount: expenseValue
}
// Send request to update expense information
await UserService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request});
// Trigger updates of expenses and balance and budget
await updateExpenses();
await updateBudget(budget.value, title.value)
await updateBalance();
} catch (error) {
errorMsg.value = handleUnknownError(error);
}
} }
/** /**
* Updates the title of the budget. * Deletes an expense from the list of expenses.
* Sends a request to the UserService to delete the expense.
* Subsequently, triggers updates of the expenses, budget, and the balance.
* *
* @param newTitle The new title for the budget. * @param {number} id - The ID of the expense to delete.
*/ */
const editBudgetTitle = (newTitle: string) => { const deleteExpense = async (id: number) => {
title.value = newTitle try {
await UserService.deleteExpense({expenseId: id});
await updateExpenses();
await updateBudget(budget.value, title.value)
await updateBalance();
} catch (error) {
errorMsg.value = handleUnknownError(error);
}
} }
/** /**
* Deletes an expense from the list of expenses. * Edits the details of an expense with the specified ID.
* Sends a request to the UserService to update the expense with new description and amount.
* Subsequently, triggers updates of the expenses and the balance.
* *
* @param index The index of the expense to delete. * @param {number} id - The ID of the expense to edit.
* @param {string} newDescription - The new description for the expense.
* @param {number} newAmount - The new amount for the expense.
*/ */
const deleteExpense = (index: number) => { const editExpense = async (id: number, newDescription: string, newAmount: number) => {
expenseJSONObject.value.expenses.splice(index, 1); try {
updateBalance() // Prepare request body with updated details
const request: ExpenseRequestDTO = {
expenseId: id,
description: newDescription,
amount: newAmount
}
// Send request to update the expense using the UserService
await UserService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: request});
await updateExpenses();
await updateBudget(budget.value, title.value)
await updateBalance();
} catch (error) {
errorMsg.value = handleUnknownError(error);
}
} }
/** /**
* Edits an existing expense in the list of expenses. * Imports a budget by updating the current budget with the data from the specified budget ID.
* *
* @param index The index of the expense to edit. * @param {number} budgetId - The ID of the budget to import.
* @param newDescription The new description for the expense.
* @param newAmount The new amount for the expense.
*/ */
const editExpense = (index: number, newDescription: string, newAmount: number) => { const importBudget = async (budgetId: number) => {
console.log('Reached') try {
expenseJSONObject.value.expenses[index].title = newDescription // Update current budget value from the imported budget
expenseJSONObject.value.expenses[index].value = newAmount const budgetResponse: BudgetResponseDTO = await UserService.getBudget({budgetId: budgetId});
updateBalance() if (budgetResponse.budgetAmount != null) {
} budget.value += budgetResponse.budgetAmount;
}
// TODO add delete functionality // Get all the expenses from imported budget, and copy them to current budget
const onDeleteBudgetPressed = () => { const expenses: ExpenseResponseDTO[] = await UserService.getExpenses({budgetId: budgetId})
router.push('/budget-overview') for (let expense of expenses) {
const expenseRequest: ExpenseRequestDTO = {
description: expense.description,
amount: expense.amount
}
await UserService.updateExpense({budgetId: useBudgetStore().getActiveBudgetId, requestBody: expenseRequest});
}
// Update display and budget
await updateExpenses();
await updateBudget(budget.value, title.value)
await updateBalance();
} catch (error) {
errorMsg.value = handleUnknownError(error)
}
} }
</script> </script>
<template> <template>
...@@ -140,11 +226,12 @@ const onDeleteBudgetPressed = () => { ...@@ -140,11 +226,12 @@ const onDeleteBudgetPressed = () => {
<h1 class="text-center">{{ title }}</h1> <h1 class="text-center">{{ title }}</h1>
<div class="button-container"> <div class="button-container">
<button1 id="optionButton" button-text="Options" data-bs-toggle="modal" <button1 id="goBack" @click="router.push('/budget-overview')" button-text="Go back"/>
data-bs-target="#modal"/> <button1 id="optionButton" button-text="Options" data-bs-toggle="modal" data-bs-target="#modal"/>
<button1 id="saveChanges" button-text="Save changes"/>
</div> </div>
<p class="text-danger">{{ errorMsg }}</p>
<div class="modal fade" id="modal"> <div class="modal fade" id="modal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
...@@ -153,27 +240,36 @@ const onDeleteBudgetPressed = () => { ...@@ -153,27 +240,36 @@ const onDeleteBudgetPressed = () => {
<button class="btn btn-close" data-bs-dismiss="modal"></button> <button class="btn btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<button id="importButton" class="btn btn-primary"><img src="../assets/icons/import.svg" height="20" width="20" alt="picture">Import budget</button> <button id="importButton" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#import-modal"><img src="../../assets/icons/import.svg" height="20" width="20" alt="picture">Import budget</button>
<button id="editBudget" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#editBudgetCollapse" aria-expanded="false" aria-controls="editBudgetCollapse"><img src="../assets/icons/edit-button.svg" alt="editButton">Rename budget</button> <button id="editBudget" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#editBudgetCollapse" aria-expanded="false" aria-controls="editBudgetCollapse"><img src="../../assets/icons/edit-button.svg" alt="editButton">Rename budget</button>
<div class="collapse" id="editBudgetCollapse"> <div class="collapse" id="editBudgetCollapse">
<div class="container collapse-container"> <div class="container collapse-container">
<form @submit.prevent="editBudgetTitle(budgetTitle)"> <form ref="renameFormRef" @submit.prevent="updateBudget(budget, budgetTitle)">
<div class="input-group"> <div class="input-group">
<input id="collapseInput" class="col-5 form-control" type="text" placeholder="Enter new name of budget" v-model="budgetTitle"> <input id="collapseInput" class="col-5 form-control" type="text" required minlength="1" placeholder="Enter new name of budget" v-model="budgetTitle">
<button1 id="collapseButton" type="submit" button-text="Edit" data-bs-dismiss="modal" /> <button1 id="collapseButton" type="submit" button-text="Confirm" data-bs-dismiss="modal"/>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<button id="deleteButton" class="btn btn-primary" data-bs-toggle="modal" @click="onDeleteBudgetPressed"><img src="../assets/icons/trash-can.svg" height="20" width="20" alt="picture">Delete budget</button> <button id="deleteButton" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirm-modal"><img src="../../assets/icons/trash-can.svg" height="20" width="20" alt="picture">Delete budget</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<confirm-delete-modal :budget-id="useBudgetStore().getActiveBudgetId"
modal-id="confirm-modal"
:budgetTitle="title"
@deletedEvent="router.push('/budget-overview')"/>
<import-budget-modal modal-id="import-modal"
:listOfBudgetResponseDTO="budgetDTOList"
@importBudgetEvent="importBudget"/>
<div class="budget-info-container"> <div class="budget-info-container">
<div class="info budget-container"> <div class="info budget-container">
<i><img src="../assets/icons/money2.svg" width="48px" height="48px" alt="picture"></i> <i><img src="../../assets/icons/money2.svg" width="48px" height="48px" alt="picture"></i>
<div class="budget-text-container"> <div class="budget-text-container">
<h5>{{budget}} kr</h5> <h5>{{budget}} kr</h5>
<p>Budget</p> <p>Budget</p>
...@@ -181,7 +277,7 @@ const onDeleteBudgetPressed = () => { ...@@ -181,7 +277,7 @@ const onDeleteBudgetPressed = () => {
</div> </div>
<div class="info expenses-container"> <div class="info expenses-container">
<i><img src="../assets/icons/credit-card.svg" width="48px" height="48px" alt="picture"></i> <i><img src="../../assets/icons/credit-card.svg" width="48px" height="48px" alt="picture"></i>
<div class="expenses-text-container"> <div class="expenses-text-container">
<h5>{{expenses}} kr</h5> <h5>{{expenses}} kr</h5>
<p>Expenses</p> <p>Expenses</p>
...@@ -189,7 +285,7 @@ const onDeleteBudgetPressed = () => { ...@@ -189,7 +285,7 @@ const onDeleteBudgetPressed = () => {
</div> </div>
<div class="info balance-container"> <div class="info balance-container">
<i ref="iRef"><img src="../assets/icons/scale.svg" width="48px" height="48px" alt="picture"></i> <i ref="iRef"><img src="../../assets/icons/scale.svg" width="48px" height="48px" alt="picture"></i>
<div class="balance-text-container"> <div class="balance-text-container">
<h5>{{balance}} kr</h5> <h5>{{balance}} kr</h5>
<p>Balance</p> <p>Balance</p>
...@@ -199,9 +295,9 @@ const onDeleteBudgetPressed = () => { ...@@ -199,9 +295,9 @@ const onDeleteBudgetPressed = () => {
<div class="budget-content-container"> <div class="budget-content-container">
<form class="budget-from" @submit.prevent="calculateNewBudget(budgetValue)"> <form class="budget-from" @submit.prevent="updateBudget(budgetValue, title)">
<div class="input-group"> <div class="input-group">
<span class="input-group-text">Your budget: </span> <span class="input-group-text">Your budget </span>
<input type="text" class="form-control" placeholder="Enter your budget" required v-model="budgetValue"> <input type="text" class="form-control" placeholder="Enter your budget" required v-model="budgetValue">
<button type="submit" class="btn btn-primary">Calculate</button> <button type="submit" class="btn btn-primary">Calculate</button>
</div> </div>
...@@ -209,7 +305,7 @@ const onDeleteBudgetPressed = () => { ...@@ -209,7 +305,7 @@ const onDeleteBudgetPressed = () => {
<form class="expenses-form" @submit.prevent="addNewExpense(expenseDescription, expenseAmount)"> <form class="expenses-form" @submit.prevent="addNewExpense(expenseDescription, expenseAmount)">
<div class="input-group"> <div class="input-group">
<span class="input-group-text">Add new expense: </span> <span class="input-group-text">Add new expense </span>
<input type="text" class="form-control" placeholder="Name of expense" required v-model="expenseDescription"> <input type="text" class="form-control" placeholder="Name of expense" required v-model="expenseDescription">
<input type="number" min="0" class="form-control" placeholder="Amount (kr)" required v-model="expenseAmount"> <input type="number" min="0" class="form-control" placeholder="Amount (kr)" required v-model="expenseAmount">
<button type="submit" class="btn btn-primary">Calculate</button> <button type="submit" class="btn btn-primary">Calculate</button>
...@@ -217,14 +313,15 @@ const onDeleteBudgetPressed = () => { ...@@ -217,14 +313,15 @@ const onDeleteBudgetPressed = () => {
</form> </form>
</div> </div>
<div class="expenses-details-container"> <div v-if="expenseDTOList.length != 0" class="expenses-details-container">
<h3>Expenses details</h3> <h3>Expenses details</h3>
<div class="expense-box-container"> <div class="expense-box-container">
<expense-box v-for="(expense, index) in expenseJSONObject.expenses" <expense-box v-for="(expenseDTO, index) in expenseDTOList"
:id="Number(expenseDTO.expenseId) || 0"
:key="index" :key="index"
:index="index" :index="index"
:description="expense.title" :description="expenseDTO.description"
:amount="expense.value" :amount="Number(expenseDTO.amount) || 0"
@deleteEvent="deleteExpense" @deleteEvent="deleteExpense"
@editEvent="editExpense"/> @editEvent="editExpense"/>
</div> </div>
...@@ -275,7 +372,7 @@ div.info:hover { ...@@ -275,7 +372,7 @@ div.info:hover {
transform: scale(1.03); transform: scale(1.03);
} }
i { .info i {
display: grid; display: grid;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment