support transaction tag group
This commit is contained in:
@@ -259,15 +259,17 @@
|
||||
|
||||
<template :key="categoryType"
|
||||
v-for="(categories, categoryType) in allPrimaryCategories">
|
||||
<v-divider />
|
||||
|
||||
<v-list-item density="compact" v-show="categories && categories.length">
|
||||
<v-list-item-title>
|
||||
<span class="text-sm">{{ getTransactionTypeName(categoryTypeToTransactionType(parseInt(categoryType)), 'Type') }}</span>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-group :key="category.id" v-for="category in categories">
|
||||
<v-list-group :key="category.id" v-for="(category, index) in categories">
|
||||
<template #activator="{ props }" v-if="!category.hidden || queryAllFilterCategoryIds[category.id] || allCategories[query.categoryIds]?.parentId === category.id || hasSubCategoryInQuery(category)">
|
||||
<v-divider />
|
||||
<v-divider v-if="index > 0" />
|
||||
<v-list-item class="text-sm" density="compact"
|
||||
:class="getCategoryListItemCheckedClass(category, queryAllFilterCategoryIds)"
|
||||
v-bind="props">
|
||||
@@ -474,24 +476,33 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider v-if="query.tagFilter && query.tagFilter !== TransactionTagFilter.TransactionNoTagFilterValue" />
|
||||
<template :key="transactionTagGroup.id"
|
||||
v-for="transactionTagGroup in allTransactionTagGroupsWithDefault">
|
||||
<v-divider v-if="allTransactionTagsByGroup[transactionTagGroup.id] && allTransactionTagsByGroup[transactionTagGroup.id]?.length && hasVisibleTagsInTagGroup(transactionTagGroup)" />
|
||||
|
||||
<template :key="transactionTag.id"
|
||||
v-for="transactionTag in allTransactionTags">
|
||||
<v-divider v-if="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])" />
|
||||
<v-list-item class="text-sm" density="compact"
|
||||
:value="transactionTag.id"
|
||||
:class="{ 'list-item-selected': queryAllFilterTagIdsCount === 1 && isDefined(queryAllFilterTagIds[transactionTag.id]), 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && isDefined(queryAllFilterTagIds[transactionTag.id]) }"
|
||||
:append-icon="(queryAllFilterTagIds[transactionTag.id] === true ? mdiCheck : (queryAllFilterTagIds[transactionTag.id] === false ? mdiClose : undefined))"
|
||||
v-if="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])">
|
||||
<v-list-item-title class="cursor-pointer"
|
||||
@click="changeTagFilter(TransactionTagFilter.of(transactionTag.id).toTextualTagFilter())">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="24" :icon="mdiPound"/>
|
||||
<span class="text-sm ms-3">{{ transactionTag.name }}</span>
|
||||
</div>
|
||||
<v-list-item density="compact" v-if="allTransactionTagsByGroup[transactionTagGroup.id] && allTransactionTagsByGroup[transactionTagGroup.id]?.length && hasVisibleTagsInTagGroup(transactionTagGroup)">
|
||||
<v-list-item-title>
|
||||
<span class="text-sm">{{ transactionTagGroup.name }}</span>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<template :key="transactionTag.id"
|
||||
v-for="(transactionTag, index) in (allTransactionTagsByGroup[transactionTagGroup.id] ?? [])">
|
||||
<v-divider v-if="index > 0 && (!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id]))" />
|
||||
<v-list-item class="text-sm" density="compact"
|
||||
:value="transactionTag.id"
|
||||
:class="{ 'list-item-selected': queryAllFilterTagIdsCount === 1 && isDefined(queryAllFilterTagIds[transactionTag.id]), 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && isDefined(queryAllFilterTagIds[transactionTag.id]) }"
|
||||
:append-icon="(queryAllFilterTagIds[transactionTag.id] === true ? mdiCheck : (queryAllFilterTagIds[transactionTag.id] === false ? mdiClose : undefined))"
|
||||
v-if="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])">
|
||||
<v-list-item-title class="cursor-pointer"
|
||||
@click="changeTagFilter(TransactionTagFilter.of(transactionTag.id).toTextualTagFilter())">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="24" :icon="mdiPound"/>
|
||||
<span class="text-sm ms-3">{{ transactionTag.name }}</span>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@@ -785,6 +796,8 @@ const {
|
||||
allCategories,
|
||||
allPrimaryCategories,
|
||||
allAvailableCategoriesCount,
|
||||
allTransactionTagGroupsWithDefault,
|
||||
allTransactionTagsByGroup,
|
||||
allTransactionTags,
|
||||
allAvailableTagsCount,
|
||||
query,
|
||||
@@ -806,6 +819,7 @@ const {
|
||||
transactionCalendarMaxDate,
|
||||
currentMonthTransactionData,
|
||||
hasSubCategoryInQuery,
|
||||
hasVisibleTagsInTagGroup,
|
||||
isSameAsDefaultTimezoneOffsetMinutes,
|
||||
canAddTransaction,
|
||||
getDisplayTime,
|
||||
|
||||
@@ -78,6 +78,7 @@ import { type NameValue, values } from '@/core/base.ts';
|
||||
import { CategoryType } from '@/core/category.ts';
|
||||
import { AUTOMATICALLY_CREATED_CATEGORY_ICON_ID } from '@/consts/icon.ts';
|
||||
import { DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
||||
import { DEFAULT_TAG_GROUP_ID } from '@/consts/tag.ts';
|
||||
|
||||
import { type TransactionCategoryCreateRequest, type TransactionCategoryCreateWithSubCategories, TransactionCategory } from '@/models/transaction_category.ts';
|
||||
import { type TransactionTagCreateRequest, TransactionTag } from '@/models/transaction_tag.ts';
|
||||
@@ -284,12 +285,13 @@ function confirm(): void {
|
||||
const submitTags: TransactionTagCreateRequest[] = [];
|
||||
|
||||
for (const item of selectedNames.value) {
|
||||
const tag: TransactionTag = TransactionTag.createNewTag(item);
|
||||
const tag: TransactionTag = TransactionTag.createNewTag(item, DEFAULT_TAG_GROUP_ID);
|
||||
submitTags.push(tag.toCreateRequest());
|
||||
}
|
||||
|
||||
transactionTagsStore.addTags({
|
||||
tags: submitTags,
|
||||
groupId: DEFAULT_TAG_GROUP_ID,
|
||||
skipExists: true
|
||||
}).then(response => {
|
||||
transactionTagsStore.loadAllTags({ force: false }).then(() => {
|
||||
|
||||
@@ -327,57 +327,14 @@
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="12">
|
||||
<v-autocomplete
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
auto-select-first
|
||||
persistent-placeholder
|
||||
multiple
|
||||
chips
|
||||
:closable-chips="mode !== TransactionEditPageMode.View"
|
||||
<transaction-tag-auto-complete
|
||||
:readonly="mode === TransactionEditPageMode.View"
|
||||
:disabled="loading || submitting"
|
||||
:label="tt('Tags')"
|
||||
:placeholder="tt('None')"
|
||||
:items="allTags"
|
||||
:show-label="true"
|
||||
:allow-add-new-tag="true"
|
||||
v-model="transaction.tagIds"
|
||||
v-model:search="tagSearchContent"
|
||||
>
|
||||
<template #chip="{ props, item }">
|
||||
<v-chip :prepend-icon="mdiPound" :text="item.title" v-bind="props"/>
|
||||
</template>
|
||||
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item :value="item.value" v-bind="props" v-if="!item.raw.hidden">
|
||||
<template #title>
|
||||
<v-list-item-title>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="20" start :icon="mdiPound"/>
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item :disabled="true" v-bind="props"
|
||||
v-if="item.raw.hidden && item.raw.name.toLowerCase().indexOf(tagSearchContent.toLowerCase()) >= 0 && isAllFilteredTagHidden">
|
||||
<template #title>
|
||||
<v-list-item-title>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="20" start :icon="mdiPound"/>
|
||||
<span>{{ item.title }}</span>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template #no-data>
|
||||
<v-list class="py-0">
|
||||
<v-list-item v-if="tagSearchContent" @click="saveNewTag(tagSearchContent)">{{ tt('format.misc.addNewTag', { tag: tagSearchContent }) }}</v-list-item>
|
||||
<v-list-item v-else-if="!tagSearchContent">{{ tt('No available tag') }}</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
@tag:saving="onSavingTag"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="12">
|
||||
<v-textarea
|
||||
@@ -535,7 +492,6 @@ import { TemplateType, ScheduledTemplateFrequencyType } from '@/core/template.ts
|
||||
import { KnownErrorCode } from '@/consts/api.ts';
|
||||
import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts';
|
||||
|
||||
import { TransactionTag } from '@/models/transaction_tag.ts';
|
||||
import { TransactionTemplate } from '@/models/transaction_template.ts';
|
||||
import type { TransactionPictureInfoBasicResponse } from '@/models/transaction_picture_info.ts';
|
||||
import { Transaction } from '@/models/transaction.ts';
|
||||
@@ -567,7 +523,6 @@ import {
|
||||
mdiSwapHorizontal,
|
||||
mdiMapMarkerOutline,
|
||||
mdiCheck,
|
||||
mdiPound,
|
||||
mdiMenuDown,
|
||||
mdiImagePlusOutline,
|
||||
mdiTrashCanOutline,
|
||||
@@ -621,7 +576,6 @@ const {
|
||||
allVisibleCategorizedAccounts,
|
||||
allCategories,
|
||||
allCategoriesMap,
|
||||
allTags,
|
||||
allTagsMap,
|
||||
firstVisibleAccountId,
|
||||
hasVisibleExpenseCategories,
|
||||
@@ -669,7 +623,6 @@ const activeTab = ref<string>('basicInfo');
|
||||
const originalTransactionEditable = ref<boolean>(false);
|
||||
const noTransactionDraft = ref<boolean>(false);
|
||||
const geoMenuState = ref<boolean>(false);
|
||||
const tagSearchContent = ref<string>('');
|
||||
const removingPictureId = ref<string>('');
|
||||
|
||||
const initAmount = ref<number | undefined>(undefined);
|
||||
@@ -692,22 +645,7 @@ const sourceAmountColor = computed<string | undefined>(() => {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const isAllFilteredTagHidden = computed<boolean>(() => {
|
||||
const lowerCaseTagSearchContent = tagSearchContent.value.toLowerCase();
|
||||
let hiddenCount = 0;
|
||||
|
||||
for (const tag of allTags.value) {
|
||||
if (!lowerCaseTagSearchContent || tag.name.toLowerCase().indexOf(lowerCaseTagSearchContent) >= 0) {
|
||||
if (!tag.hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hiddenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return hiddenCount > 0;
|
||||
});
|
||||
|
||||
const isTransactionModified = computed<boolean>(() => {
|
||||
if (mode.value === TransactionEditPageMode.Add) {
|
||||
@@ -1141,26 +1079,6 @@ function clearGeoLocation(): void {
|
||||
transaction.value.removeGeoLocation();
|
||||
}
|
||||
|
||||
function saveNewTag(tagName: string): void {
|
||||
submitting.value = true;
|
||||
|
||||
transactionTagsStore.saveTag({
|
||||
tag: TransactionTag.createNewTag(tagName)
|
||||
}).then(tag => {
|
||||
submitting.value = false;
|
||||
|
||||
if (tag && tag.id) {
|
||||
transaction.value.tagIds.push(tag.id);
|
||||
}
|
||||
}).catch(error => {
|
||||
submitting.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showOpenPictureDialog(): void {
|
||||
if (!canAddTransactionPicture.value || submitting.value) {
|
||||
return;
|
||||
@@ -1231,6 +1149,10 @@ function viewOrRemovePicture(pictureInfo: TransactionPictureInfoBasicResponse):
|
||||
});
|
||||
}
|
||||
|
||||
function onSavingTag(state: boolean): void {
|
||||
submitting.value = state;
|
||||
}
|
||||
|
||||
function onShowDateTimeError(error: string): void {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user