mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 00:12:11 +08:00
add transaction tag filter to frontend
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<f7-page @page:afterin="onPageAfterIn">
|
||||
<f7-navbar>
|
||||
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
|
||||
<f7-nav-title :title="$t(title)"></f7-nav-title>
|
||||
<f7-nav-right>
|
||||
<f7-link icon-f7="ellipsis" :class="{ 'disabled': !hasAnyAvailableTag }" @click="showMoreActionSheet = true"></f7-link>
|
||||
<f7-link :text="$t(applyText)" :class="{ 'disabled': !hasAnyAvailableTag }" @click="save"></f7-link>
|
||||
</f7-nav-right>
|
||||
</f7-navbar>
|
||||
|
||||
<f7-block class="combination-list-wrapper margin-vertical skeleton-text" v-if="loading">
|
||||
<f7-accordion-item>
|
||||
<f7-block-title>
|
||||
<f7-accordion-toggle>
|
||||
<f7-list strong inset dividers media-list
|
||||
class="combination-list-header combination-list-opened">
|
||||
<f7-list-item>
|
||||
<template #title>
|
||||
<span>Tags</span>
|
||||
<f7-icon class="combination-list-chevron-icon" f7="chevron_up"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-accordion-toggle>
|
||||
</f7-block-title>
|
||||
<f7-accordion-content style="height: auto">
|
||||
<f7-list strong inset dividers accordion-list class="combination-list-content">
|
||||
<f7-list-item checkbox class="disabled" title="Tag Name"
|
||||
:key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
|
||||
<template #media>
|
||||
<f7-icon f7="app_fill"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-accordion-content>
|
||||
</f7-accordion-item>
|
||||
</f7-block>
|
||||
|
||||
<f7-list strong inset dividers accordion-list class="margin-top" v-if="!loading && !hasAnyAvailableTag">
|
||||
<f7-list-item :title="$t('No available tag')"></f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-block class="combination-list-wrapper margin-vertical" key="default" v-if="!loading">
|
||||
<f7-accordion-item :opened="collapseStates['default'].opened"
|
||||
@accordion:open="collapseStates['default'].opened = true"
|
||||
@accordion:close="collapseStates['default'].opened = false">
|
||||
<f7-block-title>
|
||||
<f7-accordion-toggle>
|
||||
<f7-list strong inset dividers media-list
|
||||
class="combination-list-header"
|
||||
:class="collapseStates['default'].opened ? 'combination-list-opened' : 'combination-list-closed'">
|
||||
<f7-list-item>
|
||||
<template #title>
|
||||
<span>{{ $t('Tags') }}</span>
|
||||
<f7-icon class="combination-list-chevron-icon" :f7="collapseStates['default'].opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-accordion-toggle>
|
||||
</f7-block-title>
|
||||
<f7-accordion-content :style="{ height: collapseStates['default'].opened ? 'auto' : '' }">
|
||||
<f7-list strong inset dividers accordion-list class="combination-list-content">
|
||||
<f7-list-item checkbox
|
||||
:title="transactionTag.name"
|
||||
:value="transactionTag.id"
|
||||
:checked="!filterTagIds[transactionTag.id]"
|
||||
:key="transactionTag.id"
|
||||
v-for="transactionTag in allVisibleTags"
|
||||
@change="selectTransactionTag">
|
||||
<template #media>
|
||||
<f7-icon f7="number"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-accordion-content>
|
||||
</f7-accordion-item>
|
||||
</f7-block>
|
||||
|
||||
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
||||
<f7-actions-group>
|
||||
<f7-actions-button @click="selectAll">{{ $t('Select All') }}</f7-actions-button>
|
||||
<f7-actions-button @click="selectNone">{{ $t('Select None') }}</f7-actions-button>
|
||||
<f7-actions-button @click="selectInvert">{{ $t('Invert Selection') }}</f7-actions-button>
|
||||
</f7-actions-group>
|
||||
<f7-actions-group>
|
||||
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
||||
</f7-actions-group>
|
||||
</f7-actions>
|
||||
</f7-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
|
||||
import {
|
||||
selectAll,
|
||||
selectNone,
|
||||
selectInvert
|
||||
} from '@/lib/common.js';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'f7route',
|
||||
'f7router'
|
||||
],
|
||||
data: function () {
|
||||
return {
|
||||
loading: true,
|
||||
loadingError: null,
|
||||
type: null,
|
||||
filterTagIds: {},
|
||||
collapseStates: {
|
||||
'default': {
|
||||
opened: true
|
||||
}
|
||||
},
|
||||
showMoreActionSheet: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useTransactionTagsStore, useTransactionsStore),
|
||||
title() {
|
||||
return 'Filter Transaction Tags';
|
||||
},
|
||||
applyText() {
|
||||
return 'Apply';
|
||||
},
|
||||
allVisibleTags() {
|
||||
return this.transactionTagsStore.allVisibleTags;
|
||||
},
|
||||
hasAnyAvailableTag() {
|
||||
return this.transactionTagsStore.allVisibleTagsCount > 0;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
const query = self.f7route.query;
|
||||
|
||||
self.type = query.type;
|
||||
|
||||
self.transactionTagsStore.loadAllTags({
|
||||
force: false
|
||||
}).then(() => {
|
||||
self.loading = false;
|
||||
|
||||
const allTransactionTagIds = {};
|
||||
|
||||
for (let transactionTagId in self.transactionTagsStore.allTransactionTagsMap) {
|
||||
if (!Object.prototype.hasOwnProperty.call(self.transactionTagsStore.allTransactionTagsMap, transactionTagId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const transactionTag = self.transactionTagsStore.allTransactionTagsMap[transactionTagId];
|
||||
|
||||
if (self.type === 'transactionListCurrent' && self.transactionsStore.allFilterTagIdsCount > 0) {
|
||||
allTransactionTagIds[transactionTag.id] = true;
|
||||
} else {
|
||||
allTransactionTagIds[transactionTag.id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.type === 'transactionListCurrent') {
|
||||
for (let transactionTagId in self.transactionsStore.allFilterTagIds) {
|
||||
if (!Object.prototype.hasOwnProperty.call(self.transactionsStore.allFilterTagIds, transactionTagId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const transactionTag = self.transactionTagsStore.allTransactionTagsMap[transactionTagId];
|
||||
|
||||
if (transactionTag) {
|
||||
allTransactionTagIds[transactionTag.id] = false;
|
||||
}
|
||||
}
|
||||
self.filterTagIds = allTransactionTagIds;
|
||||
} else {
|
||||
self.$toast('Parameter Invalid');
|
||||
self.loadingError = 'Parameter Invalid';
|
||||
}
|
||||
}).catch(error => {
|
||||
if (error.processed) {
|
||||
self.loading = false;
|
||||
} else {
|
||||
self.loadingError = error;
|
||||
self.$toast(error.message || error);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onPageAfterIn() {
|
||||
this.$routeBackOnError(this.f7router, 'loadingError');
|
||||
},
|
||||
save() {
|
||||
const self = this;
|
||||
const router = self.f7router;
|
||||
|
||||
const filteredTagIds = {};
|
||||
let isAllSelected = true;
|
||||
let finalTagIds = '';
|
||||
|
||||
for (let transactionTagId in self.filterTagIds) {
|
||||
if (!Object.prototype.hasOwnProperty.call(self.filterTagIds, transactionTagId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const transactionTag = self.transactionTagsStore.allTransactionTagsMap[transactionTagId];
|
||||
|
||||
if (self.filterTagIds[transactionTag.id]) {
|
||||
filteredTagIds[transactionTag.id] = true;
|
||||
isAllSelected = false;
|
||||
} else {
|
||||
if (finalTagIds.length > 0) {
|
||||
finalTagIds += ',';
|
||||
}
|
||||
|
||||
finalTagIds += transactionTag.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.type === 'transactionListCurrent') {
|
||||
const changed = self.transactionsStore.updateTransactionListFilter({
|
||||
tagIds: isAllSelected ? '' : finalTagIds
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
self.transactionsStore.updateTransactionListInvalidState(true);
|
||||
}
|
||||
}
|
||||
|
||||
router.back();
|
||||
},
|
||||
selectTransactionTag(e) {
|
||||
const transactionTagId = e.target.value;
|
||||
const transactionTag = this.transactionTagsStore.allTransactionTagsMap[transactionTagId];
|
||||
|
||||
if (!transactionTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filterTagIds[transactionTag.id] = !e.target.checked;
|
||||
},
|
||||
selectAll() {
|
||||
selectAll(this.filterTagIds, this.transactionTagsStore.allTransactionTagsMap);
|
||||
},
|
||||
selectNone() {
|
||||
selectNone(this.filterTagIds, this.transactionTagsStore.allTransactionTagsMap);
|
||||
},
|
||||
selectInvert() {
|
||||
selectInvert(this.filterTagIds, this.transactionTagsStore.allTransactionTagsMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -504,6 +504,9 @@ export default {
|
||||
allTags() {
|
||||
return this.transactionTagsStore.allTransactionTags;
|
||||
},
|
||||
allTagsMap() {
|
||||
return this.transactionTagsStore.allTransactionTagsMap;
|
||||
},
|
||||
hasAvailableExpenseCategories() {
|
||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
||||
return false;
|
||||
@@ -683,11 +686,13 @@ export default {
|
||||
self.allCategoriesMap,
|
||||
self.allVisibleAccounts,
|
||||
self.allAccountsMap,
|
||||
self.allTagsMap,
|
||||
self.defaultAccountId,
|
||||
{
|
||||
type: query.type,
|
||||
categoryId: query.categoryId,
|
||||
accountId: query.accountId
|
||||
accountId: query.accountId,
|
||||
tagIds: query.tagIds
|
||||
},
|
||||
(self.mode === 'edit' || self.mode === 'view'),
|
||||
(self.mode === 'edit' || self.mode === 'view')
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
|
||||
<f7-nav-title :title="$t('Transaction List')"></f7-nav-title>
|
||||
<f7-nav-right class="navbar-compact-icons">
|
||||
<f7-link icon-f7="plus" :class="{ 'disabled': !canAddTransaction }" :href="`/transaction/add?type=${query.type}&categoryId=${queryAllFilterCategoryIdsCount === 1 ? query.categoryIds : ''}&accountId=${queryAllFilterAccountIdsCount === 1 ? query.accountIds : ''}`"></f7-link>
|
||||
<f7-link icon-f7="plus" :class="{ 'disabled': !canAddTransaction }" :href="`/transaction/add?type=${query.type}&categoryId=${queryAllFilterCategoryIdsCount === 1 ? query.categoryIds : ''}&accountId=${queryAllFilterAccountIdsCount === 1 ? query.accountIds : ''}&tagIds=${query.tagIds || ''}`"></f7-link>
|
||||
</f7-nav-right>
|
||||
|
||||
<f7-subnavbar :inner="false">
|
||||
@@ -42,7 +42,7 @@
|
||||
<span :class="{ 'tabbar-item-changed': query.accountIds }">{{ queryAccountName }}</span>
|
||||
</f7-link>
|
||||
<f7-link popover-open=".more-popover-menu">
|
||||
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter }"></f7-icon>
|
||||
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter || query.tagIds }"></f7-icon>
|
||||
</f7-link>
|
||||
</f7-toolbar>
|
||||
|
||||
@@ -445,6 +445,36 @@
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`)"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
|
||||
<f7-list-item group-title :title="$t('Tags')" />
|
||||
<f7-list-item :class="{ 'list-item-selected': !query.tagIds }" :title="$t('All')" @click="changeTagFilter('')">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.tagIds"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :class="{ 'list-item-selected': query.tagIds && queryAllFilterTagIdsCount > 1 }"
|
||||
:title="$t('Multiple Tags')" @click="filterMultipleTags()" v-if="allVisibleTagsCount > 0">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagIds && queryAllFilterTagIdsCount > 1"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :title="transactionTag.name"
|
||||
:class="{ 'list-item-selected': query.tagIds === transactionTag.id, 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && queryAllFilterTagIds[transactionTag.id] }"
|
||||
:key="transactionTag.id"
|
||||
v-for="transactionTag in allTransactionTags"
|
||||
v-show="!transactionTag.hidden"
|
||||
@click="changeTagFilter(transactionTag.id)"
|
||||
>
|
||||
<template #before-title>
|
||||
<f7-icon f7="number" class="transaction-tag-name"></f7-icon>
|
||||
</template>
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon"
|
||||
f7="checkmark_alt"
|
||||
v-if="query.tagIds === transactionTag.id">
|
||||
</f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-popover>
|
||||
|
||||
@@ -555,12 +585,18 @@ export default {
|
||||
queryAllFilterAccountIds() {
|
||||
return this.transactionsStore.allFilterAccountIds;
|
||||
},
|
||||
queryAllFilterTagIds() {
|
||||
return this.transactionsStore.allFilterTagIds;
|
||||
},
|
||||
queryAllFilterCategoryIdsCount() {
|
||||
return this.transactionsStore.allFilterCategoryIdsCount;
|
||||
},
|
||||
queryAllFilterAccountIdsCount() {
|
||||
return this.transactionsStore.allFilterAccountIdsCount;
|
||||
},
|
||||
queryAllFilterTagIdsCount() {
|
||||
return this.transactionsStore.allFilterTagIdsCount;
|
||||
},
|
||||
queryCategoryName() {
|
||||
if (this.queryAllFilterCategoryIdsCount > 1) {
|
||||
return this.$t('Multiple Categories');
|
||||
@@ -639,6 +675,9 @@ export default {
|
||||
allTransactionTags() {
|
||||
return this.transactionTagsStore.allTransactionTagsMap;
|
||||
},
|
||||
allVisibleTagsCount() {
|
||||
return this.transactionTagsStore.allVisibleTagsCount;
|
||||
},
|
||||
allDateRanges() {
|
||||
return datetimeConstants.allDateRanges;
|
||||
},
|
||||
@@ -674,7 +713,8 @@ export default {
|
||||
minTime: dateRange ? dateRange.minTime : undefined,
|
||||
type: parseInt(query.type) > 0 ? parseInt(query.type) : undefined,
|
||||
categoryIds: query.categoryIds,
|
||||
accountIds: query.accountIds
|
||||
accountIds: query.accountIds,
|
||||
tagIds: query.tagIds
|
||||
});
|
||||
|
||||
this.reload(null);
|
||||
@@ -889,6 +929,21 @@ export default {
|
||||
this.reload(null);
|
||||
}
|
||||
},
|
||||
changeTagFilter(tagIds) {
|
||||
if (this.query.tagIds === tagIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = this.transactionsStore.updateTransactionListFilter({
|
||||
tagIds: tagIds
|
||||
});
|
||||
|
||||
this.showMorePopover = false;
|
||||
|
||||
if (changed) {
|
||||
this.reload(null);
|
||||
}
|
||||
},
|
||||
changeAmountFilter(filterType) {
|
||||
if (this.query.amountFilter === filterType) {
|
||||
return;
|
||||
@@ -935,6 +990,9 @@ export default {
|
||||
filterMultipleAccounts() {
|
||||
this.f7router.navigate('/settings/filter/account?type=transactionListCurrent');
|
||||
},
|
||||
filterMultipleTags() {
|
||||
this.f7router.navigate('/settings/filter/tag?type=transactionListCurrent');
|
||||
},
|
||||
duplicate(transaction) {
|
||||
this.f7router.navigate(`/transaction/add?id=${transaction.id}&type=${transaction.type}`);
|
||||
},
|
||||
@@ -1170,6 +1228,11 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.more-popover-menu .transaction-tag-name {
|
||||
padding-right: 4px;
|
||||
font-size: var(--f7-list-item-title-font-size);
|
||||
}
|
||||
|
||||
.date-popover-menu .popover-inner,
|
||||
.category-popover-menu .popover-inner,
|
||||
.account-popover-menu .popover-inner,
|
||||
|
||||
Reference in New Issue
Block a user