mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
add search box in transaction category page / dialog
This commit is contained in:
+33
-73
@@ -1,8 +1,7 @@
|
|||||||
import { itemAndIndex, reversed, entries, keys, values } from '@/core/base.ts';
|
import { reversed, keys, values } from '@/core/base.ts';
|
||||||
import { type LocalizedPresetCategory, CategoryType } from '@/core/category.ts';
|
import { type LocalizedPresetCategory, CategoryType } from '@/core/category.ts';
|
||||||
import { TransactionType } from '@/core/transaction.ts';
|
import { TransactionType } from '@/core/transaction.ts';
|
||||||
import {
|
import {
|
||||||
type TransactionCategoriesWithVisibleCount,
|
|
||||||
type TransactionCategoryCreateRequest,
|
type TransactionCategoryCreateRequest,
|
||||||
type TransactionCategoryCreateWithSubCategories,
|
type TransactionCategoryCreateWithSubCategories,
|
||||||
TransactionCategory
|
TransactionCategory
|
||||||
@@ -133,17 +132,20 @@ export function getTransactionSecondaryCategoryName(categoryId: string | null |
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allTransactionCategoriesWithVisibleCount(allTransactionCategories: Record<number, TransactionCategory[]>, allowCategoryTypes?: Record<number, boolean>): Record<number, TransactionCategoriesWithVisibleCount> {
|
export function filterTransactionCategories(allTransactionCategories: Record<number, TransactionCategory[]>, allowCategoryTypes?: Record<number, boolean>, allowCategoryName?: string, showHidden?: boolean): Record<number, TransactionCategory[]> {
|
||||||
const ret: Record<string, TransactionCategoriesWithVisibleCount> = {};
|
const ret: Record<string, TransactionCategory[]> = {};
|
||||||
const hasAllowCategoryTypes = allowCategoryTypes
|
const hasAllowCategoryTypes = allowCategoryTypes
|
||||||
&& (allowCategoryTypes[CategoryType.Income]
|
&& (allowCategoryTypes[CategoryType.Income]
|
||||||
|| allowCategoryTypes[CategoryType.Expense]
|
|| allowCategoryTypes[CategoryType.Expense]
|
||||||
|| allowCategoryTypes[CategoryType.Transfer]);
|
|| allowCategoryTypes[CategoryType.Transfer]);
|
||||||
|
|
||||||
const allCategoryTypes = [ CategoryType.Income, CategoryType.Expense, CategoryType.Transfer ];
|
const allCategoryTypes = [ CategoryType.Income, CategoryType.Expense, CategoryType.Transfer ];
|
||||||
|
const lowercaseFilterContent = allowCategoryName ? allowCategoryName.toLowerCase() : '';
|
||||||
|
|
||||||
for (const categoryType of allCategoryTypes) {
|
for (const categoryType of allCategoryTypes) {
|
||||||
if (!allTransactionCategories[categoryType]) {
|
const allCategories = allTransactionCategories[categoryType];
|
||||||
|
|
||||||
|
if (!allCategories || allCategories.length < 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,53 +153,41 @@ export function allTransactionCategoriesWithVisibleCount(allTransactionCategorie
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allCategories: TransactionCategory[] = allTransactionCategories[categoryType];
|
const allFilteredCategories: TransactionCategory[] = [];
|
||||||
const allSubCategories: Record<string, TransactionCategory[]> = {};
|
|
||||||
const allVisibleSubCategoryCounts: Record<string, number> = {};
|
|
||||||
const allFirstVisibleSubCategoryIndexes: Record<string, number> = {};
|
|
||||||
let allVisibleCategoryCount = 0;
|
|
||||||
let firstVisibleCategoryIndex = -1;
|
|
||||||
|
|
||||||
for (const [category, cagtegoryIndex] of itemAndIndex(allCategories)) {
|
for (const category of allCategories) {
|
||||||
if (!category.hidden) {
|
if (!showHidden && category.hidden) {
|
||||||
allVisibleCategoryCount++;
|
continue;
|
||||||
|
|
||||||
if (firstVisibleCategoryIndex === -1) {
|
|
||||||
firstVisibleCategoryIndex = cagtegoryIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const categoryMatchesName = !lowercaseFilterContent || category.name.toLowerCase().includes(lowercaseFilterContent);
|
||||||
|
const filteredSubCategories: TransactionCategory[] = [];
|
||||||
|
|
||||||
if (category.subCategories) {
|
if (category.subCategories) {
|
||||||
let visibleSubCategoryCount = 0;
|
for (const subCategory of category.subCategories) {
|
||||||
let firstVisibleSubCategoryIndex = -1;
|
if (!showHidden && subCategory.hidden) {
|
||||||
|
continue;
|
||||||
for (const [subCategory, subCategoryIndex] of itemAndIndex(category.subCategories)) {
|
|
||||||
if (!subCategory.hidden) {
|
|
||||||
visibleSubCategoryCount++;
|
|
||||||
|
|
||||||
if (firstVisibleSubCategoryIndex === -1) {
|
|
||||||
firstVisibleSubCategoryIndex = subCategoryIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (category.subCategories.length > 0) {
|
if (!categoryMatchesName && lowercaseFilterContent && !subCategory.name.toLowerCase().includes(lowercaseFilterContent)) {
|
||||||
allSubCategories[category.id] = category.subCategories;
|
continue;
|
||||||
allVisibleSubCategoryCounts[category.id] = visibleSubCategoryCount;
|
}
|
||||||
allFirstVisibleSubCategoryIndexes[category.id] = firstVisibleSubCategoryIndex;
|
|
||||||
|
const filteredSubCategory = subCategory.clone();
|
||||||
|
filteredSubCategories.push(filteredSubCategory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!categoryMatchesName && filteredSubCategories.length < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredCategory = category.clone();
|
||||||
|
filteredCategory.subCategories = filteredSubCategories;
|
||||||
|
allFilteredCategories.push(filteredCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[`${categoryType}`] = {
|
ret[`${categoryType}`] = allFilteredCategories;
|
||||||
type: categoryType,
|
|
||||||
allCategories: allCategories,
|
|
||||||
allVisibleCategoryCount: allVisibleCategoryCount,
|
|
||||||
firstVisibleCategoryIndex: firstVisibleCategoryIndex,
|
|
||||||
allSubCategories: allSubCategories,
|
|
||||||
allVisibleSubCategoryCounts: allVisibleSubCategoryCounts,
|
|
||||||
allFirstVisibleSubCategoryIndexes: allFirstVisibleSubCategoryIndexes
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -274,7 +264,7 @@ export function isSubCategoryIdAvailable(categories: TransactionCategory[], cate
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFirstAvailableCategoryId(categories?: TransactionCategory[]): string {
|
export function getFirstVisibleCategoryId(categories?: TransactionCategory[]): string {
|
||||||
if (!categories || !categories.length) {
|
if (!categories || !categories.length) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -374,36 +364,6 @@ export function getLastShowingId(categories: TransactionCategory[], showHidden:
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function containsAnyAvailableCategory(allTransactionCategories: Record<string, TransactionCategoriesWithVisibleCount>, showHidden: boolean): boolean {
|
|
||||||
for (const categoryType of values(allTransactionCategories)) {
|
|
||||||
if (showHidden) {
|
|
||||||
if (categoryType.allCategories && categoryType.allCategories.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (categoryType.allVisibleCategoryCount > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function containsAvailableCategory(allTransactionCategories: Record<string, TransactionCategoriesWithVisibleCount>, showHidden: boolean): Record<number, boolean> {
|
|
||||||
const result: Record<number, boolean> = {};
|
|
||||||
|
|
||||||
for (const [type, categoryType] of entries(allTransactionCategories)) {
|
|
||||||
if (showHidden) {
|
|
||||||
result[parseInt(type)] = categoryType.allCategories && categoryType.allCategories.length > 0;
|
|
||||||
} else {
|
|
||||||
result[parseInt(type)] = categoryType.allVisibleCategoryCount > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectAllSubCategories(filterCategoryIds: Record<string, boolean>, value: boolean, category?: TransactionCategory): void {
|
export function selectAllSubCategories(filterCategoryIds: Record<string, boolean>, value: boolean, category?: TransactionCategory): void {
|
||||||
if (!category || !category.subCategories || !category.subCategories.length) {
|
if (!category || !category.subCategories || !category.subCategories.length) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
categoryTypeToTransactionType,
|
categoryTypeToTransactionType,
|
||||||
isSubCategoryIdAvailable,
|
isSubCategoryIdAvailable,
|
||||||
getFirstAvailableCategoryId,
|
getFirstVisibleCategoryId,
|
||||||
getFirstAvailableSubCategoryId
|
getFirstAvailableSubCategoryId
|
||||||
} from './category.ts';
|
} from './category.ts';
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ export function setTransactionModelByTransaction(transaction: Transaction, trans
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!transaction.expenseCategoryId) {
|
if (!transaction.expenseCategoryId) {
|
||||||
transaction.expenseCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Expense]);
|
transaction.expenseCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Expense]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export function setTransactionModelByTransaction(transaction: Transaction, trans
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!transaction.incomeCategoryId) {
|
if (!transaction.incomeCategoryId) {
|
||||||
transaction.incomeCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Income]);
|
transaction.incomeCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Income]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export function setTransactionModelByTransaction(transaction: Transaction, trans
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!transaction.transferCategoryId) {
|
if (!transaction.transferCategoryId) {
|
||||||
transaction.transferCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Transfer]);
|
transaction.transferCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Transfer]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,20 @@ export class TransactionCategory implements TransactionCategoryInfoResponse {
|
|||||||
this.visible = other.visible;
|
this.visible = other.visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public clone(): TransactionCategory {
|
||||||
|
return new TransactionCategory(
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.parentId,
|
||||||
|
this.type,
|
||||||
|
this.icon,
|
||||||
|
this.color,
|
||||||
|
this.comment,
|
||||||
|
this.displayOrder,
|
||||||
|
this.visible
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public toCreateRequest(clientSessionId: string): TransactionCategoryCreateRequest {
|
public toCreateRequest(clientSessionId: string): TransactionCategoryCreateRequest {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
@@ -217,13 +231,3 @@ export interface TransactionCategoryInfoResponse {
|
|||||||
readonly hidden: boolean;
|
readonly hidden: boolean;
|
||||||
readonly subCategories?: TransactionCategoryInfoResponse[];
|
readonly subCategories?: TransactionCategoryInfoResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionCategoriesWithVisibleCount {
|
|
||||||
readonly type: number;
|
|
||||||
readonly allCategories: TransactionCategory[];
|
|
||||||
readonly allVisibleCategoryCount: number;
|
|
||||||
readonly firstVisibleCategoryIndex: number;
|
|
||||||
readonly allSubCategories: Record<string, TransactionCategory[]>;
|
|
||||||
readonly allVisibleSubCategoryCounts: Record<string, number>;
|
|
||||||
readonly allFirstVisibleSubCategoryIndexes: Record<string, number>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ import {
|
|||||||
} from '@/lib/datetime.ts';
|
} from '@/lib/datetime.ts';
|
||||||
import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts';
|
import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts';
|
||||||
import { getCurrencyFraction } from '@/lib/currency.ts';
|
import { getCurrencyFraction } from '@/lib/currency.ts';
|
||||||
import { getFirstAvailableCategoryId } from '@/lib/category.ts';
|
import { getFirstVisibleCategoryId } from '@/lib/category.ts';
|
||||||
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
||||||
import logger from '@/lib/logger.ts';
|
import logger from '@/lib/logger.ts';
|
||||||
|
|
||||||
@@ -499,19 +499,19 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
|||||||
|
|
||||||
if (allCategories) {
|
if (allCategories) {
|
||||||
if (transaction.type === TransactionType.Expense) {
|
if (transaction.type === TransactionType.Expense) {
|
||||||
const defaultCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Expense]);
|
const defaultCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Expense]);
|
||||||
|
|
||||||
if (transaction.expenseCategoryId && transaction.expenseCategoryId !== '0' && transaction.expenseCategoryId !== defaultCategoryId && transaction.expenseCategoryId !== initCategoryId) {
|
if (transaction.expenseCategoryId && transaction.expenseCategoryId !== '0' && transaction.expenseCategoryId !== defaultCategoryId && transaction.expenseCategoryId !== initCategoryId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (transaction.type === TransactionType.Income) {
|
} else if (transaction.type === TransactionType.Income) {
|
||||||
const defaultCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Income]);
|
const defaultCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Income]);
|
||||||
|
|
||||||
if (transaction.incomeCategoryId && transaction.incomeCategoryId !== '0' && transaction.incomeCategoryId !== defaultCategoryId && transaction.incomeCategoryId !== initCategoryId) {
|
if (transaction.incomeCategoryId && transaction.incomeCategoryId !== '0' && transaction.incomeCategoryId !== defaultCategoryId && transaction.incomeCategoryId !== initCategoryId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (transaction.type === TransactionType.Transfer) {
|
} else if (transaction.type === TransactionType.Transfer) {
|
||||||
const defaultCategoryId = getFirstAvailableCategoryId(allCategories[CategoryType.Transfer]);
|
const defaultCategoryId = getFirstVisibleCategoryId(allCategories[CategoryType.Transfer]);
|
||||||
|
|
||||||
if (transaction.transferCategoryId && transaction.transferCategoryId !== '0' && transaction.transferCategoryId !== defaultCategoryId && transaction.transferCategoryId !== initCategoryId) {
|
if (transaction.transferCategoryId && transaction.transferCategoryId !== '0' && transaction.transferCategoryId !== defaultCategoryId && transaction.transferCategoryId !== initCategoryId) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from '@/models/transaction_category.ts';
|
} from '@/models/transaction_category.ts';
|
||||||
|
|
||||||
import { isEquals } from '@/lib/common.ts';
|
import { isEquals } from '@/lib/common.ts';
|
||||||
import { getFirstAvailableCategoryId } from '@/lib/category.ts';
|
import { getFirstVisibleCategoryId } from '@/lib/category.ts';
|
||||||
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
||||||
import logger from '@/lib/logger.ts';
|
import logger from '@/lib/logger.ts';
|
||||||
|
|
||||||
@@ -23,31 +23,55 @@ export const useTransactionCategoriesStore = defineStore('transactionCategories'
|
|||||||
const allTransactionCategoriesMap = ref<Record<string, TransactionCategory>>({});
|
const allTransactionCategoriesMap = ref<Record<string, TransactionCategory>>({});
|
||||||
const transactionCategoryListStateInvalid = ref<boolean>(true);
|
const transactionCategoryListStateInvalid = ref<boolean>(true);
|
||||||
|
|
||||||
const hasAvailableExpenseCategories = computed<boolean>(() => {
|
const allAvailablePrimaryCategoriesCount = computed<number>(() => {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const categories of values(allTransactionCategories.value)) {
|
||||||
|
count += categories.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
|
||||||
|
const allAvailableSecondaryCategoriesCount = computed<number>(() => {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const categories of values(allTransactionCategories.value)) {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.subCategories) {
|
||||||
|
count += category.subCategories.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasVisibleExpenseCategories = computed<boolean>(() => {
|
||||||
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Expense] || !allTransactionCategories.value[CategoryType.Expense].length) {
|
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Expense] || !allTransactionCategories.value[CategoryType.Expense].length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(allTransactionCategories.value[CategoryType.Expense]);
|
const firstVisibleCategoryId = getFirstVisibleCategoryId(allTransactionCategories.value[CategoryType.Expense]);
|
||||||
return firstAvailableCategoryId !== '';
|
return firstVisibleCategoryId !== '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAvailableIncomeCategories = computed<boolean>(() => {
|
const hasVisibleIncomeCategories = computed<boolean>(() => {
|
||||||
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Income] || !allTransactionCategories.value[CategoryType.Income].length) {
|
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Income] || !allTransactionCategories.value[CategoryType.Income].length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(allTransactionCategories.value[CategoryType.Income]);
|
const firstVisibleCategoryId = getFirstVisibleCategoryId(allTransactionCategories.value[CategoryType.Income]);
|
||||||
return firstAvailableCategoryId !== '';
|
return firstVisibleCategoryId !== '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAvailableTransferCategories = computed<boolean>(() => {
|
const hasVisibleTransferCategories = computed<boolean>(() => {
|
||||||
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Transfer] || !allTransactionCategories.value[CategoryType.Transfer].length) {
|
if (!allTransactionCategories.value || !allTransactionCategories.value[CategoryType.Transfer] || !allTransactionCategories.value[CategoryType.Transfer].length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(allTransactionCategories.value[CategoryType.Transfer]);
|
const firstVisibleCategoryId = getFirstVisibleCategoryId(allTransactionCategories.value[CategoryType.Transfer]);
|
||||||
return firstAvailableCategoryId !== '';
|
return firstVisibleCategoryId !== '';
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadTransactionCategoryList(allCategories: Record<number, TransactionCategory[]>): void {
|
function loadTransactionCategoryList(allCategories: Record<number, TransactionCategory[]>): void {
|
||||||
@@ -548,9 +572,11 @@ export const useTransactionCategoriesStore = defineStore('transactionCategories'
|
|||||||
allTransactionCategoriesMap,
|
allTransactionCategoriesMap,
|
||||||
transactionCategoryListStateInvalid,
|
transactionCategoryListStateInvalid,
|
||||||
// computed states
|
// computed states
|
||||||
hasAvailableExpenseCategories,
|
allAvailablePrimaryCategoriesCount,
|
||||||
hasAvailableIncomeCategories,
|
allAvailableSecondaryCategoriesCount,
|
||||||
hasAvailableTransferCategories,
|
hasVisibleExpenseCategories,
|
||||||
|
hasVisibleIncomeCategories,
|
||||||
|
hasVisibleTransferCategories,
|
||||||
// functions
|
// functions
|
||||||
updateTransactionCategoryListInvalidState,
|
updateTransactionCategoryListInvalidState,
|
||||||
resetTransactionCategories,
|
resetTransactionCategories,
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import { useOverviewStore } from '@/stores/overview.ts';
|
|||||||
|
|
||||||
import { keys, keysIfValueEquals, values } from '@/core/base.ts';
|
import { keys, keysIfValueEquals, values } from '@/core/base.ts';
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { CategoryType } from '@/core/category.ts';
|
||||||
import type { TransactionCategory, TransactionCategoriesWithVisibleCount } from '@/models/transaction_category.ts';
|
import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
arrayItemToObjectField
|
arrayItemToObjectField
|
||||||
} from '@/lib/common.ts';
|
} from '@/lib/common.ts';
|
||||||
import {
|
import {
|
||||||
allTransactionCategoriesWithVisibleCount,
|
filterTransactionCategories,
|
||||||
containsAnyAvailableCategory,
|
|
||||||
containsAvailableCategory,
|
|
||||||
selectAllSubCategories,
|
selectAllSubCategories,
|
||||||
isCategoryOrSubCategoriesAllChecked
|
isCategoryOrSubCategoriesAllChecked
|
||||||
} from '@/lib/category.ts';
|
} from '@/lib/category.ts';
|
||||||
@@ -38,6 +36,7 @@ export function useCategoryFilterSettingPageBase(type?: CategoryFilterType, allo
|
|||||||
|
|
||||||
const loading = ref<boolean>(true);
|
const loading = ref<boolean>(true);
|
||||||
const showHidden = ref<boolean>(false);
|
const showHidden = ref<boolean>(false);
|
||||||
|
const filterContent = ref<string>('');
|
||||||
const filterCategoryIds = ref<Record<string, boolean>>({});
|
const filterCategoryIds = ref<Record<string, boolean>>({});
|
||||||
|
|
||||||
const title = computed<string>(() => {
|
const title = computed<string>(() => {
|
||||||
@@ -56,10 +55,34 @@ export function useCategoryFilterSettingPageBase(type?: CategoryFilterType, allo
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const allTransactionCategories = computed<Record<string, TransactionCategoriesWithVisibleCount>>(() => allTransactionCategoriesWithVisibleCount(transactionCategoriesStore.allTransactionCategories, allowCategoryTypes));
|
const allVisibleTransactionCategories = computed<Record<string, TransactionCategory[]>>(() => filterTransactionCategories(transactionCategoriesStore.allTransactionCategories, allowCategoryTypes, filterContent.value, showHidden.value));
|
||||||
const hasAnyAvailableCategory = computed<boolean>(() => containsAnyAvailableCategory(allTransactionCategories.value, true));
|
const allVisibleTransactionCategoryMap = computed<Record<string, TransactionCategory>>(() => {
|
||||||
const hasAnyVisibleCategory = computed<boolean>(() => containsAnyAvailableCategory(allTransactionCategories.value, showHidden.value));
|
const categoryMap: Record<string, TransactionCategory> = {};
|
||||||
const hasAvailableCategory = computed<Record<number, boolean>>(() => containsAvailableCategory(allTransactionCategories.value, showHidden.value));
|
|
||||||
|
for (const categories of values(allVisibleTransactionCategories.value)) {
|
||||||
|
for (const category of categories) {
|
||||||
|
categoryMap[category.id] = category;
|
||||||
|
|
||||||
|
if (category.subCategories) {
|
||||||
|
for (const subCategory of category.subCategories) {
|
||||||
|
categoryMap[subCategory.id] = subCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryMap;
|
||||||
|
});
|
||||||
|
const hasAnyAvailableCategory = computed<boolean>(() => transactionCategoriesStore.allAvailablePrimaryCategoriesCount > 0 || transactionCategoriesStore.allAvailableSecondaryCategoriesCount > 0);
|
||||||
|
const hasAnyVisibleCategory = computed<boolean>(() => {
|
||||||
|
for (const categories of values(allVisibleTransactionCategories.value)) {
|
||||||
|
if (categories.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
function isCategoryChecked(category: TransactionCategory, filterCategoryIds: Record<string, boolean>): boolean {
|
function isCategoryChecked(category: TransactionCategory, filterCategoryIds: Record<string, boolean>): boolean {
|
||||||
return !filterCategoryIds[category.id];
|
return !filterCategoryIds[category.id];
|
||||||
@@ -171,14 +194,15 @@ export function useCategoryFilterSettingPageBase(type?: CategoryFilterType, allo
|
|||||||
// states
|
// states
|
||||||
loading,
|
loading,
|
||||||
showHidden,
|
showHidden,
|
||||||
|
filterContent,
|
||||||
filterCategoryIds,
|
filterCategoryIds,
|
||||||
// computed states
|
// computed states
|
||||||
title,
|
title,
|
||||||
applyText,
|
applyText,
|
||||||
allTransactionCategories,
|
allVisibleTransactionCategories,
|
||||||
|
allVisibleTransactionCategoryMap,
|
||||||
hasAnyAvailableCategory,
|
hasAnyAvailableCategory,
|
||||||
hasAnyVisibleCategory,
|
hasAnyVisibleCategory,
|
||||||
hasAvailableCategory,
|
|
||||||
// functions
|
// functions
|
||||||
isCategoryChecked,
|
isCategoryChecked,
|
||||||
getCategoryTypeName,
|
getCategoryTypeName,
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
|
|||||||
const allTagsMap = computed<Record<string, TransactionTag>>(() => transactionTagsStore.allTransactionTagsMap);
|
const allTagsMap = computed<Record<string, TransactionTag>>(() => transactionTagsStore.allTransactionTagsMap);
|
||||||
const firstVisibleAccountId = computed<string | undefined>(() => allVisibleAccounts.value && allVisibleAccounts.value[0] ? allVisibleAccounts.value[0].id : undefined);
|
const firstVisibleAccountId = computed<string | undefined>(() => allVisibleAccounts.value && allVisibleAccounts.value[0] ? allVisibleAccounts.value[0].id : undefined);
|
||||||
|
|
||||||
const hasAvailableExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableExpenseCategories);
|
const hasVisibleExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleExpenseCategories);
|
||||||
const hasAvailableIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableIncomeCategories);
|
const hasVisibleIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleIncomeCategories);
|
||||||
const hasAvailableTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableTransferCategories);
|
const hasVisibleTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleTransferCategories);
|
||||||
|
|
||||||
const canAddTransactionPicture = computed<boolean>(() => {
|
const canAddTransactionPicture = computed<boolean>(() => {
|
||||||
if (type !== TransactionEditPageType.Transaction || (mode.value !== TransactionEditPageMode.Add && mode.value !== TransactionEditPageMode.Edit)) {
|
if (type !== TransactionEditPageType.Transaction || (mode.value !== TransactionEditPageMode.Add && mode.value !== TransactionEditPageMode.Edit)) {
|
||||||
@@ -438,9 +438,9 @@ export function useTransactionEditPageBase(type: TransactionEditPageType, initMo
|
|||||||
allTags,
|
allTags,
|
||||||
allTagsMap,
|
allTagsMap,
|
||||||
firstVisibleAccountId,
|
firstVisibleAccountId,
|
||||||
hasAvailableExpenseCategories,
|
hasVisibleExpenseCategories,
|
||||||
hasAvailableIncomeCategories,
|
hasVisibleIncomeCategories,
|
||||||
hasAvailableTransferCategories,
|
hasVisibleTransferCategories,
|
||||||
canAddTransactionPicture,
|
canAddTransactionPicture,
|
||||||
title,
|
title,
|
||||||
saveButtonTitle,
|
saveButtonTitle,
|
||||||
|
|||||||
@@ -1,79 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :class="{ 'pa-sm-1 pa-md-2': dialogMode }">
|
<v-card :class="{ 'pa-sm-1 pa-md-2': dialogMode }">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="d-flex align-center justify-center" v-if="dialogMode">
|
<v-row>
|
||||||
<div class="w-100 text-center">
|
<v-col cols="6">
|
||||||
<h4 class="text-h4">{{ tt(title) }}</h4>
|
<div :class="{ 'text-h4': dialogMode, 'text-wrap': true }">
|
||||||
</div>
|
{{ tt(title) }}
|
||||||
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
|
</div>
|
||||||
:disabled="loading || !hasAnyAvailableCategory" :icon="true">
|
</v-col>
|
||||||
<v-icon :icon="mdiDotsVertical" />
|
<v-col cols="6" class="d-flex align-center">
|
||||||
<v-menu activator="parent">
|
<v-spacer v-if="!dialogMode"/>
|
||||||
<v-list>
|
<v-text-field density="compact" :disabled="loading || !hasAnyAvailableCategory"
|
||||||
<v-list-item :prepend-icon="mdiSelectAll"
|
:prepend-inner-icon="mdiMagnify"
|
||||||
:title="tt('Select All')"
|
:placeholder="tt('Find category')"
|
||||||
:disabled="!hasAnyVisibleCategory"
|
v-model="filterContent"
|
||||||
@click="selectAllCategories"></v-list-item>
|
v-if="dialogMode"></v-text-field>
|
||||||
<v-list-item :prepend-icon="mdiSelect"
|
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
|
||||||
:title="tt('Select None')"
|
:disabled="loading || !hasAnyAvailableCategory" :icon="true">
|
||||||
:disabled="!hasAnyVisibleCategory"
|
<v-icon :icon="mdiDotsVertical" />
|
||||||
@click="selectNoneCategories"></v-list-item>
|
<v-menu activator="parent">
|
||||||
<v-list-item :prepend-icon="mdiSelectInverse"
|
<v-list>
|
||||||
:title="tt('Invert Selection')"
|
<v-list-item :prepend-icon="mdiSelectAll"
|
||||||
:disabled="!hasAnyVisibleCategory"
|
:title="tt('Select All')"
|
||||||
@click="selectInvertCategories"></v-list-item>
|
:disabled="!hasAnyVisibleCategory"
|
||||||
<v-divider class="my-2"/>
|
@click="selectAllCategories"></v-list-item>
|
||||||
<v-list-item :prepend-icon="mdiSelectAll"
|
<v-list-item :prepend-icon="mdiSelect"
|
||||||
:title="tt('Select All Visible')"
|
:title="tt('Select None')"
|
||||||
:disabled="!hasAnyVisibleCategory"
|
:disabled="!hasAnyVisibleCategory"
|
||||||
@click="selectAllVisibleCategories"></v-list-item>
|
@click="selectNoneCategories"></v-list-item>
|
||||||
<v-divider class="my-2"/>
|
<v-list-item :prepend-icon="mdiSelectInverse"
|
||||||
<v-list-item :prepend-icon="mdiEyeOutline"
|
:title="tt('Invert Selection')"
|
||||||
:title="tt('Show Hidden Transaction Categories')"
|
:disabled="!hasAnyVisibleCategory"
|
||||||
v-if="!showHidden" @click="showHidden = true"></v-list-item>
|
@click="selectInvertCategories"></v-list-item>
|
||||||
<v-list-item :prepend-icon="mdiEyeOffOutline"
|
<v-divider class="my-2"/>
|
||||||
:title="tt('Hide Hidden Transaction Categories')"
|
<v-list-item :prepend-icon="mdiEyeOutline"
|
||||||
v-if="showHidden" @click="showHidden = false"></v-list-item>
|
:title="tt('Show Hidden Transaction Categories')"
|
||||||
</v-list>
|
v-if="!showHidden" @click="showHidden = true"></v-list-item>
|
||||||
</v-menu>
|
<v-list-item :prepend-icon="mdiEyeOffOutline"
|
||||||
</v-btn>
|
:title="tt('Hide Hidden Transaction Categories')"
|
||||||
</div>
|
v-if="showHidden" @click="showHidden = false"></v-list-item>
|
||||||
<div class="d-flex align-center" v-else-if="!dialogMode">
|
</v-list>
|
||||||
<span>{{ tt(title) }}</span>
|
</v-menu>
|
||||||
<v-spacer/>
|
</v-btn>
|
||||||
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
|
</v-col>
|
||||||
:disabled="loading" :icon="true">
|
</v-row>
|
||||||
<v-icon :icon="mdiDotsVertical" />
|
|
||||||
<v-menu activator="parent">
|
|
||||||
<v-list>
|
|
||||||
<v-list-item :prepend-icon="mdiSelectAll"
|
|
||||||
:title="tt('Select All')"
|
|
||||||
:disabled="!hasAnyVisibleCategory"
|
|
||||||
@click="selectAllCategories"></v-list-item>
|
|
||||||
<v-list-item :prepend-icon="mdiSelect"
|
|
||||||
:title="tt('Select None')"
|
|
||||||
:disabled="!hasAnyVisibleCategory"
|
|
||||||
@click="selectNoneCategories"></v-list-item>
|
|
||||||
<v-list-item :prepend-icon="mdiSelectInverse"
|
|
||||||
:title="tt('Invert Selection')"
|
|
||||||
:disabled="!hasAnyVisibleCategory"
|
|
||||||
@click="selectInvertCategories"></v-list-item>
|
|
||||||
<v-divider class="my-2"/>
|
|
||||||
<v-list-item :prepend-icon="mdiSelectAll"
|
|
||||||
:title="tt('Select All Visible')"
|
|
||||||
:disabled="!hasAnyVisibleCategory"
|
|
||||||
@click="selectAllVisibleCategories"></v-list-item>
|
|
||||||
<v-divider class="my-2"/>
|
|
||||||
<v-list-item :prepend-icon="mdiEyeOutline"
|
|
||||||
:title="tt('Show Hidden Transaction Categories')"
|
|
||||||
v-if="!showHidden" @click="showHidden = true"></v-list-item>
|
|
||||||
<v-list-item :prepend-icon="mdiEyeOffOutline"
|
|
||||||
:title="tt('Hide Hidden Transaction Categories')"
|
|
||||||
v-if="showHidden" @click="showHidden = false"></v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="loading">
|
<div v-if="loading">
|
||||||
@@ -83,22 +52,22 @@
|
|||||||
|
|
||||||
<v-card-text :class="{ 'flex-grow-1 overflow-y-auto': dialogMode }" v-else-if="!loading">
|
<v-card-text :class="{ 'flex-grow-1 overflow-y-auto': dialogMode }" v-else-if="!loading">
|
||||||
<v-expansion-panels class="category-types" multiple v-model="expandCategoryTypes">
|
<v-expansion-panels class="category-types" multiple v-model="expandCategoryTypes">
|
||||||
<v-expansion-panel :key="transactionType.type"
|
<v-expansion-panel :key="categoryType"
|
||||||
:value="transactionType.type"
|
:value="parseInt(categoryType) as CategoryType"
|
||||||
class="border"
|
class="border"
|
||||||
v-for="transactionType in allTransactionCategories">
|
v-for="(categories, categoryType) in allVisibleTransactionCategories">
|
||||||
<v-expansion-panel-title class="expand-panel-title-with-bg py-0">
|
<v-expansion-panel-title class="expand-panel-title-with-bg py-0">
|
||||||
<span class="ms-3">{{ getCategoryTypeName(transactionType.type) }}</span>
|
<span class="ms-3">{{ getCategoryTypeName(parseInt(categoryType)) }}</span>
|
||||||
</v-expansion-panel-title>
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<v-list rounded density="comfortable" class="pa-0">
|
<v-list rounded density="comfortable" class="pa-0">
|
||||||
<div class="ms-5 py-3" v-if="!hasAvailableCategory[transactionType.type]">{{ tt('No available category') }}</div>
|
<div class="ms-5 py-3" v-if="!categories || !categories.length">{{ tt('No available category') }}</div>
|
||||||
|
|
||||||
<template :key="category.id"
|
<template :key="category.id"
|
||||||
v-for="(category, idx) in transactionType.allCategories">
|
v-for="(category, idx) in categories">
|
||||||
<v-divider v-if="showHidden ? idx > 0 : (!category.hidden ? idx > transactionType.firstVisibleCategoryIndex : false)"/>
|
<v-divider v-if="idx > 0"/>
|
||||||
|
|
||||||
<v-list-item v-if="showHidden || !category.hidden">
|
<v-list-item>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-checkbox :model-value="isSubCategoriesAllChecked(category, filterCategoryIds)"
|
<v-checkbox :model-value="isSubCategoriesAllChecked(category, filterCategoryIds)"
|
||||||
:indeterminate="isSubCategoriesHasButNotAllChecked(category, filterCategoryIds)"
|
:indeterminate="isSubCategoriesHasButNotAllChecked(category, filterCategoryIds)"
|
||||||
@@ -112,15 +81,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
<v-divider v-if="(showHidden || !category.hidden) && ((showHidden && transactionType.allSubCategories[category.id]) || transactionType.allVisibleSubCategoryCounts[category.id])"/>
|
<v-divider v-if="category.subCategories && category.subCategories.length"/>
|
||||||
|
|
||||||
<v-list rounded density="comfortable" class="pa-0 ms-4"
|
<v-list rounded density="comfortable" class="pa-0 ms-4"
|
||||||
v-if="(showHidden || !category.hidden) && ((showHidden && transactionType.allSubCategories[category.id]) || transactionType.allVisibleSubCategoryCounts[category.id])">
|
v-if="category.subCategories && category.subCategories.length">
|
||||||
<template :key="subCategory.id"
|
<template :key="subCategory.id"
|
||||||
v-for="(subCategory, subIdx) in transactionType.allSubCategories[category.id]">
|
v-for="(subCategory, subIdx) in category.subCategories">
|
||||||
<v-divider v-if="showHidden ? subIdx > 0 : (!subCategory.hidden ? subIdx > (transactionType.allFirstVisibleSubCategoryIndexes[category.id] as number) : false)"/>
|
<v-divider v-if="subIdx > 0"/>
|
||||||
|
|
||||||
<v-list-item v-if="showHidden || !subCategory.hidden">
|
<v-list-item>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-checkbox :model-value="isCategoryChecked(subCategory, filterCategoryIds)"
|
<v-checkbox :model-value="isCategoryChecked(subCategory, filterCategoryIds)"
|
||||||
@update:model-value="updateCategorySelected(subCategory, $event)">
|
@update:model-value="updateCategorySelected(subCategory, $event)">
|
||||||
@@ -143,7 +112,7 @@
|
|||||||
|
|
||||||
<v-card-text class="overflow-y-visible" v-if="dialogMode">
|
<v-card-text class="overflow-y-visible" v-if="dialogMode">
|
||||||
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
|
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
|
||||||
<v-btn :disabled="!hasAnyVisibleCategory" @click="save">{{ tt(applyText) }}</v-btn>
|
<v-btn :disabled="!hasAnyAvailableCategory" @click="save">{{ tt(applyText) }}</v-btn>
|
||||||
<v-btn color="secondary" variant="tonal" @click="cancel">{{ tt('Cancel') }}</v-btn>
|
<v-btn color="secondary" variant="tonal" @click="cancel">{{ tt('Cancel') }}</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
@@ -170,7 +139,6 @@ import type { TransactionCategory } from '@/models/transaction_category.ts';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
selectAllSubCategories,
|
selectAllSubCategories,
|
||||||
selectAllVisible,
|
|
||||||
selectAll,
|
selectAll,
|
||||||
selectNone,
|
selectNone,
|
||||||
selectInvert,
|
selectInvert,
|
||||||
@@ -179,6 +147,7 @@ import {
|
|||||||
} from '@/lib/category.ts';
|
} from '@/lib/category.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
mdiMagnify,
|
||||||
mdiSelectAll,
|
mdiSelectAll,
|
||||||
mdiSelect,
|
mdiSelect,
|
||||||
mdiSelectInverse,
|
mdiSelectInverse,
|
||||||
@@ -205,13 +174,14 @@ const { tt } = useI18n();
|
|||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
showHidden,
|
showHidden,
|
||||||
|
filterContent,
|
||||||
filterCategoryIds,
|
filterCategoryIds,
|
||||||
title,
|
title,
|
||||||
applyText,
|
applyText,
|
||||||
allTransactionCategories,
|
allVisibleTransactionCategories,
|
||||||
|
allVisibleTransactionCategoryMap,
|
||||||
hasAnyAvailableCategory,
|
hasAnyAvailableCategory,
|
||||||
hasAnyVisibleCategory,
|
hasAnyVisibleCategory,
|
||||||
hasAvailableCategory,
|
|
||||||
isCategoryChecked,
|
isCategoryChecked,
|
||||||
getCategoryTypeName,
|
getCategoryTypeName,
|
||||||
loadFilterCategoryIds,
|
loadFilterCategoryIds,
|
||||||
@@ -267,7 +237,7 @@ function updateAllSubCategoriesSelected(category: TransactionCategory, value: bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectAllCategories(): void {
|
function selectAllCategories(): void {
|
||||||
selectAll(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectAll(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
|
|
||||||
if (props.autoSave) {
|
if (props.autoSave) {
|
||||||
save();
|
save();
|
||||||
@@ -275,7 +245,7 @@ function selectAllCategories(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectNoneCategories(): void {
|
function selectNoneCategories(): void {
|
||||||
selectNone(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectNone(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
|
|
||||||
if (props.autoSave) {
|
if (props.autoSave) {
|
||||||
save();
|
save();
|
||||||
@@ -283,15 +253,7 @@ function selectNoneCategories(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectInvertCategories(): void {
|
function selectInvertCategories(): void {
|
||||||
selectInvert(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectInvert(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
|
|
||||||
if (props.autoSave) {
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectAllVisibleCategories(): void {
|
|
||||||
selectAllVisible(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
|
||||||
|
|
||||||
if (props.autoSave) {
|
if (props.autoSave) {
|
||||||
save();
|
save();
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableExpenseCategories"
|
:disabled="loading || !hasVisibleExpenseCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Expense])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Expense])"
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableIncomeCategories"
|
:disabled="loading || !hasVisibleIncomeCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Income])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Income])"
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableTransferCategories"
|
:disabled="loading || !hasVisibleTransferCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Transfer])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(newRule.targetId, allCategories[CategoryType.Transfer])"
|
||||||
@@ -285,9 +285,9 @@ const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBala
|
|||||||
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
|
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
|
||||||
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
||||||
|
|
||||||
const hasAvailableExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableExpenseCategories);
|
const hasVisibleExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleExpenseCategories);
|
||||||
const hasAvailableIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableIncomeCategories);
|
const hasVisibleIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleIncomeCategories);
|
||||||
const hasAvailableTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableTransferCategories);
|
const hasVisibleTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleTransferCategories);
|
||||||
|
|
||||||
const sourceItems = computed<NameValue[]>(() => {
|
const sourceItems = computed<NameValue[]>(() => {
|
||||||
switch (newRule.value.dataType) {
|
switch (newRule.value.dataType) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableExpenseCategories"
|
:disabled="loading || !hasVisibleExpenseCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Expense])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Expense])"
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableIncomeCategories"
|
:disabled="loading || !hasVisibleIncomeCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Income])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Income])"
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="loading || !hasAvailableTransferCategories"
|
:disabled="loading || !hasVisibleTransferCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Transfer])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Transfer])"
|
||||||
@@ -275,9 +275,9 @@ const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBala
|
|||||||
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
|
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
|
||||||
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
||||||
|
|
||||||
const hasAvailableExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableExpenseCategories);
|
const hasVisibleExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleExpenseCategories);
|
||||||
const hasAvailableIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableIncomeCategories);
|
const hasVisibleIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleIncomeCategories);
|
||||||
const hasAvailableTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableTransferCategories);
|
const hasVisibleTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleTransferCategories);
|
||||||
|
|
||||||
function getAccountDisplayName(accountId?: string): string {
|
function getAccountDisplayName(accountId?: string): string {
|
||||||
if (accountId) {
|
if (accountId) {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!!disabled || !hasAvailableExpenseCategories"
|
:disabled="!!disabled || !hasVisibleExpenseCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Expense])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Expense])"
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!!disabled || !hasAvailableIncomeCategories"
|
:disabled="!!disabled || !hasVisibleIncomeCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Income])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Income])"
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
|
||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!!disabled || !hasAvailableTransferCategories"
|
:disabled="!!disabled || !hasVisibleTransferCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Transfer])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(item.categoryId, allCategories[CategoryType.Transfer])"
|
||||||
@@ -551,9 +551,9 @@ const allCategoriesMap = computed<Record<string, TransactionCategory>>(() => tra
|
|||||||
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
||||||
const allTagsMap = computed<Record<string, TransactionTag>>(() => transactionTagsStore.allTransactionTagsMap);
|
const allTagsMap = computed<Record<string, TransactionTag>>(() => transactionTagsStore.allTransactionTagsMap);
|
||||||
|
|
||||||
const hasAvailableExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableExpenseCategories);
|
const hasVisibleExpenseCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleExpenseCategories);
|
||||||
const hasAvailableIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableIncomeCategories);
|
const hasVisibleIncomeCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleIncomeCategories);
|
||||||
const hasAvailableTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasAvailableTransferCategories);
|
const hasVisibleTransferCategories = computed<boolean>(() => transactionCategoriesStore.hasVisibleTransferCategories);
|
||||||
|
|
||||||
const isEditing = computed<boolean>(() => !!editingTransaction.value);
|
const isEditing = computed<boolean>(() => !!editingTransaction.value);
|
||||||
const canImport = computed<boolean>(() => selectedImportTransactionCount.value > 0 && selectedInvalidTransactionCount.value < 1);
|
const canImport = computed<boolean>(() => selectedImportTransactionCount.value > 0 && selectedInvalidTransactionCount.value < 1);
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
v-model="transaction.destinationAmount"/>
|
v-model="transaction.destinationAmount"/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Expense">
|
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Expense">
|
||||||
<v-tooltip :disabled="hasAvailableExpenseCategories" :text="hasAvailableExpenseCategories ? '' : tt('No secondary expense categories are available')">
|
<v-tooltip :disabled="hasVisibleExpenseCategories" :text="hasVisibleExpenseCategories ? '' : tt('No secondary expense categories are available')">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<div v-bind="props" class="d-block">
|
<div v-bind="props" class="d-block">
|
||||||
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:readonly="mode === TransactionEditPageMode.View"
|
:readonly="mode === TransactionEditPageMode.View"
|
||||||
:disabled="loading || submitting || !hasAvailableExpenseCategories"
|
:disabled="loading || submitting || !hasVisibleExpenseCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense])"
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Income">
|
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Income">
|
||||||
<v-tooltip :disabled="hasAvailableIncomeCategories" :text="hasAvailableIncomeCategories ? '' : tt('No secondary income categories are available')">
|
<v-tooltip :disabled="hasVisibleIncomeCategories" :text="hasVisibleIncomeCategories ? '' : tt('No secondary income categories are available')">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<div v-bind="props" class="d-block">
|
<div v-bind="props" class="d-block">
|
||||||
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:readonly="mode === TransactionEditPageMode.View"
|
:readonly="mode === TransactionEditPageMode.View"
|
||||||
:disabled="loading || submitting || !hasAvailableIncomeCategories"
|
:disabled="loading || submitting || !hasVisibleIncomeCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income])"
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Transfer">
|
<v-col cols="12" md="12" v-if="transaction.type === TransactionType.Transfer">
|
||||||
<v-tooltip :disabled="hasAvailableTransferCategories" :text="hasAvailableTransferCategories ? '' : tt('No secondary transfer categories are available')">
|
<v-tooltip :disabled="hasVisibleTransferCategories" :text="hasVisibleTransferCategories ? '' : tt('No secondary transfer categories are available')">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<div v-bind="props" class="d-block">
|
<div v-bind="props" class="d-block">
|
||||||
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
<two-column-select primary-key-field="id" primary-value-field="id" primary-title-field="name"
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
|
||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:readonly="mode === TransactionEditPageMode.View"
|
:readonly="mode === TransactionEditPageMode.View"
|
||||||
:disabled="loading || submitting || !hasAvailableTransferCategories"
|
:disabled="loading || submitting || !hasVisibleTransferCategories"
|
||||||
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
:enable-filter="true" :filter-placeholder="tt('Find category')" :filter-no-items-text="tt('No available category')"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer])"
|
||||||
@@ -621,9 +621,9 @@ const {
|
|||||||
allTags,
|
allTags,
|
||||||
allTagsMap,
|
allTagsMap,
|
||||||
firstVisibleAccountId,
|
firstVisibleAccountId,
|
||||||
hasAvailableExpenseCategories,
|
hasVisibleExpenseCategories,
|
||||||
hasAvailableIncomeCategories,
|
hasVisibleIncomeCategories,
|
||||||
hasAvailableTransferCategories,
|
hasVisibleTransferCategories,
|
||||||
canAddTransactionPicture,
|
canAddTransactionPicture,
|
||||||
title,
|
title,
|
||||||
saveButtonTitle,
|
saveButtonTitle,
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<f7-page @page:afterin="onPageAfterIn">
|
<f7-page with-subnavbar @page:beforein="onPageBeforeIn" @page:afterin="onPageAfterIn">
|
||||||
<f7-navbar>
|
<f7-navbar>
|
||||||
<f7-nav-left :back-link="tt('Back')"></f7-nav-left>
|
<f7-nav-left :back-link="tt('Back')"></f7-nav-left>
|
||||||
<f7-nav-title :title="tt(title)"></f7-nav-title>
|
<f7-nav-title :title="tt(title)"></f7-nav-title>
|
||||||
<f7-nav-right class="navbar-compact-icons">
|
<f7-nav-right class="navbar-compact-icons">
|
||||||
<f7-link icon-f7="ellipsis" :class="{ 'disabled': !hasAnyAvailableCategory }" @click="showMoreActionSheet = true"></f7-link>
|
<f7-link icon-f7="ellipsis" :class="{ 'disabled': !hasAnyAvailableCategory }" @click="showMoreActionSheet = true"></f7-link>
|
||||||
<f7-link icon-f7="checkmark_alt" :class="{ 'disabled': !hasAnyVisibleCategory }" @click="save"></f7-link>
|
<f7-link icon-f7="checkmark_alt" :class="{ 'disabled': !hasAnyAvailableCategory }" @click="save"></f7-link>
|
||||||
</f7-nav-right>
|
</f7-nav-right>
|
||||||
|
|
||||||
|
<f7-subnavbar :inner="false">
|
||||||
|
<f7-searchbar
|
||||||
|
custom-searchs
|
||||||
|
:value="filterContent"
|
||||||
|
:placeholder="tt('Find category')"
|
||||||
|
:disable-button-text="tt('Cancel')"
|
||||||
|
@change="filterContent = $event.target.value"
|
||||||
|
></f7-searchbar>
|
||||||
|
</f7-subnavbar>
|
||||||
</f7-navbar>
|
</f7-navbar>
|
||||||
|
|
||||||
<div class="skeleton-text" v-if="loading">
|
<div class="skeleton-text" v-if="loading">
|
||||||
@@ -49,38 +59,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<f7-block class="combination-list-wrapper margin-vertical"
|
<f7-block class="combination-list-wrapper margin-vertical"
|
||||||
:key="categoryType.type"
|
:key="categoryType"
|
||||||
v-for="categoryType in allTransactionCategories"
|
v-for="(categories, categoryType) in allVisibleTransactionCategories"
|
||||||
v-else-if="!loading">
|
v-else-if="!loading">
|
||||||
<f7-accordion-item :opened="collapseStates[categoryType.type]!.opened"
|
<f7-accordion-item :opened="collapseStates[categoryType]!.opened"
|
||||||
@accordion:open="collapseStates[categoryType.type]!.opened = true"
|
@accordion:open="collapseStates[categoryType]!.opened = true"
|
||||||
@accordion:close="collapseStates[categoryType.type]!.opened = false">
|
@accordion:close="collapseStates[categoryType]!.opened = false">
|
||||||
<f7-block-title>
|
<f7-block-title>
|
||||||
<f7-accordion-toggle>
|
<f7-accordion-toggle>
|
||||||
<f7-list strong inset dividers
|
<f7-list strong inset dividers
|
||||||
class="combination-list-header"
|
class="combination-list-header"
|
||||||
:class="collapseStates[categoryType.type]!.opened ? 'combination-list-opened' : 'combination-list-closed'">
|
:class="collapseStates[categoryType]!.opened ? 'combination-list-opened' : 'combination-list-closed'">
|
||||||
<f7-list-item group-title>
|
<f7-list-item group-title>
|
||||||
<small>{{ getCategoryTypeName(categoryType.type) }}</small>
|
<small>{{ getCategoryTypeName(parseInt(categoryType)) }}</small>
|
||||||
<f7-icon class="combination-list-chevron-icon" :f7="collapseStates[categoryType.type]!.opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
|
<f7-icon class="combination-list-chevron-icon" :f7="collapseStates[categoryType]!.opened ? 'chevron_up' : 'chevron_down'"></f7-icon>
|
||||||
</f7-list-item>
|
</f7-list-item>
|
||||||
</f7-list>
|
</f7-list>
|
||||||
</f7-accordion-toggle>
|
</f7-accordion-toggle>
|
||||||
</f7-block-title>
|
</f7-block-title>
|
||||||
<f7-accordion-content :style="{ height: collapseStates[categoryType.type]!.opened ? 'auto' : '' }">
|
<f7-accordion-content :style="{ height: collapseStates[categoryType]!.opened ? 'auto' : '' }">
|
||||||
<f7-list strong inset dividers accordion-list class="combination-list-content" v-if="!hasAvailableCategory[categoryType.type]">
|
<f7-list strong inset dividers accordion-list class="combination-list-content" v-if="!categories || !categories.length">
|
||||||
<f7-list-item :title="tt('No available category')"></f7-list-item>
|
<f7-list-item :title="tt('No available category')"></f7-list-item>
|
||||||
</f7-list>
|
</f7-list>
|
||||||
<f7-list strong inset dividers accordion-list class="combination-list-content" v-else-if="hasAvailableCategory[categoryType.type]">
|
<f7-list strong inset dividers accordion-list class="combination-list-content" v-else-if="categories && categories.length">
|
||||||
<f7-list-item checkbox
|
<f7-list-item checkbox
|
||||||
:class="{ 'has-child-list-item': (showHidden && categoryType.allSubCategories[category.id]) || categoryType.allVisibleSubCategoryCounts[category.id] }"
|
:class="{ 'has-child-list-item': category.subCategories && category.subCategories.length > 0 }"
|
||||||
:title="category.name"
|
:title="category.name"
|
||||||
:value="category.id"
|
:value="category.id"
|
||||||
:checked="isSubCategoriesAllChecked(category, filterCategoryIds)"
|
:checked="isSubCategoriesAllChecked(category, filterCategoryIds)"
|
||||||
:indeterminate="isSubCategoriesHasButNotAllChecked(category, filterCategoryIds)"
|
:indeterminate="isSubCategoriesHasButNotAllChecked(category, filterCategoryIds)"
|
||||||
:key="category.id"
|
:key="category.id"
|
||||||
v-for="category in categoryType.allCategories"
|
v-for="category in categories"
|
||||||
v-show="showHidden || !category.hidden"
|
|
||||||
@change="updateAllSubCategoriesSelected">
|
@change="updateAllSubCategoriesSelected">
|
||||||
<template #media>
|
<template #media>
|
||||||
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color">
|
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color">
|
||||||
@@ -92,14 +101,13 @@
|
|||||||
|
|
||||||
<template #root>
|
<template #root>
|
||||||
<ul class="padding-inline-start"
|
<ul class="padding-inline-start"
|
||||||
v-if="(showHidden && categoryType.allSubCategories[category.id]) || categoryType.allVisibleSubCategoryCounts[category.id]">
|
v-if="category.subCategories && category.subCategories.length > 0">
|
||||||
<f7-list-item checkbox
|
<f7-list-item checkbox
|
||||||
:title="subCategory.name"
|
:title="subCategory.name"
|
||||||
:value="subCategory.id"
|
:value="subCategory.id"
|
||||||
:checked="isCategoryChecked(subCategory, filterCategoryIds)"
|
:checked="isCategoryChecked(subCategory, filterCategoryIds)"
|
||||||
:key="subCategory.id"
|
:key="subCategory.id"
|
||||||
v-for="subCategory in categoryType.allSubCategories[category.id]"
|
v-for="subCategory in category.subCategories"
|
||||||
v-show="showHidden || !subCategory.hidden"
|
|
||||||
@change="updateCategorySelected">
|
@change="updateCategorySelected">
|
||||||
<template #media>
|
<template #media>
|
||||||
<ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color">
|
<ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color">
|
||||||
@@ -123,9 +131,6 @@
|
|||||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleCategory }" @click="selectNoneCategories">{{ tt('Select None') }}</f7-actions-button>
|
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleCategory }" @click="selectNoneCategories">{{ tt('Select None') }}</f7-actions-button>
|
||||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleCategory }" @click="selectInvertCategories">{{ tt('Invert Selection') }}</f7-actions-button>
|
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleCategory }" @click="selectInvertCategories">{{ tt('Invert Selection') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
<f7-actions-group>
|
|
||||||
<f7-actions-button :class="{ 'disabled': !hasAnyVisibleCategory }" @click="selectAllVisibleCategories">{{ tt('Select All Visible') }}</f7-actions-button>
|
|
||||||
</f7-actions-group>
|
|
||||||
<f7-actions-group>
|
<f7-actions-group>
|
||||||
<f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Categories') }}</f7-actions-button>
|
<f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Categories') }}</f7-actions-button>
|
||||||
<f7-actions-button v-if="showHidden" @click="showHidden = false">{{ tt('Hide Hidden Transaction Categories') }}</f7-actions-button>
|
<f7-actions-button v-if="showHidden" @click="showHidden = false">{{ tt('Hide Hidden Transaction Categories') }}</f7-actions-button>
|
||||||
@@ -154,7 +159,6 @@ import { CategoryType } from '@/core/category.ts';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
selectAllSubCategories,
|
selectAllSubCategories,
|
||||||
selectAllVisible,
|
|
||||||
selectAll,
|
selectAll,
|
||||||
selectNone,
|
selectNone,
|
||||||
selectInvert,
|
selectInvert,
|
||||||
@@ -179,12 +183,13 @@ const { showToast, routeBackOnError } = useI18nUIComponents();
|
|||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
showHidden,
|
showHidden,
|
||||||
|
filterContent,
|
||||||
filterCategoryIds,
|
filterCategoryIds,
|
||||||
title,
|
title,
|
||||||
allTransactionCategories,
|
allVisibleTransactionCategories,
|
||||||
|
allVisibleTransactionCategoryMap,
|
||||||
hasAnyAvailableCategory,
|
hasAnyAvailableCategory,
|
||||||
hasAnyVisibleCategory,
|
hasAnyVisibleCategory,
|
||||||
hasAvailableCategory,
|
|
||||||
isCategoryChecked,
|
isCategoryChecked,
|
||||||
getCategoryTypeName,
|
getCategoryTypeName,
|
||||||
loadFilterCategoryIds,
|
loadFilterCategoryIds,
|
||||||
@@ -196,14 +201,14 @@ const transactionCategoriesStore = useTransactionCategoriesStore();
|
|||||||
const loadingError = ref<unknown | null>(null);
|
const loadingError = ref<unknown | null>(null);
|
||||||
const showMoreActionSheet = ref<boolean>(false);
|
const showMoreActionSheet = ref<boolean>(false);
|
||||||
|
|
||||||
const collapseStates = ref<Record<number, CollapseState>>({
|
const collapseStates = ref<Record<string, CollapseState>>({
|
||||||
[CategoryType.Income]: {
|
[CategoryType.Income.toString()]: {
|
||||||
opened: true
|
opened: true
|
||||||
},
|
},
|
||||||
[CategoryType.Expense]: {
|
[CategoryType.Expense.toString()]: {
|
||||||
opened: true
|
opened: true
|
||||||
},
|
},
|
||||||
[CategoryType.Transfer]: {
|
[CategoryType.Transfer.toString()]: {
|
||||||
opened: true
|
opened: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -231,7 +236,7 @@ function init(): void {
|
|||||||
function updateCategorySelected(e: Event): void {
|
function updateCategorySelected(e: Event): void {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
const categoryId = target.value;
|
const categoryId = target.value;
|
||||||
const category = transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
|
const category = allVisibleTransactionCategoryMap.value[categoryId];
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return;
|
return;
|
||||||
@@ -243,25 +248,21 @@ function updateCategorySelected(e: Event): void {
|
|||||||
function updateAllSubCategoriesSelected(e: Event): void {
|
function updateAllSubCategoriesSelected(e: Event): void {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
const categoryId = target.value;
|
const categoryId = target.value;
|
||||||
const category = transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
|
const category = allVisibleTransactionCategoryMap.value[categoryId];
|
||||||
|
|
||||||
selectAllSubCategories(filterCategoryIds.value, !target.checked, category);
|
selectAllSubCategories(filterCategoryIds.value, !target.checked, category);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAllCategories(): void {
|
function selectAllCategories(): void {
|
||||||
selectAll(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectAll(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNoneCategories(): void {
|
function selectNoneCategories(): void {
|
||||||
selectNone(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectNone(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectInvertCategories(): void {
|
function selectInvertCategories(): void {
|
||||||
selectInvert(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
selectInvert(filterCategoryIds.value, allVisibleTransactionCategoryMap.value);
|
||||||
}
|
|
||||||
|
|
||||||
function selectAllVisibleCategories(): void {
|
|
||||||
selectAllVisible(filterCategoryIds.value, transactionCategoriesStore.allTransactionCategoriesMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(): void {
|
function save(): void {
|
||||||
@@ -269,6 +270,10 @@ function save(): void {
|
|||||||
props.f7router.back();
|
props.f7router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPageBeforeIn(): void {
|
||||||
|
filterContent.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function onPageAfterIn(): void {
|
function onPageAfterIn(): void {
|
||||||
routeBackOnError(props.f7router, loadingError);
|
routeBackOnError(props.f7router, loadingError);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,18 +97,18 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="expenseCategorySelection"
|
key="expenseCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableExpenseCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
:class="{ 'disabled': !hasVisibleExpenseCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
||||||
:header="tt('Category')"
|
:header="tt('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === TransactionType.Expense"
|
v-if="transaction.type === TransactionType.Expense"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="list-item-custom-title" v-if="hasAvailableExpenseCategories">
|
<div class="list-item-custom-title" v-if="hasVisibleExpenseCategories">
|
||||||
<span>{{ getTransactionPrimaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
<span>{{ getTransactionPrimaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
||||||
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
||||||
<span>{{ getTransactionSecondaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
<span>{{ getTransactionSecondaryCategoryName(transaction.expenseCategoryId, allCategories[CategoryType.Expense]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-custom-title" v-else-if="!hasAvailableExpenseCategories">
|
<div class="list-item-custom-title" v-else-if="!hasVisibleExpenseCategories">
|
||||||
<span>{{ tt('None') }}</span>
|
<span>{{ tt('None') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -129,18 +129,18 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="incomeCategorySelection"
|
key="incomeCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableIncomeCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
:class="{ 'disabled': !hasVisibleIncomeCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
||||||
:header="tt('Category')"
|
:header="tt('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === TransactionType.Income"
|
v-if="transaction.type === TransactionType.Income"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="list-item-custom-title" v-if="hasAvailableIncomeCategories">
|
<div class="list-item-custom-title" v-if="hasVisibleIncomeCategories">
|
||||||
<span>{{ getTransactionPrimaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
<span>{{ getTransactionPrimaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
||||||
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
||||||
<span>{{ getTransactionSecondaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
<span>{{ getTransactionSecondaryCategoryName(transaction.incomeCategoryId, allCategories[CategoryType.Income]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-custom-title" v-else-if="!hasAvailableIncomeCategories">
|
<div class="list-item-custom-title" v-else-if="!hasVisibleIncomeCategories">
|
||||||
<span>{{ tt('None') }}</span>
|
<span>{{ tt('None') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -161,18 +161,18 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="transferCategorySelection"
|
key="transferCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableTransferCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
:class="{ 'disabled': !hasVisibleTransferCategories, 'readonly': mode === TransactionEditPageMode.View }"
|
||||||
:header="tt('Category')"
|
:header="tt('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === TransactionType.Transfer"
|
v-if="transaction.type === TransactionType.Transfer"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="list-item-custom-title" v-if="hasAvailableTransferCategories">
|
<div class="list-item-custom-title" v-if="hasVisibleTransferCategories">
|
||||||
<span>{{ getTransactionPrimaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
<span>{{ getTransactionPrimaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
||||||
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
<f7-icon class="category-separate-icon icon-with-direction" f7="chevron_right"></f7-icon>
|
||||||
<span>{{ getTransactionSecondaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
<span>{{ getTransactionSecondaryCategoryName(transaction.transferCategoryId, allCategories[CategoryType.Transfer]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-custom-title" v-else-if="!hasAvailableTransferCategories">
|
<div class="list-item-custom-title" v-else-if="!hasVisibleTransferCategories">
|
||||||
<span>{{ tt('None') }}</span>
|
<span>{{ tt('None') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -571,9 +571,9 @@ const {
|
|||||||
allTags,
|
allTags,
|
||||||
allTagsMap,
|
allTagsMap,
|
||||||
firstVisibleAccountId,
|
firstVisibleAccountId,
|
||||||
hasAvailableExpenseCategories,
|
hasVisibleExpenseCategories,
|
||||||
hasAvailableIncomeCategories,
|
hasVisibleIncomeCategories,
|
||||||
hasAvailableTransferCategories,
|
hasVisibleTransferCategories,
|
||||||
canAddTransactionPicture,
|
canAddTransactionPicture,
|
||||||
title,
|
title,
|
||||||
saveButtonTitle,
|
saveButtonTitle,
|
||||||
|
|||||||
Reference in New Issue
Block a user