chet novoe
This commit is contained in:
@@ -18,8 +18,8 @@
|
|||||||
tg.expand(); // Разворачиваем веб-приложение на весь экран
|
tg.expand(); // Разворачиваем веб-приложение на весь экран
|
||||||
|
|
||||||
// Получаем информацию о пользователе и выводим её
|
// Получаем информацию о пользователе и выводим её
|
||||||
const user = tg.initDataUnsafe.user;
|
// const user = tg.initDataUnsafe.user;
|
||||||
console.log(user);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -3,16 +3,6 @@
|
|||||||
"short_name": "Luminic Space",
|
"short_name": "Luminic Space",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
|
||||||
"src": "/icon-192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icon-512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
|
|||||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
|
"chartjs-plugin-datalabels": "^2.2.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
@@ -2153,6 +2154,14 @@
|
|||||||
"pnpm": ">=8"
|
"pnpm": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chartjs-plugin-datalabels": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"chart.js": ">=3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -11237,6 +11246,12 @@
|
|||||||
"@kurkle/color": "^0.3.0"
|
"@kurkle/color": "^0.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chartjs-plugin-datalabels": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
|
"chartjs-plugin-datalabels": "^2.2.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<!-- Контентная часть заполняет оставшееся пространство -->
|
<!-- Контентная часть заполняет оставшееся пространство -->
|
||||||
<div class="flex-grow ">
|
<div class="flex-grow ">
|
||||||
|
|
||||||
<router-view class="w-full h-full mt-4 lg:mt-0"/>
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
<OverlayView class="w-full sticky invisible lg:visible top-0 z-10"/>
|
<OverlayView class="w-full sticky invisible lg:visible top-0 z-10"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,19 @@
|
|||||||
width: 7rem;
|
width: 7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-progressbar-label {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: left !important;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
.p-progressbar {
|
||||||
|
height: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
/*margin-top: 1rem;*/
|
||||||
|
/*height: 12 8px !important*/
|
||||||
|
}
|
||||||
/*#app {*/
|
/*#app {*/
|
||||||
/* !*max-width: 1280px;*!*/
|
/* !*max-width: 1280px;*!*/
|
||||||
/* !*margin: 0 auto;*!*/
|
/* !*margin: 0 auto;*!*/
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ const items = ref([
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log(route.params['mode']);
|
|
||||||
if (route.params['mode']) {
|
if (route.path == '/transactions/create') {
|
||||||
console.log(route.params['mode']);
|
|
||||||
|
|
||||||
openDrawer('INSTANT')
|
openDrawer('INSTANT')
|
||||||
|
|
||||||
|
|||||||
@@ -151,15 +151,15 @@ const onAddClick = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
console.log(route.params['mode']);
|
// console.log(route.params['mode']);
|
||||||
if (route.params['mode']) {
|
// if (route.params['mode']) {
|
||||||
console.log(route.params['mode']);
|
// console.log(route.params['mode']);
|
||||||
|
//
|
||||||
openDrawer('INSTANT')
|
// openDrawer('INSTANT')
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
}, 10)
|
// }, 10)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
86
src/components/budgets/BudgetCategoryView.vue
Normal file
86
src/components/budgets/BudgetCategoryView.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Button from "primevue/button";
|
||||||
|
import InputNumber from "primevue/inputnumber";
|
||||||
|
import ProgressBar from "primevue/progressbar";
|
||||||
|
|
||||||
|
import {Category} from "@/models/Category";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {formatAmount} from "@/utils/utils";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
category: {
|
||||||
|
type: Object as Category,
|
||||||
|
require: true
|
||||||
|
},
|
||||||
|
budgetId: {
|
||||||
|
type: Number,
|
||||||
|
require: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['category-updated'])
|
||||||
|
|
||||||
|
const isEditing = ref(false);
|
||||||
|
const startEditing = () => {
|
||||||
|
isEditing.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopEditing = () => {
|
||||||
|
isEditing.value = false;
|
||||||
|
|
||||||
|
emits('category-updated', editedCategory.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
// const selectedCategorySettingType = ref(props.category.categorySetting.type)
|
||||||
|
|
||||||
|
const categoryAmount = ref(1000)
|
||||||
|
|
||||||
|
const editedCategory = ref(props.category);
|
||||||
|
const spentPlannedRatio = computed( () => {
|
||||||
|
if (editedCategory.value.currentLimit == 0){
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return editedCategory.value.currentSpent / editedCategory.value.currentLimit * 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="p-2 shadow-lg rounded-lg bg-white flex justify-between flex-col ">
|
||||||
|
<div class="flex flex-row justify-between w-full">
|
||||||
|
<div :class="isEditing ? 'w-1/5': ''" class="min-w-1 w-4/6 justify-between ">
|
||||||
|
<h4 class="text-lg line-clamp-1">{{editedCategory.category.icon }} {{ editedCategory.category.name }}</h4>
|
||||||
|
<!-- <p class="text-sm text-gray-500 line-clamp-1 min-w-1 ">{{ editedCategory.category.description }}</p>-->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row gap-2 justify-end items-center w-3/6 ">
|
||||||
|
|
||||||
|
<!-- Сумма, которая становится редактируемой при клике -->
|
||||||
|
<button v-if="!isEditing" @click="startEditing"
|
||||||
|
class="text-lg font-bold cursor-pointer w-fit text-end line-clamp-1">
|
||||||
|
<div class="flex flex-row gap-2 items-baseline" >
|
||||||
|
<p class="font-light text-sm" :class="spentPlannedRatio == 0 ? 'hidden': ''">{{spentPlannedRatio.toFixed(0)}} %</p>
|
||||||
|
<p class="line-clamp-1 w-fit">{{ formatAmount(editedCategory.currentSpent) }} /
|
||||||
|
{{ formatAmount(editedCategory.currentLimit) }} ₽</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<InputNumber v-else ref="inputRefs" type="text" v-model="editedCategory.currentLimit"
|
||||||
|
class="text-lg font-bold border-b-2 border-gray-300 outline-none focus:border-blue-500 w-32 text-right"
|
||||||
|
:min="editedCategory.categoryPlannedLimit" :max="900000" :invalid="editedCategory.currentLimit < editedCategory.categoryPlannedLimit" v-tooltip.top="'Сумма не должна быть ниже суммы запланированных!'"/>
|
||||||
|
<Button v-if="isEditing" @click="stopEditing" icon="pi pi-check" severity="success" rounded outlined
|
||||||
|
aria-label="Search"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<ProgressBar :value="Number(spentPlannedRatio.toFixed(0))" class="w-full" :show-value="false"> </ProgressBar>
|
||||||
|
<!-- <div class="z-50">{{formatAmount(spentPlannedRatio.toFixed(0))}}%</div>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -115,7 +115,7 @@ const pastBudgets = ref([
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
budgetInfos.value = await getBudgetInfos()
|
budgetInfos.value = await getBudgetInfos()
|
||||||
console.log(budgetInfos.value)
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,15 +16,20 @@ const props = defineProps(
|
|||||||
transaction: {
|
transaction: {
|
||||||
type: Object as PropType<Transaction>,
|
type: Object as PropType<Transaction>,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
isList: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const emits = defineEmits(['open-drawer'])
|
const emits = defineEmits(['open-drawer', 'transaction-checked', 'transaction-updated'])
|
||||||
|
|
||||||
|
|
||||||
const setIsDoneTrue = async () => {
|
const setIsDoneTrue = async () => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await updateTransactionRequest(props.transaction)
|
await updateTransactionRequest(props.transaction)
|
||||||
|
emits('transaction-checked')
|
||||||
}, 10);
|
}, 10);
|
||||||
// showedTransaction.value.isDone = !showedTransaction.value.isDone;
|
// showedTransaction.value.isDone = !showedTransaction.value.isDone;
|
||||||
|
|
||||||
@@ -40,6 +45,11 @@ const toggleDrawer = () => {
|
|||||||
emits('open-drawer', props.transaction)
|
emits('open-drawer', props.transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transactionUpdate = () => {
|
||||||
|
console.log('my tut 1')
|
||||||
|
emits('transaction-updated')
|
||||||
|
}
|
||||||
|
|
||||||
const isPlanned = computed(() => {
|
const isPlanned = computed(() => {
|
||||||
return props.transaction?.transactionType.code === "PLANNED"
|
return props.transaction?.transactionType.code === "PLANNED"
|
||||||
})
|
})
|
||||||
@@ -86,25 +96,25 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="transaction.category.type.code == 'INCOME' ? 'from-green-100 to-green-50' : ' from-red-100 to-red-50' &&
|
<div :class="
|
||||||
transaction.transactionType.code == 'INSTANT' ? ' bg-gradient-to-r shadow-lg border-2 gap-5 p-2 rounded-xl ' : 'border-b pb-2'
|
props.isList ? ' bg-gradient-to-r shadow-lg border-2 gap-5 p-2 rounded-xl me-5' : 'border-b pb-2'
|
||||||
"
|
"
|
||||||
class="flex bg-white min-w-fit max-h-fit flex-row items-center gap-4 w-full ">
|
class="flex bg-white min-w-fit max-h-fit flex-row items-center gap-4 w-full ">
|
||||||
<div>
|
<div>
|
||||||
<p v-if="transaction.transactionType.code=='INSTANT'"
|
<p v-if="transaction.transactionType.code=='INSTANT' || props.isList"
|
||||||
class="text-6xl font-bold text-gray-700 dark:text-gray-400">
|
class="text-6xl font-bold text-gray-700 dark:text-gray-400">
|
||||||
{{ transaction.category.icon }}</p>
|
{{ transaction.category.icon }}</p>
|
||||||
<Checkbox v-model="transaction.isDone" v-else-if="transaction.transactionType.code=='PLANNED'"
|
<Checkbox v-model="transaction.isDone" v-else-if="transaction.transactionType.code=='PLANNED' && !props.isList"
|
||||||
:binary="true"
|
:binary="true"
|
||||||
@click="setIsDoneTrue"/>
|
@click="setIsDoneTrue"/>
|
||||||
</div>
|
</div>
|
||||||
<button class="flex flex-row items-center p-x-4 justify-between w-full " @click="toggleDrawer">
|
<button class="flex flex-row items-center p-x-4 justify-between w-full " @click="toggleDrawer">
|
||||||
|
|
||||||
<div class="flex flex-col items-start justify-items-start">
|
<div class="flex flex-col items-start justify-items-start">
|
||||||
<p :class="transaction.isDone && isPlanned ? 'line-through' : ''" class="font-bold">{{
|
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-bold">{{
|
||||||
transaction.comment
|
transaction.comment
|
||||||
}}</p>
|
}}</p>
|
||||||
<p :class="transaction.isDone && isPlanned ? 'line-through' : ''" class="font-light">{{
|
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-light">{{
|
||||||
transaction.category.name
|
transaction.category.name
|
||||||
}} |
|
}} |
|
||||||
{{ formatDate(transaction.date) }}</p>
|
{{ formatDate(transaction.date) }}</p>
|
||||||
@@ -122,7 +132,7 @@ onMounted(async () => {
|
|||||||
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened" :expenseCategories="expenseCategories"
|
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened" :expenseCategories="expenseCategories"
|
||||||
:incomeCategories="incomeCategories" :transaction="transaction"
|
:incomeCategories="incomeCategories" :transaction="transaction"
|
||||||
:category-types="categoryTypes"
|
:category-types="categoryTypes"
|
||||||
|
@transaction-updated="transactionUpdate"
|
||||||
@close-drawer="closeDrawer()"
|
@close-drawer="closeDrawer()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,169 +16,221 @@
|
|||||||
</div>
|
</div>
|
||||||
<div :class="!updateLoading ? '' : 'h-fit bg-white opacity-50 z-0 '" class=" flex flex-col gap-3">
|
<div :class="!updateLoading ? '' : 'h-fit bg-white opacity-50 z-0 '" class=" flex flex-col gap-3">
|
||||||
<div class="flex flex-col ">
|
<div class="flex flex-col ">
|
||||||
<h2 class="text-4xl font-bold">Budget for {{ budgetInfo.budget.name }} </h2>
|
<!-- {{ budget }}-->
|
||||||
<div class="flex flex-row gap-2 text-xl">{{ formatDate(budgetInfo.budget.dateFrom) }} -
|
<h2 class="text-4xl font-bold">Budget for {{ budget.name }} </h2>
|
||||||
{{ formatDate(budgetInfo.budget.dateTo) }}
|
<!-- <div class="flex flex-row gap-2 text-xl">{{ formatDate(budget.dateFrom) }} - -->
|
||||||
</div>
|
<!-- {{ formatDate(budget.dateTo) }}-->
|
||||||
|
<!-- </div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<!-- Аналитика и плановые доходы/расходы -->
|
<!-- Аналитика и плановые доходы/расходы -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start ">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start ">
|
||||||
<!-- Блок Аналитики (25%) -->
|
<div class="flex flex-col gap-4">
|
||||||
<div class="card p-4 shadow-lg rounded-lg col-span-2 h-fit">
|
<!-- Блок Аналитики (25%) -->
|
||||||
<h3 class="text-xl mb-4 font-bold">Analytics</h3>
|
<div
|
||||||
<div class="flex ">
|
class="bg-white p-4 shadow-lg rounded-lg flex flex-col gap-4 items-start ">
|
||||||
<div class="w-128">
|
<h3 class="text-xl font-bold">Аналитика</h3>
|
||||||
<Chart type="bar" :data="incomeExpenseChartData" class=""/>
|
|
||||||
|
<SelectButton v-model="selectedChart" :options="modes" optionLabel="label" optionIcon="icon"> <template #option="slotProps">
|
||||||
|
<i :class="slotProps.option.icon"></i>
|
||||||
|
</template></SelectButton>
|
||||||
|
<Chart v-if="selectedChart.value=='bar'" type="bar" :data="incomeExpenseChartData" :options="incomeExpenseChartOptions" class="!w-full"
|
||||||
|
style="width: 100%"/>
|
||||||
|
|
||||||
|
<Chart v-if="selectedChart.value=='pie'" type="pie" :data="pieChartData" :options="pieChartOptions" class="chart "/>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex gap-5 items-center justify-items-center ">
|
||||||
|
<div class="w-full">
|
||||||
|
<button class="grid grid-cols-2 gap-5 items-center w-full" @click="detailedShowed = !detailedShowed">
|
||||||
|
<div class="flex flex-col items-center font-bold ">
|
||||||
|
<h4 class="text-xl font-bold text-green-500">Сумма поступлений</h4>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
+{{ formatAmount(totalIncomes) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
<!-- <p>Total Incomes</p>-->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center ">
|
||||||
|
<h4 class="text-xl font-bold text-red-500">Сумма трат</h4>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
-{{ formatAmount(totalExpenses) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 !gap-1 mt-4" :class="detailedShowed ? 'block' : 'hidden'">
|
||||||
|
<div class="flex flex-col items-center font-bold ">
|
||||||
|
<p class="font-bold ">Поступление в первый период</p>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full ">
|
||||||
|
+{{ formatAmount(incomesByPeriod[0]) }} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<p class="font-bold ">Траты в первый период</p>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full ">
|
||||||
|
-{{ formatAmount(expensesByPeriod[0]) }} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<p class="font-bold ">Поступления во второй период</p>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full ">
|
||||||
|
+{{ formatAmount(incomesByPeriod[1]) }} ₽
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<p class="font-bold ">Траты во второй период</p>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full ">
|
||||||
|
-{{ formatAmount(expensesByPeriod[1]) }} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-5 items-center justify-items-center ">
|
||||||
|
<div class="w-full">
|
||||||
|
<button class="grid grid-cols-3 justify-between gap-5 items-center w-full"
|
||||||
|
@click="detailedShowed = !detailedShowed">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<h4 class="text-lg">Долги</h4>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ loansRatio.toFixed(0) }} %
|
||||||
|
</div>
|
||||||
|
<!-- <p>Total Incomes</p>-->
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center ">
|
||||||
|
<h4 class="text-lg ">Сбережения</h4>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ savingRatio.toFixed(0) }} %
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center ">
|
||||||
|
<h4 class="text-lg ">Ежедневные</h4>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ dailyRatio.toFixed(0) }} %
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 !gap-1 mt-4" :class="detailedShowed ? 'block' : 'hidden'">
|
||||||
|
<div v-for="transaction in transactionCategoriesSums" class="flex flex-col items-center font-bold ">
|
||||||
|
<p class="font-bold ">{{ transaction.category.name }}</p>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full ">
|
||||||
|
{{ formatAmount(transaction.sum) }} ₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ProgressBar :value="value" class="mt-2 col-span-2" style="height: 1rem !important;"></ProgressBar>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="h-fit">
|
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-6 hidden lg:flex">
|
||||||
<!-- <Chart type="pie" :data="incomeExpenseChartData" class="h-64"/>-->
|
<div class="flex flex-row ">
|
||||||
|
<h3 class="text-2xl font-bold mb-4 ">Transactions List</h3>
|
||||||
|
</div>
|
||||||
|
<div class=" flex gap-2">
|
||||||
|
<button v-for="categorySum in transactionCategoriesSums" :key="categorySum.category.id"
|
||||||
|
class="rounded-full border p-1 bg-white border-gray-300 mb-2 px-2">
|
||||||
|
<strong>{{ categorySum.category.name }}</strong>:
|
||||||
|
{{ categorySum.sum }} ₽
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 gap-4 max-h-tlist overflow-y-auto pe-2">
|
||||||
|
<BudgetTransactionView v-for="transaction in transactions" :key="transaction.id"
|
||||||
|
:transaction="transaction"
|
||||||
|
:is-list="true" class=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-4 row-span-3">
|
||||||
|
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
|
||||||
|
<!-- Планируемые доходы -->
|
||||||
|
<div>
|
||||||
|
<div class="flex flex-row gap-4 items-center">
|
||||||
|
<h3 class="text-xl font-bold text-green-500 mb-4 ">Planned Incomes</h3>
|
||||||
|
<Button icon="pi pi-plus" rounded outlined size="small"/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 mb-2">
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ formatAmount(totalIncomes) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ formatAmount(totalIncomeLeftToGet) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<!-- {{ plannedIncomes }}-->
|
||||||
|
<BudgetTransactionView v-for="transaction in plannedIncomes" :transaction="transaction"
|
||||||
|
:is-list="false" @transaction-checked="fetchBudgetTransactions"
|
||||||
|
@transaction-updated="updateTransactions"/>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
|
||||||
|
<!-- Планируемые расходы -->
|
||||||
|
<div class>
|
||||||
|
<h3 class="text-xl font-bold text-red-500 mb-4">Planned Expenses</h3>
|
||||||
|
<div class="grid grid-cols-2 mb-2">
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ formatAmount(totalPlannedExpenses) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||||
|
{{ formatAmount(totalExpenseLeftToSpend) }}
|
||||||
|
₽
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
|
||||||
|
<BudgetTransactionView v-for="transaction in plannedExpenses" :transaction="transaction"
|
||||||
|
:is-list="false" @transaction-checked="fetchBudgetTransactions"
|
||||||
|
@transaction-updated="updateTransactions"/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5 items-center justify-items-center mt-4">
|
|
||||||
<div class="grid grid-cols-2 gap-5 items-center w-full">
|
|
||||||
<div class="flex flex-col items-center font-bold ">
|
|
||||||
<h4 class="text-xl font-bold text-green-500">Total Incomes:</h4>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
+{{ formatAmount(budgetInfo.totalIncomes) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
<!-- <p>Total Incomes</p>-->
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col items-center ">
|
|
||||||
<h4 class="text-xl font-bold text-red-500">Total Expenses:</h4>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
-{{ formatAmount(budgetInfo.totalExpenses) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 items-center font-bold">
|
|
||||||
<p class="font-bold ">Income at 10th:</p>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full px-2">
|
|
||||||
+{{ formatAmount(budgetInfo.chartData[0][0]) }} ₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 items-center">
|
|
||||||
<p class="font-bold ">Income at 25th:</p>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full px-2">
|
|
||||||
+{{ formatAmount(budgetInfo.chartData[0][1]) }} ₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 items-center">
|
|
||||||
<p class="font-bold ">Expenses at 10th:</p>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full px-2">
|
|
||||||
-{{ formatAmount(budgetInfo.chartData[1][0]) }} ₽
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<div class="row-span-1 h-fit">
|
||||||
<div class="flex flex-col gap-2 items-center">
|
<h3 class="text-2xl font-bold mb-4">Категории</h3>
|
||||||
<p class="font-bold ">Expenses at 25th:</p>
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner text-center w-full px-2">
|
|
||||||
-{{ formatAmount(budgetInfo.chartData[1][1]) }} ₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 items-center ">
|
|
||||||
<p class="font-bold">Left for unplanned:</p>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(leftForUnplanned) }} ₽
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2 items-center ">
|
|
||||||
<p class="font-bold">Current spending:</p>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(leftForUnplanned) }} ₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<UnplannedCategoryView v-for="category in categories" :key="category.id"
|
||||||
|
:category="category" :budget-id="budget.id"
|
||||||
|
@category-updated="updateBudgetCategory"
|
||||||
|
class="p-4 shadow-lg rounded-lg bg-white flex justify-between items-center"/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar :value="value" class="mt-2"></ProgressBar>
|
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-6 lg:hidden ">
|
||||||
<div class="flex flex-col w-full items-end">
|
<div class="flex flex-row ">
|
||||||
|
<h3 class="text-2xl font-bold mb-4 ">Transactions List</h3>
|
||||||
<div class="flex flex-row items-end ">
|
</div>
|
||||||
{{ formatAmount(leftForUnplanned) }} ₽
|
<div class=" flex gap-2">
|
||||||
|
<button v-for="categorySum in transactionCategoriesSums" :key="categorySum.category.id"
|
||||||
|
class="rounded-full border p-1 bg-white border-gray-300 mb-2 px-2">
|
||||||
|
<strong>{{ categorySum.category.name }}</strong>:
|
||||||
|
{{ categorySum.sum }} ₽
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 gap-4 max-h-tlist overflow-y-auto pe-2">
|
||||||
|
<BudgetTransactionView v-for="transaction in transactions" :key="transaction.id"
|
||||||
|
:transaction="transaction"
|
||||||
|
:is-list="true" class=""
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4 row-span-3">
|
|
||||||
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
|
|
||||||
<!-- Планируемые доходы -->
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-row gap-4 items-center">
|
|
||||||
<h3 class="text-xl font-bold text-green-500 mb-4 ">Planned Incomes</h3>
|
|
||||||
<Button icon="pi pi-plus" rounded outlined size="small"/>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 mb-2">
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(budgetInfo.totalIncomes) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(totalIncomeLeftToGet) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
|
|
||||||
<BudgetTransactionView v-for="transaction in budgetInfo.plannedIncomes" :transaction="transaction"/>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card p-4 shadow-lg rounded-lg col-span-1 h-fit">
|
|
||||||
<!-- Планируемые расходы -->
|
|
||||||
<div class>
|
|
||||||
<h3 class="text-xl font-bold text-red-500 mb-4">Planned Expenses</h3>
|
|
||||||
<div class="grid grid-cols-2 mb-2">
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(budgetInfo.totalExpenses) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
|
||||||
{{ formatAmount(totalExpenseLeftToSpend) }}
|
|
||||||
₽
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
|
|
||||||
<BudgetTransactionView v-for="transaction in budgetInfo.plannedExpenses" :transaction="transaction"/>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row-span-1 col-span-2 h-fit">
|
|
||||||
<h3 class="text-2xl font-bold mb-4">Unplanned Categories</h3>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
||||||
|
|
||||||
<UnplannedCategoryView v-for="category in budgetInfo.unplannedCategories" :key="category.id"
|
|
||||||
:category="category" :budget-id="budgetInfo.budget.id"
|
|
||||||
@category-updated="updateBudgetCategory"
|
|
||||||
class="p-4 shadow-lg rounded-lg bg-white flex justify-between items-center"/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-2 col-span-2">
|
|
||||||
<div class="flex flex-row ">
|
|
||||||
<h3 class="text-2xl font-bold mb-4 ">Transactions List</h3>
|
|
||||||
</div>
|
|
||||||
<div class=" flex gap-2">
|
|
||||||
<button v-for="categorySum in budgetInfo.transactionCategoriesSums" :key="categorySum.category.id"
|
|
||||||
class="rounded-full border p-1 bg-white border-gray-300 mb-2 px-2">
|
|
||||||
<strong>{{ categorySum.category.name }}</strong>:
|
|
||||||
{{ categorySum.sum }} ₽
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 max-h-tlist overflow-y-auto">
|
|
||||||
<BudgetTransactionView v-for="transaction in budgetInfo.transactions" :key="transaction.id"
|
|
||||||
:transaction="transaction"
|
|
||||||
@open-drawer="openDrawer"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -188,40 +240,61 @@
|
|||||||
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, onMounted, ref} from 'vue';
|
import { computed, onMounted, ref} from 'vue';
|
||||||
import Chart from 'primevue/chart';
|
import Chart from 'primevue/chart';
|
||||||
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
||||||
import {CategoryType} from "@/models/Category";
|
import {
|
||||||
import {getBudgetInfo, updateBudgetCategoryRequest} from "@/services/budgetsService";
|
getBudgetCategories, getBudgetCategoriesSums,
|
||||||
import {BudgetInfo} from "@/models/Budget";
|
getBudgetInfo,
|
||||||
|
getBudgetTransactions,
|
||||||
|
updateBudgetCategoryRequest
|
||||||
|
} from "@/services/budgetsService";
|
||||||
|
import {Budget, BudgetCategory, BudgetInfo} from "@/models/Budget";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
import {formatAmount, formatDate} from "@/utils/utils";
|
import {formatAmount} from "@/utils/utils";
|
||||||
import ProgressBar from "primevue/progressbar";
|
import ProgressBar from "primevue/progressbar";
|
||||||
import ProgressSpinner from "primevue/progressspinner";
|
import ProgressSpinner from "primevue/progressspinner";
|
||||||
import UnplannedCategoryView from "@/components/budgets/UnplannedCategoryView.vue";
|
import UnplannedCategoryView from "@/components/budgets/BudgetCategoryView.vue";
|
||||||
import {TransactionType} from "@/models/Transaction";
|
import {Transaction} from "@/models/Transaction";
|
||||||
import Toast from "primevue/toast";
|
import Toast from "primevue/toast";
|
||||||
import Button from "primevue/button";
|
import Button from "primevue/button";
|
||||||
import LoadingView from "@/components/LoadingView.vue";
|
import LoadingView from "@/components/LoadingView.vue";
|
||||||
|
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||||
|
import {Chart as ChartJS} from 'chart.js/auto';
|
||||||
|
import SelectButton from "primevue/selectbutton";
|
||||||
|
|
||||||
|
// Зарегистрируем плагин
|
||||||
|
ChartJS.register(ChartDataLabels);
|
||||||
|
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const updateLoading = ref(false);
|
const updateLoading = ref(false);
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const detailedShowed = ref(false);
|
||||||
|
const selectedChart = ref({ "label": "bar", "icon": "pi pi-chart-bar", "value": "bar" });
|
||||||
|
const modes = [
|
||||||
|
{label: 'bar', icon: 'pi pi-chart-bar', value: 'bar'},
|
||||||
|
{label: 'pie', icon: 'pi pi-chart-pie', value: 'pie'}
|
||||||
|
];
|
||||||
|
|
||||||
const budgetInfo = ref<BudgetInfo>();
|
|
||||||
|
|
||||||
const value = ref(50)
|
const value = ref(50)
|
||||||
|
|
||||||
const leftForUnplanned = ref(0)
|
const leftForUnplanned = ref(0)
|
||||||
|
|
||||||
const drawerOpened = ref(false);
|
|
||||||
const transactionType = ref<TransactionType>()
|
|
||||||
const categoryType = ref<CategoryType>()
|
|
||||||
|
|
||||||
|
|
||||||
|
const budget = ref<Budget>()
|
||||||
|
const plannedIncomes = ref<Transaction[]>([])
|
||||||
|
const totalIncomes = computed(() => {
|
||||||
|
let totalIncome = 0;
|
||||||
|
plannedIncomes.value.forEach((i) => {
|
||||||
|
totalIncome += i.amount
|
||||||
|
})
|
||||||
|
return totalIncome
|
||||||
|
})
|
||||||
const totalIncomeLeftToGet = computed(() => {
|
const totalIncomeLeftToGet = computed(() => {
|
||||||
let totalIncomeLeftToGet = 0;
|
let totalIncomeLeftToGet = 0;
|
||||||
budgetInfo.value?.plannedIncomes.forEach(i => {
|
plannedIncomes.value.forEach(i => {
|
||||||
if (!i.isDone) {
|
if (!i.isDone) {
|
||||||
totalIncomeLeftToGet += i.amount
|
totalIncomeLeftToGet += i.amount
|
||||||
}
|
}
|
||||||
@@ -229,93 +302,353 @@ const totalIncomeLeftToGet = computed(() => {
|
|||||||
return totalIncomeLeftToGet
|
return totalIncomeLeftToGet
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const totalLoans = computed(() => {
|
||||||
|
let value = 0
|
||||||
|
categories.value.filter((cat) => cat.category.id == 29).forEach(cat => {
|
||||||
|
value += cat.currentLimit
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
|
const loansRatio = computed(() => {
|
||||||
|
|
||||||
|
return totalLoans.value / totalExpenses.value * 100
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const savingRatio = computed(() => {
|
||||||
|
|
||||||
|
return totalSaving.value / totalExpenses.value * 100
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalSaving = computed(() => {
|
||||||
|
let value = 0
|
||||||
|
categories.value.filter((cat) => cat.category.id == 35).forEach(cat => {
|
||||||
|
value += cat.currentLimit
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const dailyRatio = computed(() => {
|
||||||
|
const value = (totalExpenses.value - totalLoans.value - totalSaving.value) / totalExpenses.value
|
||||||
|
|
||||||
|
return value * 100
|
||||||
|
})
|
||||||
|
const fetchPlannedIncomes = async () => {
|
||||||
|
plannedIncomes.value = await getBudgetTransactions(route.params.id, 'PLANNED', 'INCOME')
|
||||||
|
}
|
||||||
|
const plannedExpenses = ref<Transaction[]>([])
|
||||||
|
const totalExpenses = computed(() => {
|
||||||
|
let totalExpense = 0;
|
||||||
|
categories.value.forEach((cat) => {
|
||||||
|
let catValue = cat.currentLimit - cat.categoryPlannedLimit
|
||||||
|
|
||||||
|
plannedExpenses.value.filter(t => t.category.id == cat.category.id).forEach((i) => {
|
||||||
|
|
||||||
|
catValue += i.amount
|
||||||
|
})
|
||||||
|
|
||||||
|
totalExpense += catValue
|
||||||
|
})
|
||||||
|
|
||||||
|
return totalExpense
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const totalPlannedExpenses = computed(() => {
|
||||||
|
let expenses = 0
|
||||||
|
plannedExpenses.value.forEach((t) => {
|
||||||
|
expenses += t.amount
|
||||||
|
})
|
||||||
|
return expenses
|
||||||
|
})
|
||||||
const totalExpenseLeftToSpend = computed(() => {
|
const totalExpenseLeftToSpend = computed(() => {
|
||||||
let totalExpenseLeftToSpend = 0;
|
let totalExpenseLeftToSpend = 0;
|
||||||
budgetInfo.value?.plannedExpenses.forEach(i => {
|
plannedExpenses.value.forEach(i => {
|
||||||
if (!i.isDone) {
|
if (!i.isDone) {
|
||||||
totalExpenseLeftToSpend += i.amount
|
totalExpenseLeftToSpend += i.amount
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return totalExpenseLeftToSpend
|
return totalExpenseLeftToSpend
|
||||||
})
|
})
|
||||||
const fetchBudgetInfo = async () => {
|
const fetchPlannedExpenses = async () => {
|
||||||
updateLoading.value = true
|
plannedExpenses.value = await getBudgetTransactions(route.params.id, 'PLANNED', 'EXPENSE')
|
||||||
console.log('Trying to get budget Info')
|
updateLoading.value = false
|
||||||
budgetInfo.value = await getBudgetInfo(route.params.id);
|
}
|
||||||
|
const transactions = ref<Transaction[]>([])
|
||||||
|
const fetchBudgetTransactions = async () => {
|
||||||
|
|
||||||
|
|
||||||
console.log(budgetInfo.value)
|
transactions.value = await getBudgetTransactions(route.params.id)
|
||||||
console.log(budgetInfo.value?.chartData[0])
|
|
||||||
incomeExpenseChartData.value = {
|
|
||||||
labels: ['10.10', '25.10'],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Income',
|
|
||||||
backgroundColor: ['#2E8B57'],
|
|
||||||
data: budgetInfo.value?.chartData[0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Expense',
|
|
||||||
backgroundColor: ['#B22222'],
|
|
||||||
data: budgetInfo.value?.chartData[1],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
leftForUnplanned.value =
|
|
||||||
budgetInfo.value.chartData[0][0] +
|
|
||||||
budgetInfo.value.chartData[0][1] -
|
|
||||||
budgetInfo.value.chartData[1][0] -
|
|
||||||
budgetInfo.value.chartData[1][1]
|
|
||||||
|
|
||||||
updateLoading.value = false
|
updateLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateTransactions = async () => {
|
||||||
|
|
||||||
|
await Promise.all([fetchPlannedIncomes(), fetchPlannedExpenses()])
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = ref<BudgetCategory[]>([])
|
||||||
|
const fetchBudgetCategories = async () => {
|
||||||
|
|
||||||
|
|
||||||
|
categories.value = await getBudgetCategories(route.params.id)
|
||||||
|
updateLoading.value = false
|
||||||
|
}
|
||||||
|
const transactionCategoriesSums = ref()
|
||||||
|
const fetchBudgetTransactionCategoriesSums = async () => {
|
||||||
|
|
||||||
|
|
||||||
|
transactionCategoriesSums.value = await getBudgetCategoriesSums(route.params.id)
|
||||||
|
updateLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const budgetInfo = ref<BudgetInfo>();
|
||||||
|
const fetchBudgetInfo = async () => {
|
||||||
|
|
||||||
|
|
||||||
|
budget.value = await getBudgetInfo(route.params.id);
|
||||||
|
updateLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const updateBudgetCategory = async (category) => {
|
const updateBudgetCategory = async (category) => {
|
||||||
console.log(category)
|
|
||||||
loading.value = true
|
// loading.value = true
|
||||||
await updateBudgetCategoryRequest(budgetInfo.value.budget.id, category)
|
await updateBudgetCategoryRequest(budget.value.id, category)
|
||||||
budgetInfo.value = await getBudgetInfo(route.params.id)
|
|
||||||
console.log(budgetInfo.value)
|
// categories.value = await getBudgetCategories(route.params.id)
|
||||||
console.log(budgetInfo.value?.chartData[0])
|
|
||||||
incomeExpenseChartData.value = {
|
// incomeExpenseChartData.value = {
|
||||||
labels: ['10.10', '25.10'],
|
// labels: ['10.10', '25.10'],
|
||||||
datasets: [
|
// datasets: [
|
||||||
{
|
// {
|
||||||
label: 'Income',
|
// label: 'Income',
|
||||||
backgroundColor: ['#2E8B57'],
|
// backgroundColor: ['#2E8B57'],
|
||||||
data: budgetInfo.value?.chartData[0],
|
// data: budgetInfo.value?.chartData[0],
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
label: 'Expense',
|
// label: 'Expense',
|
||||||
backgroundColor: ['#B22222'],
|
// backgroundColor: ['#B22222'],
|
||||||
data: budgetInfo.value?.chartData[1],
|
// data: budgetInfo.value?.chartData[1],
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
}
|
// }
|
||||||
leftForUnplanned.value =
|
// leftForUnplanned.value =
|
||||||
budgetInfo.value.chartData[0][0] +
|
// budgetInfo.value.chartData[0][0] +
|
||||||
budgetInfo.value.chartData[0][1] -
|
// budgetInfo.value.chartData[0][1] -
|
||||||
budgetInfo.value.chartData[1][0] -
|
// budgetInfo.value.chartData[1][0] -
|
||||||
budgetInfo.value.chartData[1][1]
|
// budgetInfo.value.chartData[1][1]
|
||||||
loading.value = false
|
// // loading.value = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Пример данных
|
const twentyFour = computed(() => {
|
||||||
const incomeExpenseChartData = ref();
|
let twentyFour = new Date(budget.value?.dateFrom)
|
||||||
|
twentyFour.setDate(24)
|
||||||
|
return twentyFour
|
||||||
|
})
|
||||||
|
|
||||||
|
const incomesByPeriod = computed(() => {
|
||||||
|
let incomesUntil25 = 0
|
||||||
|
let incomesFrom25 = 0
|
||||||
|
plannedIncomes.value.forEach((i) => {
|
||||||
|
|
||||||
|
if (i.date <= budget.value?.dateFrom && i.date <= twentyFour.value) {
|
||||||
|
incomesUntil25 += i.amount
|
||||||
|
} else {
|
||||||
|
incomesFrom25 += i.amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return [incomesUntil25, incomesFrom25]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const expensesByPeriod = computed(() => {
|
||||||
|
let expensesUntil25 = 0
|
||||||
|
let expensesFrom25 = 0
|
||||||
|
plannedExpenses.value.forEach((i) => {
|
||||||
|
|
||||||
|
if (i.date >= budget.value?.dateFrom && i.date <= twentyFour.value) {
|
||||||
|
expensesUntil25 += i.amount
|
||||||
|
} else {
|
||||||
|
expensesFrom25 += i.amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
categories.value.forEach((i) => {
|
||||||
|
expensesUntil25 += (i.currentLimit - i.categoryPlannedLimit) / 2
|
||||||
|
expensesFrom25 += (i.currentLimit - i.categoryPlannedLimit) / 2
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return [expensesUntil25, expensesFrom25]
|
||||||
|
})
|
||||||
|
|
||||||
|
const pieChartData = computed(() => {
|
||||||
|
|
||||||
|
let labels = []
|
||||||
|
let values = []
|
||||||
|
categories.value.forEach((i) => {
|
||||||
|
labels.push(i.category.name)
|
||||||
|
values.push(i.currentLimit / totalExpenses.value)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
clip: {left: 100, top: false, right: 50, bottom: 0},
|
||||||
|
radius: '100%',
|
||||||
|
data: values,
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(6, 182, 212, 0.2)', 'rgba(249, 115, 22, 0.2)', 'rgba(50, 205, 50, 0.2)',
|
||||||
|
'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)',
|
||||||
|
'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)',
|
||||||
|
'rgba(123, 239, 178, 0.2)', 'rgba(255, 105, 180, 0.2)', 'rgba(147, 112, 219, 0.2)',
|
||||||
|
'rgba(60, 179, 113, 0.2)', 'rgba(100, 149, 237, 0.2)', 'rgba(220, 20, 60, 0.2)',
|
||||||
|
'rgba(255, 140, 0, 0.2)', 'rgba(72, 61, 139, 0.2)', 'rgba(210, 105, 30, 0.2)',
|
||||||
|
'rgba(106, 90, 205, 0.2)', 'rgba(199, 21, 133, 0.2)', 'rgba(32, 178, 170, 0.2)',
|
||||||
|
'rgba(65, 105, 225, 0.2)', 'rgba(218, 165, 32, 0.2)', 'rgba(255, 127, 80, 0.2)',
|
||||||
|
'rgba(46, 139, 87, 0.2)', 'rgba(139, 69, 19, 0.2)', 'rgba(75, 0, 130, 0.2)',
|
||||||
|
'rgba(255, 69, 0, 0.2)', 'rgba(244, 164, 96, 0.2)'
|
||||||
|
],
|
||||||
|
hoverBackgroundColor: [
|
||||||
|
'rgba(6, 182, 212, 1)', 'rgba(249, 115, 22, 1)', 'rgba(50, 205, 50, 1)',
|
||||||
|
'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)',
|
||||||
|
'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)',
|
||||||
|
'rgba(123, 239, 178, 1)', 'rgba(255, 105, 180, 1)', 'rgba(147, 112, 219, 1)',
|
||||||
|
'rgba(60, 179, 113, 1)', 'rgba(100, 149, 237, 1)', 'rgba(220, 20, 60, 1)',
|
||||||
|
'rgba(255, 140, 0, 1)', 'rgba(72, 61, 139, 1)', 'rgba(210, 105, 30, 1)',
|
||||||
|
'rgba(106, 90, 205, 1)', 'rgba(199, 21, 133, 1)', 'rgba(32, 178, 170, 1)',
|
||||||
|
'rgba(65, 105, 225, 1)', 'rgba(218, 165, 32, 1)', 'rgba(255, 127, 80, 1)',
|
||||||
|
'rgba(46, 139, 87, 1)', 'rgba(139, 69, 19, 1)', 'rgba(75, 0, 130, 1)',
|
||||||
|
'rgba(255, 69, 0, 1)', 'rgba(244, 164, 96, 1)'
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
const pieChartOptions = ref({
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
left: 50, // Добавляем отступ слева
|
||||||
|
right: 50 // Добавляем отступ справа
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false // Отключаем легенду
|
||||||
|
},
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'end', // Позиция метки относительно данных
|
||||||
|
align: 'top', // Выравнивание метки
|
||||||
|
formatter: (value, context) => {
|
||||||
|
const label = context.chart.data.labels[context.dataIndex];
|
||||||
|
const percentage = (value * 100).toFixed(0);
|
||||||
|
return percentage >= 2 ? `${label} ${percentage}%` : '';
|
||||||
|
},
|
||||||
|
color: 'black', // Цвет текста метки
|
||||||
|
font: {
|
||||||
|
size: 12 // Устанавливаем меньший размер шрифта
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true, // Включить tooltips
|
||||||
|
callbacks: {
|
||||||
|
title: function (tooltipItems) {
|
||||||
|
return tooltipItems[0].dataset.label;
|
||||||
|
},
|
||||||
|
label: function (tooltipItems) {
|
||||||
|
return Number(tooltipItems.formattedValue * 100).toFixed(0) + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const incomeExpenseChartData = computed(() => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels: ['до 25 ', 'с 25'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Пополнения',
|
||||||
|
data: incomesByPeriod.value,
|
||||||
|
backgroundColor: ['rgba(6, 182, 212, 0.2)', 'rgba(6, 182, 212, 0.2)'],
|
||||||
|
borderColor: ['rgb(6, 182, 212)', 'rgb(6, 182, 212)'],
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Расходы',
|
||||||
|
data: expensesByPeriod.value,
|
||||||
|
backgroundColor: ['rgba(249, 115, 22, 0.2)', 'rgba(249, 115, 22, 0.2)'],
|
||||||
|
borderColor: ['rgb(249, 115, 22)', 'rgb(249, 115, 22)'],
|
||||||
|
borderWidth: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const incomeExpenseChartOptions = ref({
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false // Отключаем легенду
|
||||||
|
},
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'end', // Позиция метки относительно данных
|
||||||
|
align: 'top', // Выравнивание метки
|
||||||
|
formatter: (value) => {
|
||||||
|
// const label = context.chart.data.labels[context.dataIndex];
|
||||||
|
|
||||||
|
return formatAmount(value) + '₽';
|
||||||
|
// return percentage >= 2 ? `${percentage} ` : '';
|
||||||
|
},
|
||||||
|
color: 'black', // Цвет текста метки
|
||||||
|
font: {
|
||||||
|
size: 12 // Устанавливаем меньший размер шрифта
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true, // Включить tooltips
|
||||||
|
callbacks: {
|
||||||
|
title: function (tooltipItems) {
|
||||||
|
return tooltipItems[0].dataset.label;
|
||||||
|
},
|
||||||
|
label: function (tooltipItems) {
|
||||||
|
console.log(tooltipItems);
|
||||||
|
return formatAmount(tooltipItems.raw) + '₽';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchBudgetInfo()
|
fetchBudgetInfo(),
|
||||||
|
// budget.value = await getBudgetInfo(route.params.id),
|
||||||
|
fetchPlannedIncomes(),
|
||||||
|
fetchPlannedExpenses(),
|
||||||
|
fetchBudgetCategories(),
|
||||||
|
fetchBudgetTransactions(),
|
||||||
|
fetchBudgetTransactionCategoriesSums()
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during fetching data:', error);
|
console.error('Error during fetching data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
}, 100)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -332,13 +665,16 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.max-h-tlist {
|
.max-h-tlist {
|
||||||
max-height: 45dvh; /* Ограничение высоты списка */
|
max-height: 1170px; /* Ограничение высоты списка */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.box-shadow-inner {
|
.box-shadow-inner {
|
||||||
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.3);
|
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -40,10 +40,10 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['create-transaction', 'update-transaction', 'delete-transaction', 'close-drawer']);
|
const emit = defineEmits(['create-transaction', 'update-transaction', 'delete-transaction', 'close-drawer', 'transaction-updated']);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const categoryTypeChanged = () => {
|
const categoryTypeChanged = () => {
|
||||||
console.log(selectedCategoryType.value)
|
|
||||||
editedTransaction.value.category = selectedCategoryType.value.code == "EXPENSE" ? expenseCategories.value[0] : incomeCategories.value[0];
|
editedTransaction.value.category = selectedCategoryType.value.code == "EXPENSE" ? expenseCategories.value[0] : incomeCategories.value[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -82,11 +82,30 @@ const fetchCategoriesAndTypes = async () => {
|
|||||||
|
|
||||||
categoryTypes.value = categoryTypesResponse.data;
|
categoryTypes.value = categoryTypesResponse.data;
|
||||||
transactionTypes.value = transactionTypesResponse.data;
|
transactionTypes.value = transactionTypesResponse.data;
|
||||||
|
console.log(entireCategories.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching categories and types:', error);
|
console.error('Error fetching categories and types:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkForm = () => {
|
||||||
|
const errorMessages = {
|
||||||
|
transactionType: 'Тип транзакции должен быть выбран',
|
||||||
|
category: 'Категория должна быть выбрана',
|
||||||
|
date: 'Дата должна быть выбрана',
|
||||||
|
comment: 'Комментарий должен быть введен',
|
||||||
|
amount: 'Сумма не может быть пустой или 0'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!editedTransaction.value.transactionType) return showError(errorMessages.transactionType);
|
||||||
|
if (!editedTransaction.value.category) return showError(errorMessages.category);
|
||||||
|
if (!editedTransaction.value.date) return showError(errorMessages.date);
|
||||||
|
if (!editedTransaction.value.comment) return showError(errorMessages.comment);
|
||||||
|
if (!editedTransaction.value.amount || editedTransaction.value.amount === 0) return showError(errorMessages.amount);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// Инициализация данных
|
// Инициализация данных
|
||||||
const prepareData = () => {
|
const prepareData = () => {
|
||||||
if (!props.transaction) {
|
if (!props.transaction) {
|
||||||
@@ -102,39 +121,84 @@ const prepareData = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
const result = ref(false)
|
||||||
|
const isError = ref(false)
|
||||||
|
const resultText = ref('')
|
||||||
|
|
||||||
|
const computeResult = (resultState, error) => {
|
||||||
|
|
||||||
|
if (!resultState && error) {
|
||||||
|
result.value = true;
|
||||||
|
isError.value = true
|
||||||
|
resultText.value = `Ошибка: ${error.message}`
|
||||||
|
} else {
|
||||||
|
|
||||||
|
result.value = true;
|
||||||
|
isError.value = false
|
||||||
|
resultText.value = 'Успех!'
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
result.value = false
|
||||||
|
resultText.value = ''
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showError = (message) => {
|
||||||
|
result.value = true;
|
||||||
|
isError.value = true;
|
||||||
|
resultText.value = message;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
// Создание транзакции
|
// Создание транзакции
|
||||||
const createTransaction = async () => {
|
const createTransaction = async () => {
|
||||||
try {
|
if (checkForm()) {
|
||||||
loading.value = true;
|
try {
|
||||||
if (editedTransaction.value.transactionType.code === 'INSTANT') {
|
loading.value = true;
|
||||||
editedTransaction.value.isDone = true;
|
if (editedTransaction.value.transactionType.code === 'INSTANT') {
|
||||||
|
editedTransaction.value.isDone = true;
|
||||||
|
}
|
||||||
|
await createTransactionRequest(editedTransaction.value);
|
||||||
|
toast.add({severity: 'success', summary: 'Transaction created!', detail: 'Транзакция создана!', life: 3000});
|
||||||
|
emit('create-transaction', editedTransaction.value);
|
||||||
|
computeResult(true)
|
||||||
|
resetForm();
|
||||||
|
} catch (error) {
|
||||||
|
computeResult(false, error)
|
||||||
|
console.error('Error creating transaction:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
await createTransactionRequest(editedTransaction.value);
|
|
||||||
toast.add({severity: 'success', summary: 'Transaction created!', detail: 'Транзакция создана!', life: 3000});
|
|
||||||
emit('create-transaction', editedTransaction.value);
|
|
||||||
resetForm();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating transaction:', error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
console.log(editedTransaction.value)
|
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
result.value = false
|
||||||
|
resultText.value = ''
|
||||||
|
}, 1000)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обновление транзакции
|
// Обновление транзакции
|
||||||
const updateTransaction = async () => {
|
const updateTransaction = async () => {
|
||||||
try {
|
if (checkForm()) {
|
||||||
loading.value = true;
|
try {
|
||||||
const response = await updateTransactionRequest(editedTransaction.value);
|
loading.value = true;
|
||||||
editedTransaction.value = response.data;
|
const response = await updateTransactionRequest(editedTransaction.value);
|
||||||
toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
response.data;
|
||||||
emit('update-transaction', editedTransaction.value);
|
// toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
||||||
} catch (error) {
|
emit('update-transaction', editedTransaction.value);
|
||||||
console.error('Error updating transaction:', error);
|
emit('transaction-updated');
|
||||||
} finally {
|
computeResult(true)
|
||||||
loading.value = false;
|
} catch (error) {
|
||||||
|
computeResult(false, error)
|
||||||
|
console.error('Error updating transaction:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
result.value = false
|
||||||
|
resultText.value = ''
|
||||||
|
|
||||||
|
}, 1000)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Удаление транзакции
|
// Удаление транзакции
|
||||||
@@ -145,7 +209,11 @@ const deleteTransaction = async () => {
|
|||||||
toast.add({severity: 'success', summary: 'Transaction deleted!', detail: 'Транзакция удалена!', life: 3000});
|
toast.add({severity: 'success', summary: 'Transaction deleted!', detail: 'Транзакция удалена!', life: 3000});
|
||||||
emit('delete-transaction', editedTransaction.value);
|
emit('delete-transaction', editedTransaction.value);
|
||||||
closeDrawer()
|
closeDrawer()
|
||||||
|
computeResult(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
computeResult(false, error)
|
||||||
|
toast.add({severity: 'warn', summary: 'Error!', detail: 'Транзакция обновлена!', life: 3000});
|
||||||
|
|
||||||
console.error('Error deleting transaction:', error);
|
console.error('Error deleting transaction:', error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -162,15 +230,15 @@ const resetForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dateErrorMessage = computed(() => {
|
const dateErrorMessage = computed(() => {
|
||||||
console.log('tut')
|
|
||||||
if (editedTransaction.value.transactionType.code != 'PLANNED' && editedTransaction.value.date > new Date()) {
|
if (editedTransaction.value.transactionType.code != 'PLANNED' && editedTransaction.value.date > new Date()) {
|
||||||
console.log('tut2')
|
|
||||||
return 'При мгновенных тратах дата должна быть меньше текущей!'
|
return 'При мгновенных тратах дата должна быть меньше текущей!'
|
||||||
} else if (editedTransaction.value.transactionType.code == 'PLANNED' && editedTransaction.value.date < new Date()) {
|
} else if (editedTransaction.value.transactionType.code == 'PLANNED' && editedTransaction.value.date < new Date()) {
|
||||||
console.log('tu3')
|
|
||||||
return 'При плановых тратах дата должна быть больше текущей!'
|
return 'При плановых тратах дата должна быть больше текущей!'
|
||||||
} else {
|
} else {
|
||||||
console.log('tu4')
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -183,12 +251,16 @@ const userAgent = ref(null);
|
|||||||
// Мониторинг при монтировании
|
// Мониторинг при монтировании
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
await fetchCategoriesAndTypes();
|
await fetchCategoriesAndTypes();
|
||||||
|
|
||||||
prepareData();
|
prepareData();
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
const deviceInfo = platform;
|
const deviceInfo = platform;
|
||||||
isMobile.value = deviceInfo.os.family === 'iOS' || deviceInfo.os.family === 'Android';
|
isMobile.value = deviceInfo.os.family === 'iOS' || deviceInfo.os.family === 'Android';
|
||||||
console.log(deviceInfo);
|
console.log()
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -196,14 +268,32 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<div class="card flex justify-center h-dvh">
|
<div class="card flex justify-center h-dvh">
|
||||||
|
|
||||||
|
|
||||||
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
||||||
position="right" @hide="closeDrawer"
|
position="right" @hide="closeDrawer"
|
||||||
class="!w-128 ">
|
class="!w-128 ">
|
||||||
|
<div v-if="result" class="absolute top-0 left-0 w-full h-full flex items-center justify-center z-50">
|
||||||
|
<div
|
||||||
|
class=" px-10 py-5 rounded-lg border border-gray-200 flex flex-col items-center gap-4"
|
||||||
|
:class="isError ? 'bg-red-100' : 'bg-green-100'"
|
||||||
|
aria-label="Custom ProgressSpinner">
|
||||||
|
<i class="pi pi-check " :class="isError ? 'text-red-500' : 'text-green-500'" style="font-size: 2rem;"/>
|
||||||
|
<p class="text-green-700" :class="isError ? 'text-red-500' : 'text-green-500'">{{ resultText }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="absolute w-full h-screen">
|
||||||
|
<!-- Полупрозрачный белый фон -->
|
||||||
|
<!-- <div class="absolute top-0 left-0 w-full h-full bg-white opacity-50 z-0"></div>-->
|
||||||
|
|
||||||
|
<!-- Спиннер поверх -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<LoadingView v-if="loading"/>
|
<LoadingView v-if="loading"/>
|
||||||
<div v-else class=" grid gap-4 w-full ">
|
<div v-else class=" grid gap-4 w-full ">
|
||||||
|
|
||||||
<div class="relative w-full justify-center justify-items-center ">
|
<div class="relative w-full justify-center justify-items-center ">
|
||||||
{{userAgent}}
|
|
||||||
<div class="flex flex-col justify-items-center gap-2">
|
<div class="flex flex-col justify-items-center gap-2">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<!-- {{editedTransaction.value.transactionType}}-->
|
<!-- {{editedTransaction.value.transactionType}}-->
|
||||||
@@ -217,7 +307,7 @@ onMounted(async () => {
|
|||||||
aria-labelledby="basic"
|
aria-labelledby="basic"
|
||||||
@change="categoryTypeChanged" class="justify-center"/>
|
@change="categoryTypeChanged" class="justify-center"/>
|
||||||
</div>
|
</div>
|
||||||
<button class="border border-gray-300 rounded-lg w-full z-50"
|
<button class="border border-gray-300 rounded-lg w-full z-40"
|
||||||
@click="isCategorySelectorOpened = !isCategorySelectorOpened">
|
@click="isCategorySelectorOpened = !isCategorySelectorOpened">
|
||||||
<div class="flex flex-row items-center pe-4 py-2 ">
|
<div class="flex flex-row items-center pe-4 py-2 ">
|
||||||
<div class="flex flex-row justify-between w-full gap-4 px-4 items-center">
|
<div class="flex flex-row justify-between w-full gap-4 px-4 items-center">
|
||||||
@@ -241,7 +331,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- Анимированное открытие списка категорий -->
|
<!-- Анимированное открытие списка категорий -->
|
||||||
<div v-show="isCategorySelectorOpened"
|
<div v-show="isCategorySelectorOpened"
|
||||||
class="absolute left-0 right-0 top-full overflow-hidden z-50 border-b-4 border-x rounded-b-lg bg-white shadow-lg transition-all duration-500"
|
class="absolute left-0 right-0 top-full overflow-y-auto z-50 border-b-4 border-x rounded-b-lg bg-white shadow-lg transition-all duration-500"
|
||||||
:class="{ 'max-h-0': !isCategorySelectorOpened, 'max-h-[500px]': isCategorySelectorOpened }">
|
:class="{ 'max-h-0': !isCategorySelectorOpened, 'max-h-[500px]': isCategorySelectorOpened }">
|
||||||
<div class="grid grid-cols-2 mt-2">
|
<div class="grid grid-cols-2 mt-2">
|
||||||
<button
|
<button
|
||||||
@@ -319,8 +409,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
{{ keyboardOpen }}
|
{{ keyboardOpen }}
|
||||||
<div class="fixed col-12 flex justify-content-end gap-4"
|
<div class="fixed col-12 flex justify-content-end gap-4 bottom-8">
|
||||||
:style="keyboardOpen && isMobile ? 'bottom : 350px;' :' bottom: 5rem;'">
|
|
||||||
|
|
||||||
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
||||||
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
@click="isEditing ? updateTransaction() : createTransaction()"/>
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Button from "primevue/button";
|
|
||||||
import Select from "primevue/select";
|
|
||||||
import InputNumber from "primevue/inputnumber";
|
|
||||||
|
|
||||||
import {Category} from "@/models/Category";
|
|
||||||
import {ref} from "vue";
|
|
||||||
import {formatAmount} from "@/utils/utils";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
category: {
|
|
||||||
type: Object as Category,
|
|
||||||
require: true
|
|
||||||
},
|
|
||||||
budgetId: {
|
|
||||||
type: Number,
|
|
||||||
require: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emits = defineEmits(['category-updated'])
|
|
||||||
|
|
||||||
const isEditing = ref(false);
|
|
||||||
const startEditing = (a) => {
|
|
||||||
isEditing.value = true;
|
|
||||||
console.log(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopEditing = () => {
|
|
||||||
isEditing.value = false;
|
|
||||||
|
|
||||||
emits('category-updated', editedCategory.value);
|
|
||||||
|
|
||||||
}
|
|
||||||
const selectedCategorySettingType = ref(props.category.categorySetting.type)
|
|
||||||
|
|
||||||
const categorySettingTypes = ref([
|
|
||||||
{
|
|
||||||
code: 'CONST',
|
|
||||||
name: 'По значению'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'AVG',
|
|
||||||
name: 'По среднему'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'PERCENT',
|
|
||||||
name: 'По проценту'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'LIMIT',
|
|
||||||
name: 'По лимиту'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
const categoryAmount = ref(1000)
|
|
||||||
|
|
||||||
const editedCategory = ref(props.category);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
|
|
||||||
<div class="p-2 shadow-lg rounded-lg bg-white flex justify-between ">
|
|
||||||
|
|
||||||
<div :class="isEditing ? 'w-1/5': ''" class="min-w-1 w-4/6">
|
|
||||||
<h4 class="text-lg line-clamp-1">{{ editedCategory.category.name }}</h4>
|
|
||||||
<p class="text-sm text-gray-500 line-clamp-1 min-w-1 ">{{ editedCategory.category.description }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row gap-2 justify-end w-fit ">
|
|
||||||
|
|
||||||
<Select v-if="isEditing" v-model="editedCategory.categorySetting.type" :options="categorySettingTypes"
|
|
||||||
optionLabel="name"
|
|
||||||
class="line-clamp-1 w-fit"/>
|
|
||||||
<!-- Сумма, которая становится редактируемой при клике -->
|
|
||||||
<button v-if="!isEditing" @click="startEditing"
|
|
||||||
class="text-lg font-bold cursor-pointer w-full line-clamp-1">
|
|
||||||
<p class="line-clamp-1 w-fit">{{ formatAmount(editedCategory.categorySetting.value) }} ₽</p>
|
|
||||||
</button>
|
|
||||||
<!-- @blur="stopEditing('unplannedCategories')" @keyup.enter="stopEditing('unplannedCategories')"–>-->
|
|
||||||
|
|
||||||
<InputNumber v-else ref="inputRefs" type="text" v-model="editedCategory.categorySetting.settingValue"
|
|
||||||
:disabled=" editedCategory.categorySetting.type.code == 'PERCENT' ? false : editedCategory.categorySetting.type.code == 'LIMIT' ? true : editedCategory.categorySetting.type.code == 'AVG' "
|
|
||||||
:class="editedCategory.categorySetting.type.code != 'CONST' || editedCategory.categorySetting.type.code != 'PERCENT' ? 'text-gray-500' : 'text-black' "
|
|
||||||
class="text-lg font-bold border-b-2 border-gray-300 outline-none focus:border-blue-500 text-right"
|
|
||||||
:min="0" :max="editedCategory.categorySetting.type.code == 'PERCENT' ? 100 : 9000000000"/>
|
|
||||||
<Button v-if="isEditing" @click="stopEditing" icon="pi pi-check" severity="success" rounded outlined
|
|
||||||
aria-label="Search"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -11,7 +11,7 @@ const recurrentPayments = ref<RecurrentPayment[]>([]);
|
|||||||
const fetchRecurrentPayments = async () => {
|
const fetchRecurrentPayments = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
console.log('loaded')
|
|
||||||
const result = await getRecurrentPayments();
|
const result = await getRecurrentPayments();
|
||||||
recurrentPayments.value = result.data;
|
recurrentPayments.value = result.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const fetchCategories = async () => {
|
|||||||
try {
|
try {
|
||||||
const response = await getTransactions('INSTANT');
|
const response = await getTransactions('INSTANT');
|
||||||
transactions.value = response.data
|
transactions.value = response.data
|
||||||
console.log(transactions.value)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching categories:', error);
|
console.error('Error fetching categories:', error);
|
||||||
}
|
}
|
||||||
@@ -72,7 +71,7 @@ onMounted(async () => {
|
|||||||
</IconField>
|
</IconField>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<BudgetTransactionView class="mb-2" v-for="transaction in filteredTransactions" :transaction="transaction"/>
|
<BudgetTransactionView class="mb-2" v-for="transaction in filteredTransactions" :transaction="transaction" :is-list="true"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ import Aura from '@primevue/themes/aura';
|
|||||||
import router from './router';
|
import router from './router';
|
||||||
import Ripple from "primevue/ripple";
|
import Ripple from "primevue/ripple";
|
||||||
import ToastService from 'primevue/toastservice'
|
import ToastService from 'primevue/toastservice'
|
||||||
|
import Tooltip from 'primevue/tooltip';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(ToastService);
|
app.use(ToastService);
|
||||||
app.directive('ripple', Ripple);
|
app.directive('ripple', Ripple);
|
||||||
|
app.directive('tooltip', Tooltip);
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
preset: Aura
|
preset: Aura
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import axios from 'axios';
|
|||||||
|
|
||||||
// Создание экземпляра axios с базовым URL
|
// Создание экземпляра axios с базовым URL
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
// baseURL: 'https://luminic.space/api/v1',
|
baseURL: 'https://luminic.space/api/v1',
|
||||||
baseURL: 'http://localhost:8000/api/v1',
|
// baseURL: 'http://localhost:8000/api/v1',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {createRouter, createWebHistory, useRoute} from 'vue-router';
|
import {createRouter, createWebHistory} from 'vue-router';
|
||||||
import CategoriesList from '@/components/settings/categories/CategoriesList.vue';
|
import CategoriesList from '@/components/settings/categories/CategoriesList.vue';
|
||||||
import CreateCategoryModal from "@/components/settings/categories/CreateCategoryModal.vue";
|
import CreateCategoryModal from "@/components/settings/categories/CreateCategoryModal.vue";
|
||||||
import CategoryListItem from "@/components/settings/categories/CategoryListItem.vue"; // Импортируем новый компонент
|
import CategoryListItem from "@/components/settings/categories/CategoryListItem.vue"; // Импортируем новый компонент
|
||||||
@@ -10,18 +10,33 @@ import TransactionList from "@/components/transactions/TransactionList.vue";
|
|||||||
import LoginView from "@/components/auth/LoginView.vue";
|
import LoginView from "@/components/auth/LoginView.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
{path: '/login', component: LoginView},
|
||||||
{path: '/', name: 'Budgets main', component: BudgetList, meta: { requiresAuth: true }},
|
{path: '/', name: 'Budgets main', component: BudgetList, meta: {requiresAuth: true}},
|
||||||
{ path: '/login', component: LoginView },
|
{path: '/analytics', name: 'Analytics', component: BudgetList, meta: {requiresAuth: true}},
|
||||||
{path: '/budgets', name: 'Budgets', component: BudgetList, meta: { requiresAuth: true }},
|
{path: '/budgets', name: 'Budgets', component: BudgetList, meta: {requiresAuth: true}},
|
||||||
{path: '/budgets/:id', name: 'BudgetView', component: BudgetView, meta: { requiresAuth: true }},
|
{path: '/budgets/:id', name: 'BudgetView', component: BudgetView, meta: {requiresAuth: true}},
|
||||||
{path: '/transactions/:mode*', name: 'Transaction List', component: TransactionList, meta: { requiresAuth: true }},
|
{path: '/transactions/:mode*', name: 'Transaction List', component: TransactionList, meta: {requiresAuth: true}},
|
||||||
// {path: '/transactions/create', name: 'Transaction List', component: TransactionList},
|
// {path: '/transactions/create', name: 'Transaction List', component: TransactionList},
|
||||||
{path: '/settings/', name: 'Settings', component: SettingsView, meta: { requiresAuth: true }},
|
{path: '/settings/', name: 'Settings', component: SettingsView, meta: {requiresAuth: true}},
|
||||||
{path: '/settings/categories', name: 'Categories', component: CategoriesList, meta: { requiresAuth: true }},
|
{path: '/settings/categories', name: 'Categories', component: CategoriesList, meta: {requiresAuth: true}},
|
||||||
{path: '/settings/recurrents', name: 'Recurrent operations list', component: RecurrentList, meta: { requiresAuth: true }},
|
{
|
||||||
{path: '/settings/categories/create', name: "Categories Creation", component: CreateCategoryModal, meta: { requiresAuth: true }},// Добавляем новый маршрут
|
path: '/settings/recurrents',
|
||||||
{path: '/settings/categories/one', name: "Categories Creation", component: CategoryListItem, meta: { requiresAuth: true }}// Добавляем новый маршрут
|
name: 'Recurrent operations list',
|
||||||
|
component: RecurrentList,
|
||||||
|
meta: {requiresAuth: true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/categories/create',
|
||||||
|
name: "Categories Creation",
|
||||||
|
component: CreateCategoryModal,
|
||||||
|
meta: {requiresAuth: true}
|
||||||
|
},// Добавляем новый маршрут
|
||||||
|
{
|
||||||
|
path: '/settings/categories/one',
|
||||||
|
name: "Categories Creation",
|
||||||
|
component: CategoryListItem,
|
||||||
|
meta: {requiresAuth: true}
|
||||||
|
}// Добавляем новый маршрут
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -32,11 +47,8 @@ const router = createRouter({
|
|||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (to.meta.requiresAuth && !token) {
|
if (to.meta.requiresAuth && !token) {
|
||||||
const router= useRoute()
|
// const router = useRoute()
|
||||||
console.log(to)
|
next('/login?back=' + to.fullPath);
|
||||||
console.log(router.path)
|
|
||||||
console.log(router.params)
|
|
||||||
next('/login?back='+to.fullPath);
|
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Budget, BudgetCategory} from "@/models/Budget";
|
|||||||
// Импортируете настроенный экземпляр axios
|
// Импортируете настроенный экземпляр axios
|
||||||
|
|
||||||
export const getBudgetInfos = async () => {
|
export const getBudgetInfos = async () => {
|
||||||
console.log('getBudgetInfos');
|
|
||||||
let response = await apiClient.get('/budgets/');
|
let response = await apiClient.get('/budgets/');
|
||||||
let budgetInfos = response.data;
|
let budgetInfos = response.data;
|
||||||
budgetInfos.forEach((budgetInfo: Budget) => {
|
budgetInfos.forEach((budgetInfo: Budget) => {
|
||||||
@@ -24,22 +24,43 @@ export const getBudgetInfos = async () => {
|
|||||||
return budgetInfos
|
return budgetInfos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getBudgetTransactions = async (budgetId, transactionType, categoryType) => {
|
||||||
|
|
||||||
|
let url = `/budgets/${budgetId}/transactions`
|
||||||
|
if (transactionType) {
|
||||||
|
url += '/' + transactionType
|
||||||
|
}
|
||||||
|
if (transactionType && categoryType) {
|
||||||
|
url += '/'+categoryType
|
||||||
|
}
|
||||||
|
// if (!categoryType) {
|
||||||
|
// throw new Error('No CategoryType');
|
||||||
|
// }
|
||||||
|
let response = await apiClient.get(url);
|
||||||
|
let transactions = response.data;
|
||||||
|
transactions.forEach(e => {
|
||||||
|
e.date = new Date(e.date)
|
||||||
|
})
|
||||||
|
return transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBudgetCategories = async (budgetId) => {
|
||||||
|
let response = await apiClient.get('/budgets/' + budgetId + '/categories/');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBudgetCategoriesSums = async (budgetId) => {
|
||||||
|
let response = await apiClient.get('/budgets/' + budgetId + '/categories/_calc_sums');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
export const getBudgetInfo = async (budget_id: number) => {
|
export const getBudgetInfo = async (budget_id: number) => {
|
||||||
console.log('getBudgetInfo');
|
|
||||||
let budgetInfo = await apiClient.get('/budgets/' + budget_id);
|
let budgetInfo = await apiClient.get('/budgets/' + budget_id);
|
||||||
budgetInfo = budgetInfo.data;
|
budgetInfo = budgetInfo.data;
|
||||||
|
|
||||||
budgetInfo.plannedExpenses.forEach(e => {
|
budgetInfo.dateFrom = new Date(budgetInfo.dateFrom)
|
||||||
e.date = new Date(e.date)
|
budgetInfo.dateTo = new Date(budgetInfo.dateTo)
|
||||||
})
|
|
||||||
|
|
||||||
budgetInfo.plannedIncomes.forEach(e => {
|
|
||||||
e.date = new Date(e.date)
|
|
||||||
})
|
|
||||||
|
|
||||||
budgetInfo.transactions.forEach(e => {
|
|
||||||
e.date = new Date(e.date)
|
|
||||||
})
|
|
||||||
return budgetInfo
|
return budgetInfo
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,8 +36,13 @@ export const createTransactionRequest = async (transaction: Transaction) => {
|
|||||||
|
|
||||||
export const updateTransactionRequest = async (transaction: Transaction) => {
|
export const updateTransactionRequest = async (transaction: Transaction) => {
|
||||||
const id = transaction.id
|
const id = transaction.id
|
||||||
transaction.date = format(transaction.date, 'yyyy-MM-dd')
|
// transaction.date = format(transaction.date, 'yyyy-MM-dd')
|
||||||
return await apiClient.put(`/transactions/${id}`, transaction);
|
const response = await apiClient.put(`/transactions/${id}`, transaction);
|
||||||
|
transaction = response.data
|
||||||
|
transaction.date = new Date(transaction.date);
|
||||||
|
console.log(transaction.date);
|
||||||
|
|
||||||
|
return transaction
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteTransactionRequest = async (id: number) => {
|
export const deleteTransactionRequest = async (id: number) => {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ export const formatAmount = (amount: number) => {
|
|||||||
|
|
||||||
export const formatDate = (date) => {
|
export const formatDate = (date) => {
|
||||||
const validDate = typeof date === 'string' ? new Date(date) : date;
|
const validDate = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
|
||||||
// Проверяем, является ли validDate корректной датой
|
// Проверяем, является ли validDate корректной датой
|
||||||
if (isNaN(validDate.getTime())) {
|
if (isNaN(validDate.getTime())) {
|
||||||
return 'Invalid Date'; // Если дата неверная, возвращаем текст ошибки
|
return 'Invalid Date'; // Если дата неверная, возвращаем текст ошибки
|
||||||
|
|||||||
Reference in New Issue
Block a user