202 lines
7.6 KiB
Vue
202 lines
7.6 KiB
Vue
<script setup lang="ts">
|
||
import {computed, onMounted, onUnmounted, ref, watch} 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, getTransactionTypes} from "@/services/transactionService";
|
||
import {Transaction} from "@/models/Transaction";
|
||
import ProgressSpinner from "primevue/progressspinner";
|
||
import {getUsers} from "@/services/userService";
|
||
import Button from "primevue/button";
|
||
import {EventBus} from '@/utils/EventBus.ts';
|
||
import {useSpaceStore} from "@/stores/spaceStore";
|
||
import router from "@/router";
|
||
import {useDrawerStore} from "@/stores/drawerStore";
|
||
|
||
const loading = ref(false);
|
||
const searchText = ref("");
|
||
const transactions = ref<Transaction[]>([]);
|
||
const limit = 20; // Количество транзакций на одну загрузку
|
||
const offset = ref(0); // Начальное смещение
|
||
const allLoaded = ref(false); // Флаг для отслеживания окончания данных
|
||
|
||
const drawerStore = useDrawerStore()
|
||
|
||
// Функция для получения транзакций с параметрами limit и offset
|
||
const fetchTransactions = async (reload) => {
|
||
|
||
// if (loading.value || allLoaded.value) return; // Останавливаем загрузку, если уже загружается или данные загружены полностью
|
||
loading.value = true;
|
||
|
||
try {
|
||
const response = await getTransactions('INSTANT', null, null, selectedUserId.value ? selectedUserId.value : null, null, reload ? offset.value : limit, reload ? 0 : offset.value);
|
||
const newTransactions = response.data;
|
||
|
||
// Проверка на конец данных
|
||
if (newTransactions.length < limit) {
|
||
allLoaded.value = true; // Если данных меньше limit, значит, достигнут конец
|
||
}
|
||
|
||
// Добавляем новые транзакции к текущему списку
|
||
|
||
reload ? transactions.value = newTransactions : transactions.value.push(...newTransactions)
|
||
!reload ? offset.value += limit : offset.value
|
||
} catch (error) {
|
||
console.error("Error fetching transactions:", error);
|
||
}
|
||
|
||
loading.value = false;
|
||
};
|
||
|
||
const switchUserFilter = async (user) => {
|
||
if (selectedUserId.value == user.id) {
|
||
selectedUserId.value = null
|
||
} else if (selectedUserId.value == null) {
|
||
selectedUserId.value = user.id;
|
||
} else {
|
||
selectedUserId.value = user.id;
|
||
}
|
||
|
||
await getTransactions('INSTANT', null, null, selectedUserId.value, null, offset.value, 0)
|
||
.then(it => transactions.value = it.data)
|
||
|
||
}
|
||
|
||
|
||
const tgname = computed(() => {
|
||
if (window.Telegram.WebApp) {
|
||
const tg = window.Telegram.WebApp;
|
||
return tg.initDataUnsafe.user;
|
||
}
|
||
});
|
||
|
||
// Отфильтрованные транзакции по поисковому запросу
|
||
const filteredTransactions = computed(() => {
|
||
// Проверяем, есть ли текст поиска
|
||
const search = searchText.value.trim().toLowerCase();
|
||
|
||
if (!search) {
|
||
// Если текст поиска пуст, возвращаем все транзакции
|
||
return transactions.value;
|
||
}
|
||
|
||
// Проверяем наличие данных
|
||
if (!transactions.value || !Array.isArray(transactions.value)) {
|
||
console.warn("Transactions is not a valid array");
|
||
return [];
|
||
}
|
||
|
||
// Фильтруем транзакции по тексту поиска
|
||
return transactions.value.filter(transaction => {
|
||
return transaction.comment.toLowerCase().includes(search) ||
|
||
transaction.category.name.toLowerCase().includes(search);
|
||
});
|
||
});
|
||
|
||
|
||
// Обработчик прокрутки для ленивой загрузки
|
||
// const handleScroll = () => {
|
||
// const bottomReached = window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2000;
|
||
// if (bottomReached && !loading.value) {
|
||
// fetchTransactions(); // Загружаем следующую страницу
|
||
// }
|
||
// };
|
||
|
||
const users = ref([])
|
||
const selectedUserId = ref(null)
|
||
const fetchUsers = async () => {
|
||
users.value = await getUsers();
|
||
}
|
||
|
||
const selectedTransactionType = ref(null)
|
||
const spaceStore = useSpaceStore()
|
||
const selectedSpace = computed(() => spaceStore.space)
|
||
|
||
watch(selectedSpace, async (newValue, oldValue) => {
|
||
if (newValue != oldValue) {
|
||
transactions.value = [];
|
||
await fetchTransactions(true)
|
||
}
|
||
})
|
||
const types = ref([])
|
||
onMounted(async () => {
|
||
EventBus.on('transactions-updated', fetchTransactions, true);
|
||
if (selectedSpace.value) {
|
||
await fetchTransactions(false); // Первоначальная загрузка данных
|
||
|
||
}
|
||
// await fetchUsers();
|
||
await getTransactionTypes().then(it => types.value = it.data);
|
||
// window.addEventListener("scroll", handleScroll); // Добавляем обработчик прокрутки
|
||
});
|
||
|
||
onUnmounted(async () => {
|
||
EventBus.off('transactions-updated', fetchTransactions);
|
||
})
|
||
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex flex-col gap-4 p-4 bg-gray-100 h-full ">
|
||
<div class="flex flex-row gap-4 items-center">
|
||
<h2 class="text-4xl font-bold">Список транзакций </h2>
|
||
<Button label="+ Создать" @click="{
|
||
drawerStore.setCategoryType('EXPENSE');
|
||
drawerStore.setTransactionType('INSTANT');
|
||
drawerStore.visible = true
|
||
}" size="small"/>
|
||
</div>
|
||
<div v-if="!selectedSpace" class="flex w-full h-full items-center justify-center">
|
||
<p>Сперва нужно выбрать Пространство.
|
||
<button class="text-blue-500 hover:underline" @click="router.push('/spaces').then((res) => router.go(0))">
|
||
Перейти
|
||
</button>
|
||
</p>
|
||
</div>
|
||
<div v-else class="flex flex-col gap-2">
|
||
<IconField>
|
||
<InputIcon class="pi pi-search"/>
|
||
<InputText v-model="searchText" placeholder="поиск"></InputText>
|
||
</IconField>
|
||
<div class="flex flex-row gap-2">
|
||
<!-- <span v-for="user in users">{{user.id}}</span>-->
|
||
<button v-for="user in selectedSpace.users" @click="switchUserFilter(user)"
|
||
class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2"
|
||
:class="selectedUserId == user.id ? '!bg-blue-100' : ''">
|
||
|
||
<p><span class="text-sm font-bold">{{ user.firstName }}</span></p>
|
||
</button>
|
||
|
||
|
||
<!-- <button v-for="type in types" class="rounded-xl border p-1 bg-white border-gray-300 mb-2 min-w-fit px-2">-->
|
||
<!-- <p><span class="text-sm font-bold">{{ type.name }}</span></p>-->
|
||
<!-- </button>-->
|
||
</div>
|
||
<div class="flex flex-col gap-2">
|
||
|
||
<BudgetTransactionView
|
||
v-for="transaction in filteredTransactions"
|
||
:key="transaction.id"
|
||
:transaction="transaction"
|
||
:is-list="true"
|
||
@transaction-updated="fetchTransactions(true)"
|
||
@delete-transaction="fetchTransactions(true)"
|
||
/>
|
||
<div v-if="!loading" class="flex items-center justify-center px-2 py-1 mb-5">
|
||
<Button @click="fetchTransactions(false)">Загрузить следующие...</Button>
|
||
</div>
|
||
<!-- Показать спиннер загрузки, если идет загрузка -->
|
||
<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>
|