categories search

This commit is contained in:
xds
2025-11-18 00:03:38 +03:00
parent de4e242b33
commit 10e7811f6d
2 changed files with 129 additions and 38 deletions

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import {useSpaceStore} from "@/stores/spaceStore"; import {useSpaceStore} from "@/stores/spaceStore";
import {computed, onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import {Checkbox, Divider} from "primevue"; import {Checkbox, Divider} from "primevue";
import {useToast} from "primevue/usetoast"; import {useToast} from "primevue/usetoast";
import {Transaction} from "@/models/transaction"; import {Transaction, UpdateTransactionDTO} from "@/models/transaction";
import {TransactionService} from "@/services/transactions-service"; import {TransactionFilters, TransactionService} from "@/services/transactions-service";
import {formatAmount, formatDate} from "@/utils/utils"; import {formatAmount, formatDate} from "@/utils/utils";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import {TransactionKind} from "@/models/enums"; import {TransactionKind} from "@/models/enums";
@@ -15,39 +15,105 @@ const router = useRouter();
const spaceStore = useSpaceStore() const spaceStore = useSpaceStore()
const toolbar = useToolbarStore() const toolbar = useToolbarStore()
const transactionService = TransactionService const transactionService = TransactionService
const transactions = ref<Transaction[]>([])
const groupedTransactions = computed(() => {
const planned: Transaction[] = []
const instant: Transaction[] = []
for (const tx of transactions.value) { const showIsDone = ref(false)
if (tx.kind === TransactionKind.PLANNING) planned.push(tx)
else if (tx.kind === TransactionKind.INSTANT) instant.push(tx)
}
return { planned, instant } const setTransactionDone = async (transaction: Transaction): Promise<void> => {
}) const updateTransaction = {
type: transaction.type,
const plannedTransactions = computed(() => groupedTransactions.value.planned) kind: transaction.kind,
const instantTransactions = computed(() => groupedTransactions.value.instant) categoryId: transaction.category.id,
comment: transaction.comment,
amount: transaction.amount,
const fetchData = async () => { fees: 0,
if (spaceStore.selectedSpaceId) { isDone: true,
try { date: new Date(transaction.date),
console.log('hereeeee ') } as UpdateTransactionDTO
transactions.value = await transactionService.getTransactions(spaceStore.selectedSpaceId); try {
} catch (e) { await transactionService.updateTransaction(spaceStore.selectedSpaceId as number, transaction.id, updateTransaction)
toast.add({ } catch (error) {
severity: "error", toast.add({
summary: "Failed to load transactions.", severity: 'error',
detail: e, summary: 'Failed to update transactions.',
life: 3000, detail: String(error),
}) life: 3000,
} })
} }
} }
const plannedTransactions = ref<Transaction[]>([])
const plannedOffset = ref(0)
const plannedLimit = ref(10)
const plannedLastBatch = ref(false)
const instantTransactions = ref<Transaction[]>([])
const instantOffset = ref(0)
const instantLimit = ref(10)
const instantLastBatch = ref(false)
const fetchMorePlanned = async () => {
plannedOffset.value += plannedLimit.value
await fetchData(true, false)
}
const fetchMoreInstant = async () => {
instantOffset.value += instantLimit.value
await fetchData(false, true)
}
const fetchData = async (fetchPlanned: boolean = true, fetchInstant: boolean = true, replace: boolean = false) => {
if (!spaceStore.selectedSpaceId) return
try {
// Готовим промисы
const plannedPromise: Promise<Transaction[]> =
fetchPlanned
? transactionService.getTransactions(spaceStore.selectedSpaceId, {
kind: TransactionKind.PLANNING,
isDone: showIsDone.value ? undefined : false,
offset: plannedOffset.value,
limit: plannedLimit.value,
} as TransactionFilters) // никаких `as TransactionFilters`, если поля опциональные
: Promise.resolve(plannedTransactions.value)
const instantPromise: Promise<Transaction[]> =
fetchInstant
? transactionService.getTransactions(spaceStore.selectedSpaceId, {
kind: TransactionKind.INSTANT,
offset: instantOffset.value,
limit: instantLimit.value,
} as TransactionFilters)
: Promise.resolve(instantTransactions.value)
const [planned, instant] = await Promise.all([plannedPromise, instantPromise])
if (replace) {
// Если хочешь просто перезаписывать
plannedTransactions.value = planned
instantTransactions.value = instant
} else {
// Если хочешь "подгружать ещё" (пагинация, load more):
if (fetchPlanned) {
plannedTransactions.value = [...plannedTransactions.value, ...planned]
if (planned.length < plannedLimit.value) plannedLastBatch.value = true
}
if (fetchInstant) {
instantTransactions.value = [...instantTransactions.value, ...instant]
if (instant.length < instantLimit.value) instantLastBatch.value = true
}
}
} catch (e) {
toast.add({
severity: 'error',
summary: 'Failed to load transactions.',
detail: String(e),
life: 3000,
})
}
}
onMounted(async () => { onMounted(async () => {
await fetchData() await fetchData()
toolbar.registerHandler('openTransactionCreation', () => { toolbar.registerHandler('openTransactionCreation', () => {
@@ -63,21 +129,30 @@ onMounted(async () => {
</div> </div>
<div v-else class="flex flex-col gap-6 pb-10"> <div v-else class="flex flex-col gap-6 pb-10">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<span class="text-xl !font-semibold !pl-2">Planned transactions</span> <div class="flex flex-row justify-between">
<span class="text-xl !font-semibold !pl-2">Planned transactions</span>
<div class="flex flex-row gap-2 items-center">
<Checkbox v-model="showIsDone" binary value=" Показывать выполненные" @change="fetchData(true, false, true)"/>
Показывать выполненные
</div>
</div>
<div class="flex card"> <div class="flex card">
<span v-if="plannedTransactions.length==0">Looks like you haven't plan any transactions yet. <router-link <span v-if="plannedTransactions.length==0">Looks like you haven't plan any transactions yet. <router-link
to="/transactions/create" class="!text-blue-400">Try to create some.</router-link></span> to="/transactions/create" class="!text-blue-400">Try to create some.</router-link></span>
<div v-else v-for="key in plannedTransactions.keys()" :key="plannedTransactions[key].id" <div v-else v-for="key in plannedTransactions.keys()" :key="plannedTransactions[key].id"
class="flex flex-col w-full gap-0 pl-5 items-start justify-items-center font-bold "> class="flex flex-col w-full gap-0 pl-5 items-start justify-items-center font-bold ">
<div class="flex flex-row w-full items-center gap-4"> <div class="flex flex-row w-full items-center gap-4">
<Checkbox v-model="plannedTransactions[key].isDone" binary class="text-3xl"> <Checkbox v-model="plannedTransactions[key].isDone" binary class="text-3xl"
@change="setTransactionDone(plannedTransactions[key])">
{{ plannedTransactions[key].category.icon }} {{ plannedTransactions[key].category.icon }}
</Checkbox> </Checkbox>
<div class="flex !flex-row !justify-between !w-full" <div class="flex !flex-row !justify-between !w-full"
@click="router.push(`/transactions/${plannedTransactions[key].id}/edit`)"> @click="router.push(`/transactions/${plannedTransactions[key].id}/edit`)">
<div class="flex flex-row items-center gap-2"> <div class="flex flex-row items-center gap-2">
<div class="flex flex-col !font-bold "> {{ plannedTransactions[key].comment }} <div class="flex flex-col !font-bold "> {{ plannedTransactions[key].comment }}
<div class="flex flex-row text-sm">{{ plannedTransactions[key].category.name }}</div> <div class="flex flex-row text-sm">{{ plannedTransactions[key].category.icon }}
{{ plannedTransactions[key].category.name }}
</div>
</div> </div>
</div> </div>
<div class="flex flex-row gap-2 items-center !w-fit"> <div class="flex flex-row gap-2 items-center !w-fit">
@@ -92,6 +167,7 @@ onMounted(async () => {
<Divider v-if="key+1 !== plannedTransactions.length" class="!m-0 !py-3"/> <Divider v-if="key+1 !== plannedTransactions.length" class="!m-0 !py-3"/>
</div> </div>
</div> </div>
<button v-if="!plannedLastBatch" class="card w-fit " @click="fetchMorePlanned">Load more...</button>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<span class="text-xl !font-semibold !pl-2">Instant transactions</span> <span class="text-xl !font-semibold !pl-2">Instant transactions</span>
@@ -103,10 +179,14 @@ onMounted(async () => {
class="flex flex-col w-full gap-0 pl-5 items-start justify-items-center font-bold "> class="flex flex-col w-full gap-0 pl-5 items-start justify-items-center font-bold ">
<div class="flex flex-row w-full items-center justify-between"> <div class="flex flex-row w-full items-center justify-between">
<div class="flex flex-row items-center gap-2 "> <div class="flex flex-row items-center gap-2 ">
<span v-if="instantTransactions[key].category" class="text-3xl"> {{ instantTransactions[key].category.icon }}</span> <span v-if="instantTransactions[key].category" class="text-3xl"> {{
instantTransactions[key].category.icon
}}</span>
<i v-else class="pi pi-question !text-3xl"/> <i v-else class="pi pi-question !text-3xl"/>
<div class="flex flex-col !font-bold "> {{ instantTransactions[key].comment }} <div class="flex flex-col !font-bold "> {{ instantTransactions[key].comment }}
<div v-if="instantTransactions[key].category" class="flex flex-row text-sm">{{ instantTransactions[key].category.name }}</div> <div v-if="instantTransactions[key].category" class="flex flex-row text-sm">
{{ instantTransactions[key].category.name }}
</div>
</div> </div>
</div> </div>
<div class="flex flex-row gap-2 items-center"> <div class="flex flex-row gap-2 items-center">
@@ -120,6 +200,8 @@ onMounted(async () => {
<Divider v-if="key+1 !== instantTransactions.length" class="!m-0 !py-3"/> <Divider v-if="key+1 !== instantTransactions.length" class="!m-0 !py-3"/>
</div> </div>
</div> </div>
<button v-if="!instantLastBatch" class="card w-fit " @click="fetchMoreInstant">Load more...</button>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -12,10 +12,19 @@ function toDateOnly(d: Date): string {
return `${y}-${m}-${day}`; return `${y}-${m}-${day}`;
} }
export interface TransactionFilters{
type : TransactionType | null
kind: TransactionKind | null
dateFrom: string | Date | null
dateTo: string | Date | null
isDone: boolean | null
offset: number | null
limit: number | null
}
async function getTransactions(spaceId: number): Promise<Transaction[]> { async function getTransactions(spaceId: number, filters: TransactionFilters): Promise<Transaction[]> {
try { try {
let response = await api.get(`/spaces/${spaceId}/transactions`); let response = await api.post(`/spaces/${spaceId}/transactions/_search`, filters );
return response.data; return response.data;
}catch (error) { }catch (error) {
console.error(error); console.error(error);