fixessome
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"pinia": "^2.2.6",
|
||||
"platform": "^1.3.6",
|
||||
"primeicons": "^7.0.0",
|
||||
"primelocale": "^1.0.4",
|
||||
"primevue": "^4.1.0",
|
||||
"tailwindcss-primeui": "^0.3.4",
|
||||
"vue": "^3.5.11",
|
||||
@@ -7155,6 +7156,15 @@
|
||||
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz",
|
||||
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
|
||||
},
|
||||
"node_modules/primelocale": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/primelocale/-/primelocale-1.0.4.tgz",
|
||||
"integrity": "sha512-98hx5Nwq7CusZnb2ToOLBsamroihcvCPwxK8CUOXMCwoL7nn5jwL7QdEQWzFuTkE3UAAgBWYQwi1eITtzpTulw==",
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/primevue": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.1.0.tgz",
|
||||
@@ -14656,6 +14666,11 @@
|
||||
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz",
|
||||
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
|
||||
},
|
||||
"primelocale": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/primelocale/-/primelocale-1.0.4.tgz",
|
||||
"integrity": "sha512-98hx5Nwq7CusZnb2ToOLBsamroihcvCPwxK8CUOXMCwoL7nn5jwL7QdEQWzFuTkE3UAAgBWYQwi1eITtzpTulw=="
|
||||
},
|
||||
"primevue": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.1.0.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"pinia": "^2.2.6",
|
||||
"platform": "^1.3.6",
|
||||
"primeicons": "^7.0.0",
|
||||
"primelocale": "^1.0.4",
|
||||
"primevue": "^4.1.0",
|
||||
"tailwindcss-primeui": "^0.3.4",
|
||||
"vue": "^3.5.11",
|
||||
|
||||
14
src/App.vue
14
src/App.vue
@@ -9,7 +9,7 @@
|
||||
<div class="flex flex-col flex-grow gap-4 ">
|
||||
<!-- {{ tg_id }}-->
|
||||
<Button label="Sub" :class="checkNotif ? 'flex' : '!hidden'" @click="checkSubscribe"/>
|
||||
<router-view class="pt-2"/>
|
||||
<router-view />
|
||||
<div class="bg-gray-100 h-12 block lg:hidden"></div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ import {subscribeUserToPush} from "@/services/pushManager";
|
||||
import apiClient from '@/services/axiosSetup';
|
||||
import {useUserStore} from "@/stores/userStore";
|
||||
import {useDrawerStore} from '@/stores/drawerStore'
|
||||
import TransactionForm from "@/components/TransactionForm.vue";
|
||||
import TransactionForm from "@/components/transactions/TransactionForm.vue";
|
||||
|
||||
|
||||
const drawerStore = useDrawerStore();
|
||||
@@ -79,17 +79,17 @@ const sendSubscribe = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('vyzyvaem app')
|
||||
|
||||
const userStore = useUserStore();
|
||||
const user = computed(() => userStore.user);
|
||||
console.log('vyzvali app')
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
console.log("Загружаем данные при монтировании...");
|
||||
|
||||
if (!userStore.user) {
|
||||
console.log('vyzyvaem app2')
|
||||
|
||||
await userStore.fetchUserProfile();
|
||||
console.log('vyzvali app2')
|
||||
|
||||
}
|
||||
await checkSubscribe();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Изменить транзакцию ' : 'Создать транзакцию'" :showCloseIcon="false"
|
||||
position="right" @hide="closeDrawer"
|
||||
<Drawer :visible="visible" :header="isEditing ? 'Изменить транзакцию ' : 'Создать транзакцию'" :showCloseIcon="true"
|
||||
position="right" @hide="closeDrawer" @update:visible="closeDrawer"
|
||||
class="!w-128 hidden lg:block ">
|
||||
<slot />
|
||||
</Drawer>
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from 'vue-class-component';
|
||||
|
||||
@Options({
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
})
|
||||
export default class HelloWorld extends Vue {
|
||||
msg!: string
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
@@ -29,7 +29,7 @@
|
||||
<template #end>
|
||||
<div class="flex items-center gap-2">
|
||||
{{ user.firstName }}
|
||||
<Button @click="drawerStore.visible = true" label="Create"/>
|
||||
<Button @click="drawerStore.visible = true" label="Создать"/>
|
||||
|
||||
<!-- <InputText placeholder="Search" type="text" class="w-32 sm:w-auto" />-->
|
||||
<!-- <Avatar image="https://primefaces.org/cdn/primevue/images/avatar/amyelsner.png" shape="circle" />-->
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<script setup>
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
225
src/components/analytics/AnalyticsView.vue
Normal file
225
src/components/analytics/AnalyticsView.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {getTransactionCategoriesSums} from "@/services/transactionService";
|
||||
import Chart from "primevue/chart";
|
||||
import MultiSelect from "primevue/multiselect";
|
||||
import {format} from "date-fns";
|
||||
import {generateRandomColors} from "@/utils/utils";
|
||||
import {Category} from "@/models/Category";
|
||||
import {getCategories} from "@/services/categoryService";
|
||||
import {Chart as ChartJS} from "chart.js/auto";
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
|
||||
ChartJS.register(ChartDataLabels);
|
||||
|
||||
|
||||
const loading = ref(false);
|
||||
const categoriesSums = ref([])
|
||||
|
||||
const fetchCategoriesSums = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
categoriesSums.value = await getTransactionCategoriesSums()
|
||||
// console.log(categoriesSums.value)
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories sums:', error);
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const categories = ref<Category[]>([])
|
||||
const selectedCategories = ref([])
|
||||
|
||||
const fetchCategories = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getCategories('EXPENSE');
|
||||
categories.value = response.data
|
||||
console.log(categories.value.filter(i => i.id==30))
|
||||
selectedCategories.value.push(categories.value.filter(i => i.id==30)[0])
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const chartData = computed(() => {
|
||||
return {
|
||||
labels: chartLabels.value[0],
|
||||
datasets: chartLabels.value[1]
|
||||
};
|
||||
})
|
||||
const chartOptions = computed(() => {
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 0.6,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: 'rgba(0, 0, 0, 1)',
|
||||
font: {
|
||||
weight: 'bold',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 0, 0, 0.5)',
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
const chartLabels = computed(() => {
|
||||
let dates = new Array<Date>()
|
||||
categoriesSums.value.filter(i => i[0].id == 30).forEach((item) => {
|
||||
dates.push(format(new Date(item[1]), 'MM.yy'))
|
||||
})
|
||||
|
||||
|
||||
let datasets = []
|
||||
|
||||
categoriesSums.value.forEach((item) => {
|
||||
// Проверка, есть ли категория в массиве выбранных категорий `selectedCategories`
|
||||
const isSelected = selectedCategories.value.some((category) => category.id == item[0].id);
|
||||
|
||||
if (!isSelected) {
|
||||
return; // Пропускаем категории, которых нет в `selectedCategories`
|
||||
}
|
||||
|
||||
// Проверка, есть ли уже категория с таким названием в `datasets`
|
||||
const existingDataset = datasets.find((v) => v.label === item[0].name);
|
||||
let color = generateRandomColors();
|
||||
|
||||
if (!existingDataset) {
|
||||
// Создаем новый объект `dataset` для новой категории
|
||||
let dataset = {
|
||||
label: item[0].name,
|
||||
data: categoriesSums.value
|
||||
.filter((i) => i[0].id === item[0].id)
|
||||
.map((value) => value[2]), // Собираем массив значений для категории
|
||||
fill: true,
|
||||
borderColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`,
|
||||
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.2)`,
|
||||
tension: 0.4,
|
||||
};
|
||||
|
||||
// Добавляем созданный объект `dataset` в массив `datasets`
|
||||
datasets.push(dataset);
|
||||
}
|
||||
});
|
||||
|
||||
return [dates, datasets];
|
||||
|
||||
})
|
||||
|
||||
// const categories = computed(() => {
|
||||
// let categoriesIds = new Array<number>()
|
||||
// categoriesSums.value.forEach((i) => {
|
||||
// categoriesIds.push(i[0].id)
|
||||
// })
|
||||
// return categoriesIds
|
||||
// })
|
||||
|
||||
|
||||
const setChartData = () => {
|
||||
console.log(chartLabels.value[1])
|
||||
return {
|
||||
labels: chartLabels.value[0],
|
||||
datasets: chartLabels.value[1]
|
||||
};
|
||||
}
|
||||
|
||||
const setChartOptions = () => {
|
||||
// const documentStyle = getComputedStyle(document.documentElement);
|
||||
// const textColor = documentStyle.getPropertyValue('--p-text-color');
|
||||
// const textColorSecondary = documentStyle.getPropertyValue('--p-text-muted-color');
|
||||
// const surfaceBorder = documentStyle.getPropertyValue('--p-content-border-color');
|
||||
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 0.6,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: 'rgba(0, 0, 0, 1)',
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
datalabels: {
|
||||
color: 'rgba(0, 0, 0, 1)', // Цвет текста
|
||||
anchor: 'end', // Привязка метки
|
||||
align: 'top', // Выравнивание над точкой
|
||||
offset: 8, // Отступ от точки
|
||||
labels: {
|
||||
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 0, 0, 0.5)',
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
await Promise.all([fetchCategoriesSums(), fetchCategories()])
|
||||
loading.value = false
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="px-4 bg-gray-100 h-full flex flex-col gap-4 ">
|
||||
<MultiSelect v-model="selectedCategories" :options="categories" optionLabel="name" filter
|
||||
placeholder="Выберите категории"
|
||||
:maxSelectedLabels="3" class="w-full md:w-80"/>
|
||||
|
||||
<Chart v-if="selectedCategories.length > 0" type="line" :data="chartData" :options="chartOptions" class="h-[30rem]"/>
|
||||
|
||||
<!-- {{categories}}-->
|
||||
<!-- {{// chartData}}-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -16,7 +16,7 @@ const props = defineProps({
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['close-modal'])
|
||||
const emits = defineEmits(['budget-created','close-modal'])
|
||||
const createRecurrentPayments = ref<Boolean>(true)
|
||||
|
||||
const name = ref('')
|
||||
@@ -29,7 +29,7 @@ const create = async () => {
|
||||
console.log(budget.value)
|
||||
try {
|
||||
await createBudget(budget.value, createRecurrentPayments.value)
|
||||
emits("close-modal");
|
||||
emits("budget-created");
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :visible="opened" modal header="Создать новый бюджет" :style="{ width: '25rem' }">
|
||||
<Dialog :visible="opened" modal header="Создать новый бюджет" :style="{ width: '25rem' }" @hide="cancel" @update:visible="cancel">
|
||||
<div class="flex flex-col gap-4 mt-1">
|
||||
<FloatLabel variant="on" class="w-full">
|
||||
<label for="name">Название</label>
|
||||
@@ -87,7 +87,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 justify-end items-center">
|
||||
<Button label="Создать" severity="success" icon="pi pi-save" @click="create"/>
|
||||
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle"/>
|
||||
<Button label="Отмена" severity="secondary" icon="pi pi-times-circle" @click="cancel"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<h2 class="text-4xl font-bold">Бюджеты</h2>
|
||||
<Button label="+ Создать" @click="creationOpened=true" size="small"/>
|
||||
<BudgetCreationView :opened="creationOpened" @close-modal="creationOpened = false; creationSuccessShow() "/>
|
||||
<BudgetCreationView :opened="creationOpened" @budget-created="creationSuccessShow()" @close-modal="creationOpened=false" />
|
||||
<StatusView :show="creationSuccessModal" :is-error="false" :message="'Бюджет создан!'"/>
|
||||
</div>
|
||||
<!-- Плитка с бюджетами -->
|
||||
@@ -22,44 +22,43 @@
|
||||
<div class="text-sm text-gray-600 mb-4">
|
||||
{{ formatDate(budget.dateFrom) }} - {{ formatDate(budget.dateTo) }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<!-- <div class="mb-4">-->
|
||||
<!-- <div class="text-sm">Total Income: <span class="font-bold">{{ formatAmount(budgettotalIncomes) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Total Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<!-- <div class="text-sm">Planned Expenses: <span class="font-bold">{{ formatAmount(budget.totalExpenses) }} ₽</span></div>-->
|
||||
<div class="text-sm flex items-center">
|
||||
Unplanned Expenses:
|
||||
<!-- <div class="text-sm flex items-center">-->
|
||||
<!-- Unplanned Expenses:-->
|
||||
<!-- <span class="ml-2 font-bold">{{ formatAmount(budget.totalIncomes - budget.totalExpenses) }} ₽</span>-->
|
||||
<!-- Прогресс бар -->
|
||||
<!-- <ProgressBar :value="budget.unplannedProgress" class="ml-4 w-full"/>-->
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<!-- Прошедшие бюджеты (забеленные) -->
|
||||
<div v-for="budget in pastBudgets" :key="budget.id" class="p-4 shadow-lg rounded-lg bg-gray-100 opacity-60">
|
||||
<div class="text-xl font-bold mb-2">{{ budget.month }}</div>
|
||||
<div class="text-sm text-gray-600 mb-4">
|
||||
{{ budget.startDate }} - {{ budget.endDate }}
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="text-sm">Total Income: <span class="font-bold">{{ budget.totalIncome }}</span></div>
|
||||
<div class="text-sm">Total Expenses: <span class="font-bold">{{ budget.totalExpenses }}</span></div>
|
||||
<div class="text-sm">Planned Expenses: <span class="font-bold">{{ budget.plannedExpenses }}</span></div>
|
||||
<div class="text-sm flex items-center">
|
||||
Unplanned Expenses:
|
||||
<span class="ml-2 font-bold">{{ budget.remainingForUnplanned }}</span>
|
||||
<!-- Прогресс бар -->
|
||||
<ProgressBar :value="budget.unplannedProgress" class="ml-4 w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div v-for="budget in pastBudgets" :key="budget.id" class="p-4 shadow-lg rounded-lg bg-gray-100 opacity-60">-->
|
||||
<!-- <div class="text-xl font-bold mb-2">{{ budget.month }}</div>-->
|
||||
<!-- <div class="text-sm text-gray-600 mb-4">-->
|
||||
<!-- {{ budget.startDate }} - {{ budget.endDate }}-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mb-4">-->
|
||||
<!-- <div class="text-sm">Total Income: <span class="font-bold">{{ budget.totalIncome }}</span></div>-->
|
||||
<!-- <div class="text-sm">Total Expenses: <span class="font-bold">{{ budget.totalExpenses }}</span></div>-->
|
||||
<!-- <div class="text-sm">Planned Expenses: <span class="font-bold">{{ budget.plannedExpenses }}</span></div>-->
|
||||
<!-- <div class="text-sm flex items-center">-->
|
||||
<!-- Unplanned Expenses:-->
|
||||
<!-- <span class="ml-2 font-bold">{{ budget.remainingForUnplanned }}</span>-->
|
||||
<!-- <!– Прогресс бар –>-->
|
||||
<!-- <ProgressBar :value="budget.unplannedProgress" class="ml-4 w-full"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from 'vue';
|
||||
import ProgressBar from 'primevue/progressbar';
|
||||
import {BudgetInfo} from "@/models/Budget";
|
||||
import {getBudgetInfos} from "@/services/budgetsService";
|
||||
import {formatDate} from "@/utils/utils";
|
||||
@@ -74,6 +73,7 @@ const budgetInfos = ref<BudgetInfo[]>([])
|
||||
const creationOpened = ref(false)
|
||||
const creationSuccessModal = ref(false)
|
||||
const creationSuccessShow = async () => {
|
||||
creationOpened.value = false
|
||||
budgetInfos.value = await getBudgetInfos()
|
||||
creationSuccessModal.value = true
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {Category, CategoryType} from "@/models/Category";
|
||||
import {getCategories, getCategoryTypes} from "@/services/categoryService";
|
||||
import {setTransactionDoneRequest} from "@/services/transactionService";
|
||||
import {formatAmount, formatDate} from "@/utils/utils";
|
||||
import TransactionForm from "@/components/TransactionForm.vue";
|
||||
import TransactionForm from "@/components/transactions/TransactionForm.vue";
|
||||
|
||||
|
||||
const props = defineProps(
|
||||
|
||||
@@ -121,6 +121,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<button class="grid grid-cols-2 justify-between gap-5 items-end w-full"
|
||||
@click="detailedShowed = !detailedShowed">
|
||||
<div class="flex flex-col items-center">
|
||||
<h4 class="text-sm lg:text-base">Факт. поступления ✅</h4>
|
||||
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
{{ formatAmount(totalInstantIncomes) }} ₽
|
||||
</div>
|
||||
<!-- <p>Total Incomes</p>-->
|
||||
</div>
|
||||
<div class="flex flex-col items-center ">
|
||||
<span class="text-sm lg:text-base">Факт. траты 📛</span>
|
||||
<div class="font-light bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
{{ formatAmount(totalInstantExpenses)}} ₽
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</button>
|
||||
<div class="grid grid-cols-2 !gap-1 mt-4" :class="detailedShowed ? 'block' : 'hidden'">
|
||||
<div v-for="categorySum in transactionCategoriesSums" class="flex flex-col items-center font-bold ">
|
||||
<p class="font-light ">{{ categorySum.category.icon }} {{ categorySum.category.name }}</p>
|
||||
@@ -139,20 +159,19 @@
|
||||
<div class="flex flex-row gap-4">
|
||||
<h3 class="text-2xl font-bold">Транзакции</h3>
|
||||
<button @click="openDrawer('INSTANT', 'EXPENSE')">
|
||||
<!-- <i class="pi pi-plus-circle text-green-500" style="font-size: 1rem;"/>-->
|
||||
<span class="font-light text-sm">+ Добавить</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class=" flex gap-2 overflow-x-auto ">
|
||||
|
||||
<button v-for="categorySum in transactionCategoriesSums"
|
||||
class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2">
|
||||
<button v-for="categorySum in transactionCategoriesSums" @click="selectCategoryType(categorySum.category.id)"
|
||||
class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2" :class="selectedCategoryId == categorySum.category.id ? '!bg-blue-100' : ''">
|
||||
<p><span class="text-sm font-bold">{{ categorySum.category.name }}</span>: {{ categorySum.sum }} ₽</p>
|
||||
|
||||
</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"
|
||||
<div class="grid grid-cols-1 gap-1 max-h-tlist overflow-y-auto pe-2">
|
||||
<BudgetTransactionView v-for="transaction in filteredTransactions" :key="transaction.id"
|
||||
:transaction="transaction"
|
||||
:is-list="true"
|
||||
@transaction-updated="updateTransactions"
|
||||
@@ -173,7 +192,7 @@
|
||||
<span class="font-light text-sm">+ Добавить</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 mb-2">
|
||||
<div class="grid grid-cols-2 gap-1 mb-2">
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
{{ formatAmount(totalIncomes) }}
|
||||
₽
|
||||
@@ -203,7 +222,7 @@
|
||||
<span class="font-light text-sm">+ Добавить</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 mb-2">
|
||||
<div class="grid grid-cols-2 gap-1 mb-2">
|
||||
<div class="font-bold bg-gray-100 p-1 rounded-lg box-shadow-inner w-full text-center">
|
||||
{{ formatAmount(totalPlannedExpenses) }}
|
||||
₽
|
||||
@@ -243,14 +262,14 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class=" flex gap-2">
|
||||
<button v-for="categorySum in transactionCategoriesSums"
|
||||
class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2">
|
||||
<button v-for="categorySum in transactionCategoriesSums" @click="selectCategoryType(categorySum.category.id)"
|
||||
class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2" :class="selectedCategoryId == categorySum.category.id ? '!bg-blue-100' : ''">
|
||||
<p><span class="text-sm font-bold">{{ categorySum.category.name }}</span>: {{ categorySum.sum }} ₽</p>
|
||||
|
||||
</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"
|
||||
<div class="grid grid-cols-1 gap-1 max-h-tlist overflow-y-auto pe-2">
|
||||
<BudgetTransactionView v-for="transaction in filteredTransactions" :key="transaction.id"
|
||||
:transaction="transaction"
|
||||
:is-list="true"
|
||||
@transaction-updated="updateTransactions"
|
||||
@@ -306,7 +325,7 @@ 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";
|
||||
import TransactionForm from "@/components/TransactionForm.vue";
|
||||
import TransactionForm from "@/components/transactions/TransactionForm.vue";
|
||||
|
||||
// Зарегистрируем плагин
|
||||
ChartJS.register(ChartDataLabels);
|
||||
@@ -337,6 +356,15 @@ const totalIncomes = computed(() => {
|
||||
})
|
||||
return totalIncome
|
||||
})
|
||||
|
||||
const totalInstantIncomes = computed(() => {
|
||||
let totalIncome = 0;
|
||||
transactions.value.filter(t => t.transactionType.code=='INSTANT' && t.category.type.code =='INCOME' ).forEach((i) => {
|
||||
totalIncome += i.amount
|
||||
})
|
||||
return totalIncome
|
||||
})
|
||||
|
||||
const totalIncomeLeftToGet = computed(() => {
|
||||
let totalIncomeLeftToGet = 0;
|
||||
plannedIncomes.value.forEach(i => {
|
||||
@@ -357,13 +385,13 @@ const totalLoans = computed(() => {
|
||||
|
||||
const loansRatio = computed(() => {
|
||||
|
||||
return totalLoans.value / totalExpenses.value * 100
|
||||
return totalExpenses.value == 0? 0 : totalLoans.value / totalExpenses.value * 100
|
||||
})
|
||||
|
||||
|
||||
const savingRatio = computed(() => {
|
||||
|
||||
return totalSaving.value / totalExpenses.value * 100
|
||||
return totalExpenses.value == 0? 0 :totalSaving.value / totalExpenses.value * 100
|
||||
})
|
||||
|
||||
const totalSaving = computed(() => {
|
||||
@@ -396,7 +424,7 @@ const closeDrawer = async () => {
|
||||
}
|
||||
|
||||
const dailyRatio = computed(() => {
|
||||
const value = (totalExpenses.value - totalLoans.value - totalSaving.value) / totalExpenses.value
|
||||
const value = totalExpenses.value == 0? 0 : (totalExpenses.value - totalLoans.value - totalSaving.value) / totalExpenses.value
|
||||
|
||||
return value * 100
|
||||
})
|
||||
@@ -428,6 +456,14 @@ const totalPlannedExpenses = computed(() => {
|
||||
})
|
||||
return expenses
|
||||
})
|
||||
|
||||
const totalInstantExpenses = computed(() => {
|
||||
let totalExpenses = 0;
|
||||
transactions.value.filter(t => t.transactionType.code=='INSTANT' && t.category.type.code =='EXPENSE').forEach((i) => {
|
||||
totalExpenses += i.amount
|
||||
})
|
||||
return totalExpenses
|
||||
})
|
||||
const totalExpenseLeftToSpend = computed(() => {
|
||||
let totalExpenseLeftToSpend = 0;
|
||||
plannedExpenses.value.forEach(i => {
|
||||
@@ -442,6 +478,18 @@ const fetchPlannedExpenses = async () => {
|
||||
updateLoading.value = false
|
||||
}
|
||||
const transactions = ref<Transaction[]>([])
|
||||
|
||||
const selectedCategoryId = ref()
|
||||
const selectCategoryType = (categoryId) => {
|
||||
if (selectedCategoryId.value==categoryId) {
|
||||
selectedCategoryId.value = null
|
||||
} else {
|
||||
selectedCategoryId.value = categoryId
|
||||
}
|
||||
}
|
||||
const filteredTransactions = computed(() => {
|
||||
return selectedCategoryId.value ? transactions.value.filter(i => i.category.id==selectedCategoryId.value) : transactions.value
|
||||
})
|
||||
const fetchBudgetTransactions = async () => {
|
||||
transactions.value = await getBudgetTransactions(route.params.id, 'INSTANT')
|
||||
updateLoading.value = false
|
||||
@@ -740,9 +788,7 @@ onMounted(async () => {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.grid {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.max-h-tlist {
|
||||
max-height: 1170px; /* Ограничение высоты списка */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="card flex justify-center h-fit">
|
||||
<DrawerForm v-if="isDesktop" :visible="visible" :isEditing="isEditing" @close-drawer="closeDrawer">
|
||||
<DrawerForm v-if="isDesktop" :visible="visible" :isEditing="isEditing" @close-drawer="closeDrawer" >
|
||||
<template #default>
|
||||
<TransactionFormContent :transaction="props.transaction" :transaction-type="transactionType" :category-type="categoryType" @close-drawer="closeDrawer" @create-transaction="transactionUpdated"
|
||||
@delete-transaction="transactionUpdated" @transaction-updated="transactionUpdated" />
|
||||
@@ -20,7 +20,7 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DrawerForm from "@/components/DrawerForm.vue";
|
||||
import PopUp from "@/components/PopUp.vue";
|
||||
import TransactionFormContent from "@/components/TransactionFormContent.vue";
|
||||
import TransactionFormContent from "@/components/transactions/TransactionFormContent.vue";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ const createTransaction = async () => {
|
||||
editedTransaction.value.isDone = true;
|
||||
}
|
||||
await createTransactionRequest(editedTransaction.value);
|
||||
toast.add({severity: 'success', summary: 'Transaction created!', detail: 'Транзакция создана!', life: 3000});
|
||||
toast.add({severity: 'success', summary: 'Транзакция создана!', detail: 'Транзакция создана!', life: 3000});
|
||||
emit('create-transaction', editedTransaction.value);
|
||||
computeResult(true)
|
||||
resetForm();
|
||||
@@ -213,7 +213,7 @@ const deleteTransaction = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await deleteTransactionRequest(editedTransaction.value.id);
|
||||
toast.add({severity: 'success', summary: 'Transaction deleted!', detail: 'Транзакция удалена!', life: 3000});
|
||||
toast.add({severity: 'success', summary: 'Транзакция удалена!', detail: 'Транзакция удалена!', life: 3000});
|
||||
emit('delete-transaction', editedTransaction.value);
|
||||
closeDrawer()
|
||||
computeResult(true)
|
||||
@@ -230,7 +230,7 @@ const deleteTransaction = async () => {
|
||||
// Сброс формы
|
||||
const resetForm = () => {
|
||||
|
||||
editedTransaction.value.date = new Date();
|
||||
// editedTransaction.value.date = new Date();
|
||||
editedTransaction.value.amount = null;
|
||||
editedTransaction.value.comment = '';
|
||||
|
||||
@@ -280,10 +280,6 @@ onMounted(async () => {
|
||||
<template>
|
||||
|
||||
<div class="card flex justify-center h-fit">
|
||||
|
||||
|
||||
|
||||
|
||||
<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"
|
||||
@@ -312,7 +308,6 @@ onMounted(async () => {
|
||||
optionLabel="name"
|
||||
aria-labelledby="basic"
|
||||
class="justify-center"/>
|
||||
|
||||
<SelectButton v-model="selectedCategoryType" :options="categoryTypes" :allow-empty="false"
|
||||
optionLabel="name"
|
||||
aria-labelledby="basic"
|
||||
@@ -1,47 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import BudgetTransactionView from "@/components/budgets/BudgetTransactionView.vue";
|
||||
import IconField from "primevue/iconfield";
|
||||
import InputIcon from "primevue/inputicon";
|
||||
import InputText from "primevue/inputtext";
|
||||
import {getTransactions} from "@/services/transactionService";
|
||||
import {Transaction} from "@/models/Transaction";
|
||||
|
||||
import { getTransactions } from "@/services/transactionService";
|
||||
import { Transaction } from "@/models/Transaction";
|
||||
import ProgressSpinner from "primevue/progressspinner";
|
||||
|
||||
const loading = ref(false);
|
||||
const searchText = ref("");
|
||||
const transactions = ref<Transaction[]>([]);
|
||||
const limit = 20; // Количество транзакций на одну загрузку
|
||||
const offset = ref(0); // Начальное смещение
|
||||
const allLoaded = ref(false); // Флаг для отслеживания окончания данных
|
||||
|
||||
// Функция для получения транзакций с параметрами limit и offset
|
||||
const fetchTransactions = async () => {
|
||||
if (loading.value || allLoaded.value) return; // Останавливаем загрузку, если уже загружается или данные загружены полностью
|
||||
loading.value = true;
|
||||
|
||||
|
||||
|
||||
const fetchCategories = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getTransactions('INSTANT');
|
||||
transactions.value = response.data
|
||||
const response = await getTransactions('INSTANT', null,null, null, limit, offset.value);
|
||||
const newTransactions = response.data;
|
||||
|
||||
// Проверка на конец данных
|
||||
if (newTransactions.length < limit) {
|
||||
allLoaded.value = true; // Если данных меньше limit, значит, достигнут конец
|
||||
}
|
||||
|
||||
// Добавляем новые транзакции к текущему списку
|
||||
transactions.value.push(...newTransactions);
|
||||
offset.value += limit; // Обновляем смещение для следующей загрузки
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
console.error("Error fetching transactions:", error);
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const tgname = computed(() => {
|
||||
if (window.Telegram.WebApp) {
|
||||
const tg = window.Telegram.WebApp;
|
||||
// tg.expand(); // Разворачиваем веб-приложение на весь экран
|
||||
|
||||
// Получаем информацию о пользователе и выводим её
|
||||
return tg.initDataUnsafe.user
|
||||
return tg.initDataUnsafe.user;
|
||||
}
|
||||
})
|
||||
|
||||
const transactions = ref<Transaction[]>([])
|
||||
});
|
||||
|
||||
// Отфильтрованные транзакции по поисковому запросу
|
||||
const filteredTransactions = computed(() => {
|
||||
if (searchText.value.length === 0) {
|
||||
return transactions.value; // Return the full list when there's no search text
|
||||
return transactions.value;
|
||||
} else {
|
||||
return transactions.value.filter(transaction => {
|
||||
const search = searchText.value.toLowerCase();
|
||||
@@ -53,32 +61,47 @@ const filteredTransactions = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик прокрутки для ленивой загрузки
|
||||
const handleScroll = () => {
|
||||
const bottomReached = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2000;
|
||||
if (bottomReached && !loading.value) {
|
||||
fetchTransactions(); // Загружаем следующую страницу
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchCategories();
|
||||
})
|
||||
await fetchTransactions(); // Первоначальная загрузка данных
|
||||
window.addEventListener("scroll", handleScroll); // Добавляем обработчик прокрутки
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 bg-gray-100 h-full ">
|
||||
<!-- Заголовок -->
|
||||
<!-- {{tgname}}-->
|
||||
|
||||
<h2 class="text-4xl mb-6 font-bold">Transaction list</h2>
|
||||
<div class="px-4 bg-gray-100 h-full">
|
||||
<h2 class="text-4xl mb-6 font-bold">Transaction list</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-search"/>
|
||||
<InputText v-model="searchText" placeholder="Search"></InputText>
|
||||
</IconField>
|
||||
|
||||
<div class=" flex flex-col gap-2">
|
||||
<BudgetTransactionView v-for="transaction in filteredTransactions" :transaction="transaction" :is-list="true"/>
|
||||
<div class="flex flex-col gap-2">
|
||||
<BudgetTransactionView
|
||||
v-for="transaction in filteredTransactions"
|
||||
:key="transaction.id"
|
||||
:transaction="transaction"
|
||||
:is-list="true"
|
||||
@transaction-updated="fetchTransactions"
|
||||
/>
|
||||
<!-- Показать спиннер загрузки, если идет загрузка -->
|
||||
<ProgressSpinner v-if="loading" class="mb-4" style="width: 50px; height: 50px;"
|
||||
strokeWidth="8"
|
||||
fill="transparent"
|
||||
animationDuration=".5s" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
14
src/main.ts
14
src/main.ts
@@ -23,6 +23,20 @@ app.use(PrimeVue, {
|
||||
preset: Aura
|
||||
}
|
||||
});
|
||||
|
||||
app.config.globalProperties.$primevue.config.locale = {
|
||||
firstDayOfWeek: 1, // Устанавливаем понедельник как первый день недели
|
||||
dayNames: ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"],
|
||||
dayNamesShort: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
|
||||
dayNamesMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
|
||||
monthNames: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"],
|
||||
monthNamesShort: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
|
||||
today: "Сегодня",
|
||||
clear: "Очистить",
|
||||
dateFormat: "dd.mm.yy",
|
||||
weekHeader: "Нед",
|
||||
};
|
||||
|
||||
// main.js
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
|
||||
@@ -8,11 +8,12 @@ import SettingsView from "@/components/settings/SettingsView.vue";
|
||||
import RecurrentList from "@/components/settings/recurrent/RecurrentList.vue";
|
||||
import TransactionList from "@/components/transactions/TransactionList.vue";
|
||||
import LoginView from "@/components/auth/LoginView.vue";
|
||||
import AnalyticsView from "@/components/analytics/AnalyticsView.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/login', component: LoginView},
|
||||
{path: '/', name: 'Budgets main', component: BudgetList, meta: {requiresAuth: true}},
|
||||
{path: '/analytics', name: 'Analytics', component: BudgetList, meta: {requiresAuth: true}},
|
||||
{path: '/analytics', name: 'Analytics', component: AnalyticsView, 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}},
|
||||
|
||||
@@ -7,7 +7,7 @@ export const getTransaction = async (transactionId: int) => {
|
||||
return await apiClient.post(`/transactions/${transactionId}`,);
|
||||
}
|
||||
|
||||
export const getTransactions = async (transaction_type = null, category_type = null, category_id = null, user_id = null) => {
|
||||
export const getTransactions = async (transaction_type = null, category_type = null, category_id = null, user_id = null, limit = null, offset = null) => {
|
||||
const params = {};
|
||||
|
||||
// Add the parameters to the params object if they are not null
|
||||
@@ -25,6 +25,12 @@ export const getTransactions = async (transaction_type = null, category_type = n
|
||||
if (user_id) {
|
||||
params.user_id = user_id
|
||||
}
|
||||
if (limit) {
|
||||
params.limit = limit
|
||||
}
|
||||
if (offset) {
|
||||
params.offset = offset
|
||||
}
|
||||
|
||||
// Use axios to make the GET request, passing the params as the second argument
|
||||
return await apiClient.get('/transactions/', {
|
||||
@@ -34,7 +40,10 @@ export const getTransactions = async (transaction_type = null, category_type = n
|
||||
|
||||
export const createTransactionRequest = async (transaction: Transaction) => {
|
||||
transaction.date = format(transaction.date, 'yyyy-MM-dd')
|
||||
return await apiClient.post('/transactions', transaction);
|
||||
let transactionResponse = await apiClient.post('/transactions', transaction);
|
||||
console.log(transaction.date)
|
||||
transaction.date = new Date(transaction.date);
|
||||
return transactionResponse.data
|
||||
};
|
||||
|
||||
export const updateTransactionRequest = async (transaction: Transaction) => {
|
||||
@@ -63,4 +72,9 @@ export const deleteTransactionRequest = async (id: number) => {
|
||||
|
||||
export const getTransactionTypes = async () => {
|
||||
return await apiClient.get('/transactions/types');
|
||||
}
|
||||
|
||||
export const getTransactionCategoriesSums = async () => {
|
||||
let response = await apiClient.get('/transactions/categories/_calc_sums');
|
||||
return response.data;
|
||||
}
|
||||
@@ -20,7 +20,6 @@ export const useUserStore = defineStore('user', () => {
|
||||
user.value = null;
|
||||
} finally {
|
||||
loadingUser.value = false; // Сбрасываем флаг `loadingUser` в `false` после завершения
|
||||
console.log('Загрузка завершена, loadingUser:', loadingUser.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,4 +43,17 @@ export const getMonthName = (month: number) => {
|
||||
case 11:
|
||||
return 'Декабрь'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const generateRandomColors = () => {
|
||||
|
||||
const r = Math.floor(Math.random() * 256);
|
||||
const g = Math.floor(Math.random() * 256);
|
||||
const b = Math.floor(Math.random() * 256);
|
||||
const a = 0.5; // Прозрачность от 0.00 до 1.00
|
||||
|
||||
return [r,g,b]
|
||||
}
|
||||
|
||||
// Пример использования
|
||||
|
||||
|
||||
Reference in New Issue
Block a user