chet novoe
This commit is contained in:
@@ -18,8 +18,8 @@
|
||||
tg.expand(); // Разворачиваем веб-приложение на весь экран
|
||||
|
||||
// Получаем информацию о пользователе и выводим её
|
||||
const user = tg.initDataUnsafe.user;
|
||||
console.log(user);
|
||||
// const user = tg.initDataUnsafe.user;
|
||||
|
||||
}
|
||||
</script>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -3,16 +3,6 @@
|
||||
"short_name": "Luminic Space",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
|
||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"axios": "^1.7.7",
|
||||
"chart.js": "^4.4.4",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"platform": "^1.3.6",
|
||||
@@ -2153,6 +2154,14 @@
|
||||
"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": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -11237,6 +11246,12 @@
|
||||
"@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": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"axios": "^1.7.7",
|
||||
"chart.js": "^4.4.4",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"platform": "^1.3.6",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!-- Контентная часть заполняет оставшееся пространство -->
|
||||
<div class="flex-grow ">
|
||||
|
||||
<router-view class="w-full h-full mt-4 lg:mt-0"/>
|
||||
<router-view />
|
||||
</div>
|
||||
<OverlayView class="w-full sticky invisible lg:visible top-0 z-10"/>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
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 {*/
|
||||
/* !*max-width: 1280px;*!*/
|
||||
/* !*margin: 0 auto;*!*/
|
||||
|
||||
@@ -58,9 +58,9 @@ const items = ref([
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
console.log(route.params['mode']);
|
||||
if (route.params['mode']) {
|
||||
console.log(route.params['mode']);
|
||||
|
||||
if (route.path == '/transactions/create') {
|
||||
|
||||
|
||||
openDrawer('INSTANT')
|
||||
|
||||
|
||||
@@ -151,15 +151,15 @@ const onAddClick = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
console.log(route.params['mode']);
|
||||
if (route.params['mode']) {
|
||||
console.log(route.params['mode']);
|
||||
|
||||
openDrawer('INSTANT')
|
||||
|
||||
}
|
||||
}, 10)
|
||||
// setTimeout(() => {
|
||||
// console.log(route.params['mode']);
|
||||
// if (route.params['mode']) {
|
||||
// console.log(route.params['mode']);
|
||||
//
|
||||
// openDrawer('INSTANT')
|
||||
//
|
||||
// }
|
||||
// }, 10)
|
||||
})
|
||||
</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 () => {
|
||||
loading.value = true;
|
||||
budgetInfos.value = await getBudgetInfos()
|
||||
console.log(budgetInfos.value)
|
||||
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -16,15 +16,20 @@ const props = defineProps(
|
||||
transaction: {
|
||||
type: Object as PropType<Transaction>,
|
||||
required: true,
|
||||
},
|
||||
isList: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
)
|
||||
const emits = defineEmits(['open-drawer'])
|
||||
const emits = defineEmits(['open-drawer', 'transaction-checked', 'transaction-updated'])
|
||||
|
||||
|
||||
const setIsDoneTrue = async () => {
|
||||
setTimeout(async () => {
|
||||
await updateTransactionRequest(props.transaction)
|
||||
emits('transaction-checked')
|
||||
}, 10);
|
||||
// showedTransaction.value.isDone = !showedTransaction.value.isDone;
|
||||
|
||||
@@ -40,6 +45,11 @@ const toggleDrawer = () => {
|
||||
emits('open-drawer', props.transaction)
|
||||
}
|
||||
|
||||
const transactionUpdate = () => {
|
||||
console.log('my tut 1')
|
||||
emits('transaction-updated')
|
||||
}
|
||||
|
||||
const isPlanned = computed(() => {
|
||||
return props.transaction?.transactionType.code === "PLANNED"
|
||||
})
|
||||
@@ -86,25 +96,25 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="transaction.category.type.code == 'INCOME' ? 'from-green-100 to-green-50' : ' from-red-100 to-red-50' &&
|
||||
transaction.transactionType.code == 'INSTANT' ? ' bg-gradient-to-r shadow-lg border-2 gap-5 p-2 rounded-xl ' : 'border-b pb-2'
|
||||
<div :class="
|
||||
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 ">
|
||||
<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">
|
||||
{{ 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"
|
||||
@click="setIsDoneTrue"/>
|
||||
</div>
|
||||
<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">
|
||||
<p :class="transaction.isDone && isPlanned ? 'line-through' : ''" class="font-bold">{{
|
||||
<p :class="transaction.isDone && isPlanned && !props.isList ? 'line-through' : ''" class="font-bold">{{
|
||||
transaction.comment
|
||||
}}</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
|
||||
}} |
|
||||
{{ formatDate(transaction.date) }}</p>
|
||||
@@ -122,7 +132,7 @@ onMounted(async () => {
|
||||
<TransactionEditDrawer v-if="drawerOpened" :visible="drawerOpened" :expenseCategories="expenseCategories"
|
||||
:incomeCategories="incomeCategories" :transaction="transaction"
|
||||
:category-types="categoryTypes"
|
||||
|
||||
@transaction-updated="transactionUpdate"
|
||||
@close-drawer="closeDrawer()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,92 +16,139 @@
|
||||
</div>
|
||||
<div :class="!updateLoading ? '' : 'h-fit bg-white opacity-50 z-0 '" class=" flex flex-col gap-3">
|
||||
<div class="flex flex-col ">
|
||||
<h2 class="text-4xl font-bold">Budget for {{ budgetInfo.budget.name }} </h2>
|
||||
<div class="flex flex-row gap-2 text-xl">{{ formatDate(budgetInfo.budget.dateFrom) }} -
|
||||
{{ formatDate(budgetInfo.budget.dateTo) }}
|
||||
</div>
|
||||
<!-- {{ budget }}-->
|
||||
<h2 class="text-4xl font-bold">Budget for {{ budget.name }} </h2>
|
||||
<!-- <div class="flex flex-row gap-2 text-xl">{{ formatDate(budget.dateFrom) }} - -->
|
||||
<!-- {{ formatDate(budget.dateTo) }}-->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- Аналитика и плановые доходы/расходы -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start ">
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Блок Аналитики (25%) -->
|
||||
<div class="card p-4 shadow-lg rounded-lg col-span-2 h-fit">
|
||||
<h3 class="text-xl mb-4 font-bold">Analytics</h3>
|
||||
<div class="flex ">
|
||||
<div class="w-128">
|
||||
<Chart type="bar" :data="incomeExpenseChartData" class=""/>
|
||||
</div>
|
||||
<div class="h-fit">
|
||||
<!-- <Chart type="pie" :data="incomeExpenseChartData" class="h-64"/>-->
|
||||
</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="bg-white p-4 shadow-lg rounded-lg flex flex-col gap-4 items-start ">
|
||||
<h3 class="text-xl font-bold">Аналитика</h3>
|
||||
|
||||
<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">Total Incomes:</h4>
|
||||
<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(budgetInfo.totalIncomes) }}
|
||||
+{{ formatAmount(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>
|
||||
<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(budgetInfo.totalExpenses) }}
|
||||
-{{ formatAmount(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]) }} ₽
|
||||
</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 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 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 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 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 gap-2 items-center">
|
||||
<p class="font-bold ">Expenses 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[1][1]) }} ₽
|
||||
<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 class="flex flex-col gap-2 items-center ">
|
||||
<p class="font-bold">Left for unplanned:</p>
|
||||
|
||||
</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">
|
||||
{{ formatAmount(leftForUnplanned) }} ₽
|
||||
|
||||
{{ loansRatio.toFixed(0) }} %
|
||||
</div>
|
||||
<!-- <p>Total Incomes</p>-->
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 items-center ">
|
||||
<p class="font-bold">Current spending:</p>
|
||||
<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">
|
||||
{{ formatAmount(leftForUnplanned) }} ₽
|
||||
{{ 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 class=" h-full overflow-y-auto gap-4 flex-col row-span-6 hidden lg:flex">
|
||||
<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>
|
||||
<ProgressBar :value="value" class="mt-2"></ProgressBar>
|
||||
<div class="flex flex-col w-full items-end">
|
||||
|
||||
<div class="flex flex-row items-end ">
|
||||
{{ formatAmount(leftForUnplanned) }} ₽
|
||||
</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">
|
||||
<!-- Планируемые доходы -->
|
||||
@@ -112,7 +159,7 @@
|
||||
</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) }}
|
||||
{{ formatAmount(totalIncomes) }}
|
||||
₽
|
||||
</div>
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
@@ -121,8 +168,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
|
||||
<BudgetTransactionView v-for="transaction in budgetInfo.plannedIncomes" :transaction="transaction"/>
|
||||
<!-- {{ plannedIncomes }}-->
|
||||
<BudgetTransactionView v-for="transaction in plannedIncomes" :transaction="transaction"
|
||||
:is-list="false" @transaction-checked="fetchBudgetTransactions"
|
||||
@transaction-updated="updateTransactions"/>
|
||||
|
||||
</ul>
|
||||
|
||||
@@ -134,7 +183,7 @@
|
||||
<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) }}
|
||||
{{ formatAmount(totalPlannedExpenses) }}
|
||||
₽
|
||||
</div>
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
@@ -144,38 +193,41 @@
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
|
||||
<BudgetTransactionView v-for="transaction in budgetInfo.plannedExpenses" :transaction="transaction"/>
|
||||
<BudgetTransactionView v-for="transaction in plannedExpenses" :transaction="transaction"
|
||||
:is-list="false" @transaction-checked="fetchBudgetTransactions"
|
||||
@transaction-updated="updateTransactions"/>
|
||||
</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">
|
||||
<div class="row-span-1 h-fit">
|
||||
<h3 class="text-2xl font-bold mb-4">Категории</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
|
||||
<UnplannedCategoryView v-for="category in budgetInfo.unplannedCategories" :key="category.id"
|
||||
:category="category" :budget-id="budgetInfo.budget.id"
|
||||
<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 class=" h-full overflow-y-auto gap-4 flex-col row-span-2 col-span-2">
|
||||
<div class=" h-full overflow-y-auto gap-4 flex-col row-span-6 lg:hidden ">
|
||||
<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"
|
||||
<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">
|
||||
<BudgetTransactionView v-for="transaction in budgetInfo.transactions" :key="transaction.id"
|
||||
<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"
|
||||
@open-drawer="openDrawer"/>
|
||||
:is-list="true" class=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,40 +240,61 @@
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, ref} from 'vue';
|
||||
import { computed, onMounted, ref} from 'vue';
|
||||
import Chart from 'primevue/chart';
|
||||
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
||||
import {CategoryType} from "@/models/Category";
|
||||
import {getBudgetInfo, updateBudgetCategoryRequest} from "@/services/budgetsService";
|
||||
import {BudgetInfo} from "@/models/Budget";
|
||||
import {
|
||||
getBudgetCategories, getBudgetCategoriesSums,
|
||||
getBudgetInfo,
|
||||
getBudgetTransactions,
|
||||
updateBudgetCategoryRequest
|
||||
} from "@/services/budgetsService";
|
||||
import {Budget, BudgetCategory, BudgetInfo} from "@/models/Budget";
|
||||
import {useRoute} from "vue-router";
|
||||
import {formatAmount, formatDate} from "@/utils/utils";
|
||||
import {formatAmount} from "@/utils/utils";
|
||||
import ProgressBar from "primevue/progressbar";
|
||||
import ProgressSpinner from "primevue/progressspinner";
|
||||
import UnplannedCategoryView from "@/components/budgets/UnplannedCategoryView.vue";
|
||||
import {TransactionType} from "@/models/Transaction";
|
||||
import UnplannedCategoryView from "@/components/budgets/BudgetCategoryView.vue";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
import Toast from "primevue/toast";
|
||||
import Button from "primevue/button";
|
||||
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 updateLoading = ref(false);
|
||||
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 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(() => {
|
||||
let totalIncomeLeftToGet = 0;
|
||||
budgetInfo.value?.plannedIncomes.forEach(i => {
|
||||
plannedIncomes.value.forEach(i => {
|
||||
if (!i.isDone) {
|
||||
totalIncomeLeftToGet += i.amount
|
||||
}
|
||||
@@ -229,93 +302,353 @@ const totalIncomeLeftToGet = computed(() => {
|
||||
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(() => {
|
||||
let totalExpenseLeftToSpend = 0;
|
||||
budgetInfo.value?.plannedExpenses.forEach(i => {
|
||||
plannedExpenses.value.forEach(i => {
|
||||
if (!i.isDone) {
|
||||
totalExpenseLeftToSpend += i.amount
|
||||
}
|
||||
})
|
||||
return totalExpenseLeftToSpend
|
||||
})
|
||||
const fetchPlannedExpenses = async () => {
|
||||
plannedExpenses.value = await getBudgetTransactions(route.params.id, 'PLANNED', 'EXPENSE')
|
||||
updateLoading.value = false
|
||||
}
|
||||
const transactions = ref<Transaction[]>([])
|
||||
const fetchBudgetTransactions = async () => {
|
||||
|
||||
|
||||
transactions.value = await getBudgetTransactions(route.params.id)
|
||||
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 () => {
|
||||
updateLoading.value = true
|
||||
console.log('Trying to get budget Info')
|
||||
budgetInfo.value = await getBudgetInfo(route.params.id);
|
||||
|
||||
|
||||
console.log(budgetInfo.value)
|
||||
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]
|
||||
|
||||
budget.value = await getBudgetInfo(route.params.id);
|
||||
updateLoading.value = false
|
||||
}
|
||||
|
||||
const updateBudgetCategory = async (category) => {
|
||||
console.log(category)
|
||||
loading.value = true
|
||||
await updateBudgetCategoryRequest(budgetInfo.value.budget.id, category)
|
||||
budgetInfo.value = await getBudgetInfo(route.params.id)
|
||||
console.log(budgetInfo.value)
|
||||
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]
|
||||
loading.value = false
|
||||
|
||||
// loading.value = true
|
||||
await updateBudgetCategoryRequest(budget.value.id, category)
|
||||
|
||||
// categories.value = await getBudgetCategories(route.params.id)
|
||||
|
||||
// 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]
|
||||
// // loading.value = false
|
||||
|
||||
}
|
||||
|
||||
// Пример данных
|
||||
const incomeExpenseChartData = ref();
|
||||
const twentyFour = computed(() => {
|
||||
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 () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchBudgetInfo()
|
||||
fetchBudgetInfo(),
|
||||
// budget.value = await getBudgetInfo(route.params.id),
|
||||
fetchPlannedIncomes(),
|
||||
fetchPlannedExpenses(),
|
||||
fetchBudgetCategories(),
|
||||
fetchBudgetTransactions(),
|
||||
fetchBudgetTransactionCategoriesSums()
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error during fetching data:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
}, 100)
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -332,13 +665,16 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.max-h-tlist {
|
||||
max-height: 45dvh; /* Ограничение высоты списка */
|
||||
max-height: 1170px; /* Ограничение высоты списка */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.box-shadow-inner {
|
||||
box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.3);
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
</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 categoryTypeChanged = () => {
|
||||
console.log(selectedCategoryType.value)
|
||||
|
||||
editedTransaction.value.category = selectedCategoryType.value.code == "EXPENSE" ? expenseCategories.value[0] : incomeCategories.value[0];
|
||||
|
||||
}
|
||||
@@ -82,11 +82,30 @@ const fetchCategoriesAndTypes = async () => {
|
||||
|
||||
categoryTypes.value = categoryTypesResponse.data;
|
||||
transactionTypes.value = transactionTypesResponse.data;
|
||||
console.log(entireCategories.value)
|
||||
} catch (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 = () => {
|
||||
if (!props.transaction) {
|
||||
@@ -102,9 +121,38 @@ 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 () => {
|
||||
if (checkForm()) {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (editedTransaction.value.transactionType.code === 'INSTANT') {
|
||||
@@ -113,28 +161,44 @@ const createTransaction = async () => {
|
||||
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;
|
||||
console.log(editedTransaction.value)
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
result.value = false
|
||||
resultText.value = ''
|
||||
}, 1000)
|
||||
};
|
||||
|
||||
// Обновление транзакции
|
||||
const updateTransaction = async () => {
|
||||
if (checkForm()) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await updateTransactionRequest(editedTransaction.value);
|
||||
editedTransaction.value = response.data;
|
||||
toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
response.data;
|
||||
// toast.add({severity: 'success', summary: 'Transaction updated!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
emit('update-transaction', editedTransaction.value);
|
||||
emit('transaction-updated');
|
||||
computeResult(true)
|
||||
} 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});
|
||||
emit('delete-transaction', editedTransaction.value);
|
||||
closeDrawer()
|
||||
computeResult(true)
|
||||
} catch (error) {
|
||||
computeResult(false, error)
|
||||
toast.add({severity: 'warn', summary: 'Error!', detail: 'Транзакция обновлена!', life: 3000});
|
||||
|
||||
console.error('Error deleting transaction:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -162,15 +230,15 @@ const resetForm = () => {
|
||||
};
|
||||
|
||||
const dateErrorMessage = computed(() => {
|
||||
console.log('tut')
|
||||
|
||||
if (editedTransaction.value.transactionType.code != 'PLANNED' && editedTransaction.value.date > new Date()) {
|
||||
console.log('tut2')
|
||||
|
||||
return 'При мгновенных тратах дата должна быть меньше текущей!'
|
||||
} else if (editedTransaction.value.transactionType.code == 'PLANNED' && editedTransaction.value.date < new Date()) {
|
||||
console.log('tu3')
|
||||
|
||||
return 'При плановых тратах дата должна быть больше текущей!'
|
||||
} else {
|
||||
console.log('tu4')
|
||||
|
||||
return ''
|
||||
}
|
||||
})
|
||||
@@ -183,12 +251,16 @@ const userAgent = ref(null);
|
||||
// Мониторинг при монтировании
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
|
||||
await fetchCategoriesAndTypes();
|
||||
|
||||
prepareData();
|
||||
|
||||
loading.value = false;
|
||||
const deviceInfo = platform;
|
||||
isMobile.value = deviceInfo.os.family === 'iOS' || deviceInfo.os.family === 'Android';
|
||||
console.log(deviceInfo);
|
||||
console.log()
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -196,14 +268,32 @@ onMounted(async () => {
|
||||
|
||||
<div class="card flex justify-center h-dvh">
|
||||
|
||||
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Edit Transaction' : 'Create Transaction'" :showCloseIcon="false"
|
||||
position="right" @hide="closeDrawer"
|
||||
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"/>
|
||||
<div v-else class=" grid gap-4 w-full ">
|
||||
|
||||
<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-row gap-2">
|
||||
<!-- {{editedTransaction.value.transactionType}}-->
|
||||
@@ -217,7 +307,7 @@ onMounted(async () => {
|
||||
aria-labelledby="basic"
|
||||
@change="categoryTypeChanged" class="justify-center"/>
|
||||
</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">
|
||||
<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">
|
||||
@@ -241,7 +331,7 @@ onMounted(async () => {
|
||||
|
||||
<!-- Анимированное открытие списка категорий -->
|
||||
<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 }">
|
||||
<div class="grid grid-cols-2 mt-2">
|
||||
<button
|
||||
@@ -319,8 +409,7 @@ onMounted(async () => {
|
||||
|
||||
<!-- Buttons -->
|
||||
{{ keyboardOpen }}
|
||||
<div class="fixed col-12 flex justify-content-end gap-4"
|
||||
:style="keyboardOpen && isMobile ? 'bottom : 350px;' :' bottom: 5rem;'">
|
||||
<div class="fixed col-12 flex justify-content-end gap-4 bottom-8">
|
||||
|
||||
<Button label="Save" icon="pi pi-check" class="p-button-success"
|
||||
@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 () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
console.log('loaded')
|
||||
|
||||
const result = await getRecurrentPayments();
|
||||
recurrentPayments.value = result.data;
|
||||
} catch (error) {
|
||||
|
||||
@@ -21,7 +21,6 @@ const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await getTransactions('INSTANT');
|
||||
transactions.value = response.data
|
||||
console.log(transactions.value)
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
}
|
||||
@@ -72,7 +71,7 @@ onMounted(async () => {
|
||||
</IconField>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -8,11 +8,15 @@ import Aura from '@primevue/themes/aura';
|
||||
import router from './router';
|
||||
import Ripple from "primevue/ripple";
|
||||
import ToastService from 'primevue/toastservice'
|
||||
import Tooltip from 'primevue/tooltip';
|
||||
|
||||
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(ToastService);
|
||||
app.directive('ripple', Ripple);
|
||||
app.directive('tooltip', Tooltip);
|
||||
app.use(PrimeVue, {
|
||||
theme: {
|
||||
preset: Aura
|
||||
|
||||
@@ -3,8 +3,8 @@ import axios from 'axios';
|
||||
|
||||
// Создание экземпляра axios с базовым URL
|
||||
const apiClient = axios.create({
|
||||
// baseURL: 'https://luminic.space/api/v1',
|
||||
baseURL: 'http://localhost:8000/api/v1',
|
||||
baseURL: 'https://luminic.space/api/v1',
|
||||
// baseURL: 'http://localhost:8000/api/v1',
|
||||
headers: {
|
||||
'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 CreateCategoryModal from "@/components/settings/categories/CreateCategoryModal.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";
|
||||
|
||||
const routes = [
|
||||
|
||||
{path: '/', name: 'Budgets main', component: BudgetList, meta: { requiresAuth: true }},
|
||||
{ path: '/login', component: LoginView },
|
||||
{path: '/budgets', name: 'Budgets', component: BudgetList, 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: '/login', component: LoginView},
|
||||
{path: '/', name: 'Budgets main', component: BudgetList, meta: {requiresAuth: true}},
|
||||
{path: '/analytics', name: 'Analytics', 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: '/transactions/:mode*', name: 'Transaction List', component: TransactionList, meta: {requiresAuth: true}},
|
||||
// {path: '/transactions/create', name: 'Transaction List', component: TransactionList},
|
||||
{path: '/settings/', name: 'Settings', component: SettingsView, 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/categories/one', name: "Categories Creation", component: CategoryListItem, meta: { requiresAuth: true }}// Добавляем новый маршрут
|
||||
{path: '/settings/', name: 'Settings', component: SettingsView, 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/categories/one',
|
||||
name: "Categories Creation",
|
||||
component: CategoryListItem,
|
||||
meta: {requiresAuth: true}
|
||||
}// Добавляем новый маршрут
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
@@ -32,11 +47,8 @@ const router = createRouter({
|
||||
router.beforeEach((to, from, next) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (to.meta.requiresAuth && !token) {
|
||||
const router= useRoute()
|
||||
console.log(to)
|
||||
console.log(router.path)
|
||||
console.log(router.params)
|
||||
next('/login?back='+to.fullPath);
|
||||
// const router = useRoute()
|
||||
next('/login?back=' + to.fullPath);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Budget, BudgetCategory} from "@/models/Budget";
|
||||
// Импортируете настроенный экземпляр axios
|
||||
|
||||
export const getBudgetInfos = async () => {
|
||||
console.log('getBudgetInfos');
|
||||
|
||||
let response = await apiClient.get('/budgets/');
|
||||
let budgetInfos = response.data;
|
||||
budgetInfos.forEach((budgetInfo: Budget) => {
|
||||
@@ -24,22 +24,43 @@ export const getBudgetInfos = async () => {
|
||||
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) => {
|
||||
console.log('getBudgetInfo');
|
||||
|
||||
let budgetInfo = await apiClient.get('/budgets/' + budget_id);
|
||||
budgetInfo = budgetInfo.data;
|
||||
|
||||
budgetInfo.plannedExpenses.forEach(e => {
|
||||
e.date = new Date(e.date)
|
||||
})
|
||||
|
||||
budgetInfo.plannedIncomes.forEach(e => {
|
||||
e.date = new Date(e.date)
|
||||
})
|
||||
|
||||
budgetInfo.transactions.forEach(e => {
|
||||
e.date = new Date(e.date)
|
||||
})
|
||||
budgetInfo.dateFrom = new Date(budgetInfo.dateFrom)
|
||||
budgetInfo.dateTo = new Date(budgetInfo.dateTo)
|
||||
return budgetInfo
|
||||
|
||||
};
|
||||
|
||||
@@ -36,8 +36,13 @@ export const createTransactionRequest = async (transaction: Transaction) => {
|
||||
|
||||
export const updateTransactionRequest = async (transaction: Transaction) => {
|
||||
const id = transaction.id
|
||||
transaction.date = format(transaction.date, 'yyyy-MM-dd')
|
||||
return await apiClient.put(`/transactions/${id}`, transaction);
|
||||
// transaction.date = format(transaction.date, 'yyyy-MM-dd')
|
||||
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) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ export const formatAmount = (amount: number) => {
|
||||
|
||||
export const formatDate = (date) => {
|
||||
const validDate = typeof date === 'string' ? new Date(date) : date;
|
||||
|
||||
// Проверяем, является ли validDate корректной датой
|
||||
if (isNaN(validDate.getTime())) {
|
||||
return 'Invalid Date'; // Если дата неверная, возвращаем текст ошибки
|
||||
|
||||
Reference in New Issue
Block a user