mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 16:24:25 +08:00
tag filter supports selecting both included and excluded tags simultaneously
This commit is contained in:
@@ -40,14 +40,27 @@
|
||||
</f7-list>
|
||||
|
||||
<f7-block class="combination-list-wrapper margin-vertical" key="default" v-show="!loading && hasAnyVisibleTag">
|
||||
<f7-list class="margin-top-half margin-bottom" strong inset dividers v-if="query['type'] === 'statisticsCurrent'">
|
||||
<f7-list class="margin-top-half margin-bottom" strong inset dividers>
|
||||
<f7-list-item radio
|
||||
:title="filterType.displayName"
|
||||
:value="filterType.type"
|
||||
:checked="tagFilterType === filterType.type"
|
||||
:title="tt(filterType.name)"
|
||||
:key="filterType.type"
|
||||
v-for="filterType in allTagFilterTypes"
|
||||
@change="tagFilterType = filterType.type">
|
||||
:value="filterType.type"
|
||||
:checked="includeTagFilterType === filterType.type"
|
||||
v-for="filterType in [TransactionTagFilterType.HasAny, TransactionTagFilterType.HasAll]"
|
||||
@change="includeTagFilterType = filterType.type"
|
||||
v-if="includeTagsCount > 1">
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list class="margin-top-half margin-bottom" strong inset dividers>
|
||||
<f7-list-item radio
|
||||
:title="tt(filterType.name)"
|
||||
:key="filterType.type"
|
||||
:value="filterType.type"
|
||||
:checked="excludeTagFilterType === filterType.type"
|
||||
v-for="filterType in [TransactionTagFilterType.NotHasAny, TransactionTagFilterType.NotHasAll]"
|
||||
@change="excludeTagFilterType = filterType.type"
|
||||
v-if="excludeTagsCount > 1">
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
@@ -68,14 +81,15 @@
|
||||
</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
|
||||
<f7-list-item link="#"
|
||||
popover-open=".tag-filter-state-popover-menu"
|
||||
:title="transactionTag.name"
|
||||
:value="transactionTag.id"
|
||||
:checked="!filterTagIds[transactionTag.id]"
|
||||
:key="transactionTag.id"
|
||||
:after="tt(filterTagIds[transactionTag.id] === TransactionTagFilterState.Include ? 'Included' : filterTagIds[transactionTag.id] === TransactionTagFilterState.Exclude ? 'Excluded' : 'Default')"
|
||||
v-for="transactionTag in allTags"
|
||||
v-show="showHidden || !transactionTag.hidden"
|
||||
@change="updateTransactionTagSelected">
|
||||
@click="currentTransactionTagId = transactionTag.id">
|
||||
<template #media>
|
||||
<f7-icon class="transaction-tag-icon" f7="number">
|
||||
<f7-badge color="gray" class="right-bottom-icon" v-if="transactionTag.hidden">
|
||||
@@ -89,14 +103,35 @@
|
||||
</f7-accordion-item>
|
||||
</f7-block>
|
||||
|
||||
<f7-popover class="tag-filter-state-popover-menu"
|
||||
v-model:opened="showTagFilterStatePopover">
|
||||
<f7-list dividers>
|
||||
<f7-list-item :title="state.displayName"
|
||||
:class="{ 'list-item-selected': filterTagIds[currentTransactionTagId] === state.type }"
|
||||
:key="state.type"
|
||||
v-for="state in [
|
||||
{ type: TransactionTagFilterState.Include, displayName: tt('Included') },
|
||||
{ type: TransactionTagFilterState.Default, displayName: tt('Default') },
|
||||
{ type: TransactionTagFilterState.Exclude, displayName: tt('Excluded') }
|
||||
]"
|
||||
@click="updateCurrentTransactionTagState(state.type)">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="filterTagIds[currentTransactionTagId] === state.type"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-popover>
|
||||
|
||||
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
||||
<f7-actions-group>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectAllTransactionTags">{{ tt('Select All') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectNoneTransactionTags">{{ tt('Select None') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectInvertTransactionTags">{{ tt('Invert Selection') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Include)">{{ tt('Set All to Included') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Default)">{{ tt('Set All to Default') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(false, TransactionTagFilterState.Exclude)">{{ tt('Set All to Excluded') }}</f7-actions-button>
|
||||
</f7-actions-group>
|
||||
<f7-actions-group>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="selectAllVisibleTransactionTags">{{ tt('Select All Visible') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Include)">{{ tt('Set All Visible Items to Included') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Default)">{{ tt('Set All Visible Items to Default') }}</f7-actions-button>
|
||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleTag }" @click="setAllToState(true, TransactionTagFilterState.Exclude)">{{ tt('Set All Visible Items to Excluded') }}</f7-actions-button>
|
||||
</f7-actions-group>
|
||||
<f7-actions-group>
|
||||
<f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Tags') }}</f7-actions-button>
|
||||
@@ -115,16 +150,14 @@ import type { Router } from 'framework7/types';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { useI18nUIComponents } from '@/lib/ui/mobile.ts';
|
||||
import { useTransactionTagFilterSettingPageBase } from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts';
|
||||
import {
|
||||
useTransactionTagFilterSettingPageBase,
|
||||
TransactionTagFilterState
|
||||
} from '@/views/base/settings/TransactionTagFilterSettingPageBase.ts';
|
||||
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
||||
|
||||
import {
|
||||
selectAllVisible,
|
||||
selectAll,
|
||||
selectNone,
|
||||
selectInvert
|
||||
} from '@/lib/common.ts';
|
||||
import { TransactionTagFilterType } from '@/core/transaction.ts';
|
||||
|
||||
interface CollapseState {
|
||||
opened: boolean;
|
||||
@@ -144,11 +177,13 @@ const {
|
||||
loading,
|
||||
showHidden,
|
||||
filterTagIds,
|
||||
tagFilterType,
|
||||
includeTagFilterType,
|
||||
excludeTagFilterType,
|
||||
includeTagsCount,
|
||||
excludeTagsCount,
|
||||
title,
|
||||
applyText,
|
||||
allTags,
|
||||
allTagFilterTypes,
|
||||
hasAnyAvailableTag,
|
||||
hasAnyVisibleTag,
|
||||
loadFilterTagIds,
|
||||
@@ -158,6 +193,8 @@ const {
|
||||
const transactionTagsStore = useTransactionTagsStore();
|
||||
|
||||
const loadingError = ref<unknown | null>(null);
|
||||
const currentTransactionTagId = ref<string>('');
|
||||
const showTagFilterStatePopover = ref<boolean>(false);
|
||||
const showMoreActionSheet = ref<boolean>(false);
|
||||
|
||||
const collapseStates = ref<Record<string, CollapseState>>({
|
||||
@@ -186,32 +223,20 @@ function init(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function updateTransactionTagSelected(e: Event): void {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const transactionTagId = target.value;
|
||||
const transactionTag = transactionTagsStore.allTransactionTagsMap[transactionTagId];
|
||||
function updateCurrentTransactionTagState(state: number): void {
|
||||
filterTagIds.value[currentTransactionTagId.value] = state;
|
||||
showTagFilterStatePopover.value = false;
|
||||
currentTransactionTagId.value = '';
|
||||
}
|
||||
|
||||
if (!transactionTag) {
|
||||
return;
|
||||
function setAllToState(onlyVisible: boolean, value: TransactionTagFilterState): void {
|
||||
for (const tag of allTags.value) {
|
||||
if (onlyVisible && !showHidden.value && tag.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filterTagIds.value[tag.id] = value;
|
||||
}
|
||||
|
||||
filterTagIds.value[transactionTag.id] = !target.checked;
|
||||
}
|
||||
|
||||
function selectAllTransactionTags(): void {
|
||||
selectAll(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
|
||||
}
|
||||
|
||||
function selectNoneTransactionTags(): void {
|
||||
selectNone(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
|
||||
}
|
||||
|
||||
function selectInvertTransactionTags(): void {
|
||||
selectInvert(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
|
||||
}
|
||||
|
||||
function selectAllVisibleTransactionTags(): void {
|
||||
selectAllVisible(filterTagIds.value, transactionTagsStore.allTransactionTagsMap);
|
||||
}
|
||||
|
||||
function save(): void {
|
||||
|
||||
@@ -62,7 +62,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 || query.tagIds }"></f7-icon>
|
||||
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter || query.tagFilter }"></f7-icon>
|
||||
</f7-link>
|
||||
</f7-toolbar>
|
||||
|
||||
@@ -509,52 +509,37 @@
|
||||
<f7-list-item group-title>
|
||||
<small>{{ tt('Tags') }}</small>
|
||||
</f7-list-item>
|
||||
<f7-list-item :class="{ 'list-item-selected': !query.tagIds }" :title="tt('All')" @click="changeTagFilter('')">
|
||||
<f7-list-item :class="{ 'list-item-selected': !query.tagFilter }" :title="tt('All')" @click="changeTagFilter('')">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.tagIds"></f7-icon>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.tagFilter"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :class="{ 'list-item-selected': query.tagIds === 'none' }" :title="tt('Without Tags')" @click="changeTagFilter('none')">
|
||||
<f7-list-item :class="{ 'list-item-selected': query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue }" :title="tt('Without Tags')" @click="changeTagFilter(TransactionTagFilter.TransactionNoTagFilterValue)">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagIds === 'none'"></f7-icon>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagFilter === TransactionTagFilter.TransactionNoTagFilterValue"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :class="{ 'list-item-selected': query.tagIds && queryAllFilterTagIdsCount > 1 }"
|
||||
<f7-list-item :class="{ 'list-item-selected': query.tagFilter && queryAllFilterTagIdsCount > 1 }"
|
||||
:title="tt('Multiple Tags')" @click="filterMultipleTags()" v-if="allAvailableTagsCount > 0">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagIds && queryAllFilterTagIdsCount > 1"></f7-icon>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.tagFilter && queryAllFilterTagIdsCount > 1"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
|
||||
<template v-if="query.tagIds && query.tagIds !== 'none'">
|
||||
<f7-list-item :title="filterType.displayName"
|
||||
:key="filterType.type"
|
||||
v-for="filterType in allTransactionTagFilterTypes"
|
||||
@click="changeTagFilterType(filterType.type)"
|
||||
>
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon"
|
||||
f7="checkmark_alt"
|
||||
v-if="query.tagFilterType === filterType.type">
|
||||
</f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</template>
|
||||
|
||||
<f7-list-item :title="transactionTag.name"
|
||||
:class="{ 'list-item-selected': query.tagIds === transactionTag.id, 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && queryAllFilterTagIds[transactionTag.id] }"
|
||||
:class="{ 'list-item-selected': queryAllFilterTagIdsCount === 1 && isDefined(queryAllFilterTagIds[transactionTag.id]), 'item-in-multiple-selection': queryAllFilterTagIdsCount > 1 && isDefined(queryAllFilterTagIds[transactionTag.id]) }"
|
||||
:key="transactionTag.id"
|
||||
v-for="transactionTag in allTransactionTags"
|
||||
v-show="!transactionTag.hidden || query.tagIds === transactionTag.id"
|
||||
@click="changeTagFilter(transactionTag.id)"
|
||||
v-show="!transactionTag.hidden || isDefined(queryAllFilterTagIds[transactionTag.id])"
|
||||
@click="changeTagFilter(TransactionTagFilter.of(transactionTag.id).toTextualTagFilter())"
|
||||
>
|
||||
<template #before-title>
|
||||
<f7-icon class="transaction-tag-name transaction-tag-icon" f7="number"></f7-icon>
|
||||
</template>
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon"
|
||||
f7="checkmark_alt"
|
||||
v-if="query.tagIds === transactionTag.id">
|
||||
:f7="queryAllFilterTagIds[transactionTag.id] === true ? 'checkmark_alt' : (queryAllFilterTagIds[transactionTag.id] === false ? 'multiply' : undefined)"
|
||||
v-if="isDefined(queryAllFilterTagIds[transactionTag.id])">
|
||||
</f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
@@ -597,7 +582,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
||||
import { type TransactionMonthList, useTransactionsStore } from '@/stores/transaction.ts';
|
||||
|
||||
import { type TypeAndDisplayName, keys } from '@/core/base.ts';
|
||||
import { keys } from '@/core/base.ts';
|
||||
import { TextDirection } from '@/core/text.ts';
|
||||
import {
|
||||
type TextualYearMonth,
|
||||
@@ -609,10 +594,12 @@ import {
|
||||
import { AmountFilterType } from '@/core/numeral.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||
import type { Transaction } from '@/models/transaction.ts';
|
||||
import { type Transaction, TransactionTagFilter } from '@/models/transaction.ts';
|
||||
|
||||
import {
|
||||
isNumber
|
||||
isDefined,
|
||||
isNumber,
|
||||
objectFieldWithValueToArrayItem
|
||||
} from '@/lib/common.ts';
|
||||
import {
|
||||
getCurrentUnixTime,
|
||||
@@ -644,7 +631,6 @@ const props = defineProps<{
|
||||
const {
|
||||
tt,
|
||||
getCurrentLanguageTextDirection,
|
||||
getAllTransactionTagFilterTypes,
|
||||
getWeekdayShortName,
|
||||
getCalendarDisplayDayOfMonthFromUnixTime
|
||||
} = useI18n();
|
||||
@@ -723,8 +709,6 @@ const showDeleteActionSheet = ref<boolean>(false);
|
||||
const textDirection = computed<TextDirection>(() => getCurrentLanguageTextDirection());
|
||||
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
|
||||
|
||||
const allTransactionTagFilterTypes = computed<TypeAndDisplayName[]>(() => getAllTransactionTagFilterTypes());
|
||||
|
||||
const transactions = computed<TransactionMonthList[]>(() => {
|
||||
if (loading.value) {
|
||||
return [];
|
||||
@@ -925,8 +909,7 @@ function init(): void {
|
||||
type: initQuery['type'] && parseInt(initQuery['type']) > 0 ? parseInt(initQuery['type']) : undefined,
|
||||
categoryIds: initQuery['categoryIds'],
|
||||
accountIds: initQuery['accountIds'],
|
||||
tagIds: initQuery['tagIds'],
|
||||
tagFilterType: initQuery['tagFilterType'] && parseInt(initQuery['tagFilterType']) >= 0 ? parseInt(initQuery['tagFilterType']) : undefined,
|
||||
tagFilter: initQuery['tagFilter'],
|
||||
keyword: initQuery['keyword']
|
||||
});
|
||||
|
||||
@@ -1277,13 +1260,13 @@ function filterMultipleAccounts(): void {
|
||||
props.f7router.navigate('/settings/filter/account?type=transactionListCurrent');
|
||||
}
|
||||
|
||||
function changeTagFilter(tagIds: string): void {
|
||||
if (query.value.tagIds === tagIds) {
|
||||
function changeTagFilter(tagFilter: string): void {
|
||||
if (query.value.tagFilter === tagFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = transactionsStore.updateTransactionListFilter({
|
||||
tagIds: tagIds
|
||||
tagFilter: tagFilter
|
||||
});
|
||||
|
||||
showMorePopover.value = false;
|
||||
@@ -1297,22 +1280,6 @@ function filterMultipleTags(): void {
|
||||
props.f7router.navigate('/settings/filter/tag?type=transactionListCurrent');
|
||||
}
|
||||
|
||||
function changeTagFilterType(filterType: number): void {
|
||||
if (query.value.tagFilterType === filterType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = transactionsStore.updateTransactionListFilter({
|
||||
tagFilterType: filterType
|
||||
});
|
||||
|
||||
showMorePopover.value = false;
|
||||
|
||||
if (changed) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
function changeKeywordFilter(keyword: string): void {
|
||||
if (query.value.keyword === keyword) {
|
||||
return;
|
||||
@@ -1383,8 +1350,8 @@ function add(): void {
|
||||
params.push(`accountId=${query.value.accountIds}`);
|
||||
}
|
||||
|
||||
if (query.value.tagIds) {
|
||||
params.push(`tagIds=${query.value.tagIds}`);
|
||||
if (query.value.tagFilter) {
|
||||
params.push(`tagIds=${objectFieldWithValueToArrayItem(queryAllFilterTagIds.value, true).join(',') || ''}`);
|
||||
}
|
||||
|
||||
props.f7router.navigate(`/transaction/add?${params.join('&')}`);
|
||||
|
||||
Reference in New Issue
Block a user