diff --git a/src/assets/icons/edit-button.svg b/src/assets/icons/edit-button.svg new file mode 100644 index 0000000000000000000000000000000000000000..877f06fa63241e2343a3f38b3c7572c389b21648 --- /dev/null +++ b/src/assets/icons/edit-button.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg fill="#FFFFFF" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + width="20px" height="20px" viewBox="0 0 494.936 494.936" + xml:space="preserve"> +<g> + <g> + <path d="M389.844,182.85c-6.743,0-12.21,5.467-12.21,12.21v222.968c0,23.562-19.174,42.735-42.736,42.735H67.157 + c-23.562,0-42.736-19.174-42.736-42.735V150.285c0-23.562,19.174-42.735,42.736-42.735h267.741c6.743,0,12.21-5.467,12.21-12.21 + s-5.467-12.21-12.21-12.21H67.157C30.126,83.13,0,113.255,0,150.285v267.743c0,37.029,30.126,67.155,67.157,67.155h267.741 + c37.03,0,67.156-30.126,67.156-67.155V195.061C402.054,188.318,396.587,182.85,389.844,182.85z"/> + <path d="M483.876,20.791c-14.72-14.72-38.669-14.714-53.377,0L221.352,229.944c-0.28,0.28-3.434,3.559-4.251,5.396l-28.963,65.069 + c-2.057,4.619-1.056,10.027,2.521,13.6c2.337,2.336,5.461,3.576,8.639,3.576c1.675,0,3.362-0.346,4.96-1.057l65.07-28.963 + c1.83-0.815,5.114-3.97,5.396-4.25L483.876,74.169c7.131-7.131,11.06-16.61,11.06-26.692 + C494.936,37.396,491.007,27.915,483.876,20.791z M466.61,56.897L257.457,266.05c-0.035,0.036-0.055,0.078-0.089,0.107 + l-33.989,15.131L238.51,247.3c0.03-0.036,0.071-0.055,0.107-0.09L447.765,38.058c5.038-5.039,13.819-5.033,18.846,0.005 + c2.518,2.51,3.905,5.855,3.905,9.414C470.516,51.036,469.127,54.38,466.61,56.897z"/> + </g> +</g> +</svg> \ No newline at end of file diff --git a/src/assets/icons/import.svg b/src/assets/icons/import.svg new file mode 100644 index 0000000000000000000000000000000000000000..e832f47a76ed58d701ffe52126dc5245b3f3a877 --- /dev/null +++ b/src/assets/icons/import.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg fill="#FFF" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 512 512" xml:space="preserve"> +<g> + <g> + <path d="M435.2,153.6H320v25.6h102.4v307.2H89.6V179.2H192v-25.6H76.8c-7.066,0-12.8,5.734-12.8,12.8v332.8 + c0,7.066,5.734,12.8,12.8,12.8h358.4c7.066,0,12.8-5.734,12.8-12.8V166.4C448,159.334,442.266,153.6,435.2,153.6z"/> + </g> +</g> +<g> + <g> + <path d="M341.956,234.249c-4.941-5.052-13.056-5.146-18.099-0.205L268.8,287.898V12.8C268.8,5.734,263.066,0,256,0 + c-7.066,0-12.8,5.734-12.791,12.8v275.089l-55.057-53.854c-5.043-4.941-13.158-4.847-18.099,0.205 + c-4.941,5.06-4.855,13.158,0.205,18.099l76.8,75.128c5.043,4.949,13.158,4.855,18.099-0.188l76.595-74.931 + C346.803,247.407,346.897,239.309,341.956,234.249z"/> + </g> +</g> +</svg> \ No newline at end of file diff --git a/src/assets/icons/trash-can.svg b/src/assets/icons/trash-can.svg new file mode 100644 index 0000000000000000000000000000000000000000..2206f7cf70ee47cb883112fa37d704a09df1463a --- /dev/null +++ b/src/assets/icons/trash-can.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg fill="#FFFFFF" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + width="20px" height="20px" viewBox="0 0 408.483 408.483" + xml:space="preserve"> +<g> + <g> + <path d="M87.748,388.784c0.461,11.01,9.521,19.699,20.539,19.699h191.911c11.018,0,20.078-8.689,20.539-19.699l13.705-289.316 + H74.043L87.748,388.784z M247.655,171.329c0-4.61,3.738-8.349,8.35-8.349h13.355c4.609,0,8.35,3.738,8.35,8.349v165.293 + c0,4.611-3.738,8.349-8.35,8.349h-13.355c-4.61,0-8.35-3.736-8.35-8.349V171.329z M189.216,171.329 + c0-4.61,3.738-8.349,8.349-8.349h13.355c4.609,0,8.349,3.738,8.349,8.349v165.293c0,4.611-3.737,8.349-8.349,8.349h-13.355 + c-4.61,0-8.349-3.736-8.349-8.349V171.329L189.216,171.329z M130.775,171.329c0-4.61,3.738-8.349,8.349-8.349h13.356 + c4.61,0,8.349,3.738,8.349,8.349v165.293c0,4.611-3.738,8.349-8.349,8.349h-13.356c-4.61,0-8.349-3.736-8.349-8.349V171.329z"/> + <path d="M343.567,21.043h-88.535V4.305c0-2.377-1.927-4.305-4.305-4.305h-92.971c-2.377,0-4.304,1.928-4.304,4.305v16.737H64.916 + c-7.125,0-12.9,5.776-12.9,12.901V74.47h304.451V33.944C356.467,26.819,350.692,21.043,343.567,21.043z"/> + </g> +</g> +</svg> \ No newline at end of file diff --git a/src/components/BaseComponents/Menu.vue b/src/components/BaseComponents/Menu.vue index e3335e85db757500d08ee536ae9c8897b2cbb0c1..a44cedcafc8e80e96e3c8cf8deea826889f8d58a 100644 --- a/src/components/BaseComponents/Menu.vue +++ b/src/components/BaseComponents/Menu.vue @@ -70,6 +70,10 @@ function toHome() { router.push('/') } +function toBudget() { + router.push('/budget-overview') +} + function toSavingGoals() { router.push('/roadmap') } diff --git a/src/components/Budget/BudgetBox.vue b/src/components/Budget/BudgetBox.vue index 42f33935cbad55419bff4eea5ecc2f2b6756469f..bf75574c9f70d67475b2ca3410c5a73661a9a600 100644 --- a/src/components/Budget/BudgetBox.vue +++ b/src/components/Budget/BudgetBox.vue @@ -1,6 +1,8 @@ <script setup lang="ts"> - import { onMounted, ref } from 'vue' +import { useRouter } from 'vue-router' + +const router = useRouter(); const props = defineProps({ title: { @@ -27,10 +29,14 @@ onMounted(() => { } }) +const onBudgetContainerPressed = () => { + router.push('/budget') +} + </script> <template> - <div class="container-fluid row"> + <div class="container-fluid row" @click="onBudgetContainerPressed"> <div class="col-12"> <div class="title-container"> <h2>{{title}}</h2> @@ -77,14 +83,15 @@ onMounted(() => { } .container-fluid { - border: 4px solid #4747ce; + border: 4px solid #5959ea; min-height: 90px; border-radius: 15px; - transition: transform 150ms ease-in-out; + transition: transform 150ms ease-in-out, border 200ms ease-in-out; cursor: pointer; } .container-fluid:hover { + border: 4px solid #0000f1; transform: scale(1.03); } diff --git a/src/components/Budget/ExpenseBox.vue b/src/components/Budget/ExpenseBox.vue new file mode 100644 index 0000000000000000000000000000000000000000..858d61179d6931f063859b0ca7e7f0a2664ffaa6 --- /dev/null +++ b/src/components/Budget/ExpenseBox.vue @@ -0,0 +1,85 @@ +<script setup lang="ts"> + +import Button1 from '@/components/Buttons/Button1.vue' +import { type CreateAppFunction, ref } from 'vue' + +const emit = defineEmits(['deleteEvent', 'editEvent']); +const props = defineProps({ + index: { + type: Number, + default: 0 + }, + description: { + type: String, + default: '' + }, + amount: { + type: Number, + default: 0 + } +}) + +let editDescription = ref('') +let editAmount = ref('') + +const emitDeleteEvent = () => { + emit('deleteEvent', props.index) +} + +const emitEditEvent = () => { + emit('editEvent', props.index, editDescription.value, editAmount.value) +} +</script> + +<template> + <div class="expense-container"> + <p>{{index + 1}}</p> + <p>{{description}}</p> + <p>{{amount}} kr</p> + <button class="btn btn-success" data-bs-toggle="collapse" :data-bs-target="'#' + index" aria-expanded="false" aria-controls="editBudgetCollapse"> + <img src="../../assets/icons/edit-button.svg" alt="Edit" height="18" width="18"> + Edit + </button> + <button class="btn btn-danger" @click="emitDeleteEvent"> + <img src="../../assets/icons/trash-can.svg" alt="Edit" height="18" width="18"> + Delete + </button> + </div> + + <div class="collapse" :id="index"> + <div class="container collapse-container"> + <form @submit.prevent="emitEditEvent"> + <div class="input-group"> + <span class="input-group-text">Edit expense #{{ index+1 }}: </span> + <input type="text" class="form-control" placeholder="Expense description" required v-model="editDescription"> + <input type="number" min="0" class="form-control" placeholder="Amount (kr)" required v-model="editAmount"> + <button type="submit" class="btn btn-primary" data-bs-toggle="collapse" :data-bs-target="'#' + index">Confirm</button> + </div> + </form> + </div> + </div> +</template> + +<style scoped> +.expense-container { + padding: 0 10px; + display: grid; + grid-template-columns: 1fr 1fr 1fr .6fr .6fr; + border-radius: 10px; + background-color: #2a2a34; + align-content: center; + justify-self: center; + margin: 10px 5px; +} + +.expense-container p { + color: white; + align-self: center; + margin: 0; +} + +.expense-container button { + margin: 5px; + padding: 0; +} +</style> \ No newline at end of file diff --git a/src/components/Budget/ExpensesBox.vue b/src/components/Budget/ExpensesBox.vue deleted file mode 100644 index 96c0baf2962af764a8ee54b135a5bb33402f69c8..0000000000000000000000000000000000000000 --- a/src/components/Budget/ExpensesBox.vue +++ /dev/null @@ -1,11 +0,0 @@ -<script setup lang="ts"> - -</script> - -<template> - -</template> - -<style scoped> - -</style> \ No newline at end of file diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index 2401b49d3f1452d28f4d3aa91732be573c9279b7..ef0d093bcdf1af8d832b5923de4aa0f2ec3b80e4 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -37,6 +37,7 @@ const onChangedChallengeEvent = (value) => { else { chosenChallenges.value = chosenChallenges.value.filter(item => item !== value[0]); } + console.log(chosenChallenges.value) } /** diff --git a/src/router/index.ts b/src/router/index.ts index eb8adea45bb3a50d7376dd5d38f0ad99dbb6249d..170e39ea9491fc3e2defbab475f4ae3a74b41080 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -53,10 +53,15 @@ const routes = [ name: 'profile', component: UserProfileView }, + { + path: '/budget-overview', + name: 'budget overview', + component: () => import('@/views/BudgetOverview.vue'), + }, { path: '/budget', name: 'budget', - component: () => import('@/views/BudgetOverview.vue'), + component: () => import('@/views/BudgetView.vue'), }, { path: 'friends', diff --git a/src/views/BudgetOverview.vue b/src/views/BudgetOverview.vue index 31599bda055d2c4cc41e4b64f20b54ea631551bd..6fc66387dd8dec41a6376be2610a549a831cc9db 100644 --- a/src/views/BudgetOverview.vue +++ b/src/views/BudgetOverview.vue @@ -1,33 +1,32 @@ <script setup lang="ts"> - import Button1 from '@/components/Buttons/Button1.vue' -import Budget from '@/components/Budget/BudgetBox.vue' +import BudgetBox from '@/components/Budget/BudgetBox.vue' </script> <template> <div class="container"> <h1 class="text-center">Your Budgets</h1> - <button1 id="createBudgetButton" button-text="Create new budget" lass="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample"/> + <button1 id="createBudgetButton" button-text="Create new budget" class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample"/> <div class="collapse" id="collapseExample"> <div class="container collapse-container"> <div class="input-group row"> - <input id="collapseInput" class="col-5" type="text" placeholder="Enter name of budget"> + <input id="collapseInput" class="col-5 form-control" type="text" placeholder="Enter name of budget"> <button1 id="collapseButton" class="col-1" button-text="Create" data-bs-dismiss="modal"/> </div> </div> </div> <ul class="budgetContainer"> - <li><budget title="April 2024" budget="1000" expenses="908700"></budget></li> - <li><budget title="Mai 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="Juni 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="Juli 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="August 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="September 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="Oktober 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="November 2024" budget="1000" expenses="87"></budget></li> - <li><budget title="Desember 2024" budget="1000" expenses="87"></budget></li> + <li><budget-box title="April 2024" budget="1000" expenses="908700"></budget-box></li> + <li><budget-box title="Mai 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="Juni 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="Juli 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="August 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="September 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="Oktober 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="November 2024" budget="1000" expenses="87"></budget-box></li> + <li><budget-box title="Desember 2024" budget="1000" expenses="87"></budget-box></li> </ul> <nav id="navbar" aria-label="Page navigation example"> diff --git a/src/views/BudgetView.vue b/src/views/BudgetView.vue index 0cfa5fec232a5ec5a97e3cb10057a3448ac2184b..706a9bfc0bfd02254cbd530ba2814e64355ff381 100644 --- a/src/views/BudgetView.vue +++ b/src/views/BudgetView.vue @@ -1,13 +1,289 @@ <script setup lang="ts"> +import { onMounted, ref } from 'vue' +import Button1 from '@/components/Buttons/Button1.vue' +import ExpenseBox from '@/components/Budget/ExpenseBox.vue' +import router from '@/router' + +// TODO Need endpoint in order to retrieve budget +// Mocked values +let title = ref('Mai 2024'); +let budget = ref(10000); +let expenses = ref(0); +let balance = ref(budget.value - expenses.value); +let expenseJSONObject = ref({ + "expenses": [ + { + "title": "Ost", + "value": 30 + }, + { + "title": "Skinke", + "value": 20 + }, + { + "title": "Bread", + "value": 15 + } + ] +}); + +for (let expense of expenseJSONObject.value.expenses) { + expenses.value += expense.value +} + +// Reactive input values +let budgetTitle = ref('') +let budgetValue = ref() +let expenseDescription = ref('') +let expenseAmount = ref() + +const iRef = ref() + +onMounted(() => { + if (balance.value >= 0) { + iRef.value.style.backgroundColor = 'rgba(34, 231, 50, 0.43)'; + } + balance.value = budget.value - expenses.value +}) + +const updateBalance = () => { + expenses.value = 0 + for (let expense of expenseJSONObject.value.expenses) { + expenses.value += expense.value + } + balance.value = budget.value - expenses.value + if (balance.value >= 0) { + iRef.value.style.backgroundColor = 'rgba(34, 231, 50, 0.43)'; + } else { + iRef.value.style.backgroundColor= 'rgba(232, 14, 14, 0.43)'; + } +} + +const calculateNewBudget = (newBudget: number) => { + budget.value = newBudget + updateBalance() +} + +const addNewExpense = (expenseDescription: string, expenseValue: number) => { + expenseJSONObject.value.expenses.push({ + "title": expenseDescription, + "value": expenseValue + }); + updateBalance() +} + +const editBudgetTitle = (newTitle: string) => { + title.value = newTitle +} + +const deleteExpense = (index: number) => { + expenseJSONObject.value.expenses.splice(index, 1); + updateBalance() +} + +const editExpense = (index: number, newDescription: string, newAmount: number) => { + console.log('Reached') + expenseJSONObject.value.expenses[index].title = newDescription + expenseJSONObject.value.expenses[index].value = newAmount + updateBalance() +} + +// TODO add delete functionality +const onDeleteBudgetPressed = () => { + router.push('/budget-overview') +} </script> <template> <div class="container"> + <h1 class="text-center">{{ title }}</h1> + + <div class="button-container"> + <button1 id="optionButton" button-text="Options" data-bs-toggle="modal" + data-bs-target="#modal"/> + <button1 id="saveChanges" button-text="Save changes"/> + </div> + + <div class="modal fade" id="modal"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h3>Options</h3> + <button class="btn btn-close" data-bs-dismiss="modal"></button> + </div> + <div class="modal-body"> + <button id="importButton" class="btn btn-primary"><img src="../assets/icons/import.svg" height="20" width="20">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> + <div class="collapse" id="editBudgetCollapse"> + <div class="container collapse-container"> + <form @submit.prevent="editBudgetTitle(budgetTitle)"> + <div class="input-group"> + <input id="collapseInput" class="col-5 form-control" type="text" placeholder="Enter new name of budget" v-model="budgetTitle"> + <button1 id="collapseButton" type="submit" button-text="Edit" data-bs-dismiss="modal" /> + </div> + </form> + </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">Delete budget</button> + </div> + </div> + </div> + </div> + + <div class="budget-info-container"> + <div class="info budget-container"> + <i><img src="../assets/icons/money2.svg" width="48px" height="48px"></i> + <div class="budget-text-container"> + <h5>{{budget}} kr</h5> + <p>Budget</p> + </div> + </div> + + <div class="info expenses-container"> + <i><img src="../assets/icons/credit-card.svg" width="48px" height="48px"></i> + <div class="expenses-text-container"> + <h5>{{expenses}} kr</h5> + <p>Expenses</p> + </div> + </div> + + <div class="info balance-container"> + <i ref="iRef"><img src="../assets/icons/scale.svg" width="48px" height="48px"></i> + <div class="balance-text-container"> + <h5>{{balance}} kr</h5> + <p>Balance</p> + </div> + </div> + </div> + + + <div class="budget-content-container"> + <form class="budget-from" @submit.prevent="calculateNewBudget(budgetValue)"> + <div class="input-group"> + <span class="input-group-text">Your budget: </span> + <input type="text" class="form-control" placeholder="Enter your budget" required v-model="budgetValue"> + <button type="submit" class="btn btn-primary">Calculate</button> + </div> + </form> + + <form class="expenses-form" @submit.prevent="addNewExpense(expenseDescription, expenseAmount)"> + <div class="input-group"> + <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="number" min="0" class="form-control" placeholder="Amount (kr)" required v-model="expenseAmount"> + <button type="submit" class="btn btn-primary">Calculate</button> + </div> + </form> + </div> + + <div class="expenses-details-container"> + <h3>Expenses details</h3> + <div class="expense-box-container"> + <expense-box v-for="(expense, index) in expenseJSONObject.expenses" + :key="index" + :index="index" + :description="expense.title" + :amount="expense.value" + @deleteEvent="deleteExpense" + @editEvent="editExpense" + /> + </div> + </div> </div> </template> <style scoped> +.button-container { + display: flex; + gap: 10px; +} + +.container.collapse-container { + padding: 0; + margin: 0; +} + +.modal-header { + display: flex; +} + +.modal-body { + display: grid; + gap: 10px +} + +div.budget-info-container { + margin-top: 2rem; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + justify-content: center; + gap: 1rem; +} + +div.info { + display: flex; + flex-direction: row; + background-color: rgba(221, 221, 224, 0.5); + border-radius: 10px; + padding: 10px; + transition: transform 150ms ease-in-out; +} + +div.info:hover { + transform: scale(1.03); +} + +i { + display: grid; + justify-content: center; + align-content: center; + margin: 5px; + border-radius: 7px; + min-width: 90px; +} + +.budget-container i { + background-color: rgba(78, 107, 239, 0.43); +} + +.expenses-container i { + background-color: rgba(238, 191, 43, 0.43); +} + +.balance-container i { + background-color: rgba(232, 14, 14, 0.43); +} + +.budget-content-container { + margin: 2rem 0; + display: grid; + gap: 5px; +} +.budget-content-container label { + display: flex; + align-items: center; +} + + +.expenses-details-container { + margin: 1rem 0; + min-height: 80px; + border-radius: 8px; + background-color: rgba(234, 234, 234, 0.8); +} + +.expenses-details-container h3 { + margin-top: 1rem; + padding: 10px; +} + +.expense-box-container { + overflow-y: auto; + overflow-x: hidden; + max-height: 100vh; +} + </style> \ No newline at end of file