tag filter supports selecting both included and excluded tags simultaneously

This commit is contained in:
MaysWind
2025-11-24 02:12:44 +08:00
parent 45be96cf68
commit 6430a52027
45 changed files with 1151 additions and 706 deletions
@@ -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 {