mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
migrate batch replace dialog to composition API and typescript
This commit is contained in:
+2
-2
@@ -26,7 +26,7 @@ export function categoryTypeToTransactionType(categoryType: CategoryType): Trans
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTransactionPrimaryCategoryName(categoryId: string, allCategories: TransactionCategory[]): string {
|
export function getTransactionPrimaryCategoryName(categoryId: string | null | undefined, allCategories: TransactionCategory[]): string {
|
||||||
if (!allCategories) {
|
if (!allCategories) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ export function getTransactionPrimaryCategoryName(categoryId: string, allCategor
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTransactionSecondaryCategoryName(categoryId: string, allCategories: TransactionCategory[]): string {
|
export function getTransactionSecondaryCategoryName(categoryId: string | null | undefined, allCategories: TransactionCategory[]): string {
|
||||||
if (!allCategories) {
|
if (!allCategories) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ import { ALL_CURRENCIES } from '@/consts/currency.ts';
|
|||||||
import { DEFAULT_EXPENSE_CATEGORIES, DEFAULT_INCOME_CATEGORIES, DEFAULT_TRANSFER_CATEGORIES } from '@/consts/category.ts';
|
import { DEFAULT_EXPENSE_CATEGORIES, DEFAULT_INCOME_CATEGORIES, DEFAULT_TRANSFER_CATEGORIES } from '@/consts/category.ts';
|
||||||
import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts';
|
import { KnownErrorCode, SPECIFIED_API_NOT_FOUND_ERRORS, PARAMETERIZED_ERRORS } from '@/consts/api.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type CategorizedAccount,
|
||||||
|
Account,
|
||||||
|
AccountWithDisplayBalance,
|
||||||
|
CategorizedAccountWithDisplayBalance
|
||||||
|
} from '@/models/account.ts';
|
||||||
import type { LatestExchangeRateResponse, LocalizedLatestExchangeRate } from '@/models/exchange_rate.ts';
|
import type { LatestExchangeRateResponse, LocalizedLatestExchangeRate } from '@/models/exchange_rate.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -98,6 +104,7 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
isNumber,
|
isNumber,
|
||||||
isBoolean,
|
isBoolean,
|
||||||
|
copyObjectTo,
|
||||||
copyArrayTo
|
copyArrayTo
|
||||||
} from '@/lib/common.ts';
|
} from '@/lib/common.ts';
|
||||||
|
|
||||||
@@ -132,11 +139,17 @@ import {
|
|||||||
getAmountPrependAndAppendCurrencySymbol
|
getAmountPrependAndAppendCurrencySymbol
|
||||||
} from '@/lib/currency.ts';
|
} from '@/lib/currency.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCategorizedAccountsMap,
|
||||||
|
getAllFilteredAccountsBalance
|
||||||
|
} from '@/lib/account.ts';
|
||||||
|
|
||||||
import services from '@/lib/services.ts';
|
import services from '@/lib/services.ts';
|
||||||
import logger from '@/lib/logger.ts';
|
import logger from '@/lib/logger.ts';
|
||||||
|
|
||||||
import { useSettingsStore } from '@/stores/setting.ts';
|
import { useSettingsStore } from '@/stores/setting.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
import { useUserStore } from '@/stores/user.ts';
|
||||||
|
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
|
||||||
|
|
||||||
export interface LocalizedErrorParameter {
|
export interface LocalizedErrorParameter {
|
||||||
readonly key: string;
|
readonly key: string;
|
||||||
@@ -177,6 +190,7 @@ export function useI18n() {
|
|||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const exchangeRatesStore = useExchangeRatesStore();
|
||||||
|
|
||||||
// private functions
|
// private functions
|
||||||
function getLanguageDisplayName(languageName: string): string {
|
function getLanguageDisplayName(languageName: string): string {
|
||||||
@@ -1371,6 +1385,87 @@ export function useI18n() {
|
|||||||
return getAmountPrependAndAppendCurrencySymbol(currencyDisplayType, currencyCode, currencyUnit, currencyName, isPlural);
|
return getAmountPrependAndAppendCurrencySymbol(currencyDisplayType, currencyCode, currencyUnit, currencyName, isPlural);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCategorizedAccountsWithDisplayBalance(allVisibleAccounts: Account[], showAccountBalance: boolean): CategorizedAccountWithDisplayBalance[] {
|
||||||
|
const ret: CategorizedAccountWithDisplayBalance[] = [];
|
||||||
|
const defaultCurrency = userStore.currentUserDefaultCurrency;
|
||||||
|
const allCategories = AccountCategory.values();
|
||||||
|
const categorizedAccounts: Record<number, CategorizedAccount> = copyObjectTo(getCategorizedAccountsMap(allVisibleAccounts), {}) as Record<number, CategorizedAccount>;
|
||||||
|
|
||||||
|
for (let i = 0; i < allCategories.length; i++) {
|
||||||
|
const category = allCategories[i];
|
||||||
|
|
||||||
|
if (!categorizedAccounts[category.type]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountCategory = categorizedAccounts[category.type];
|
||||||
|
const accountsWithDisplayBalance: AccountWithDisplayBalance[] = [];
|
||||||
|
|
||||||
|
if (accountCategory.accounts) {
|
||||||
|
for (let i = 0; i < accountCategory.accounts.length; i++) {
|
||||||
|
const account = accountCategory.accounts[i];
|
||||||
|
let accountWithDisplaceBalance: AccountWithDisplayBalance;
|
||||||
|
|
||||||
|
if (showAccountBalance && account.isAsset) {
|
||||||
|
accountWithDisplaceBalance = AccountWithDisplayBalance.fromAccount(account, getFormattedAmountWithCurrency(account.balance, account.currency) as string);
|
||||||
|
} else if (showAccountBalance && account.isLiability) {
|
||||||
|
accountWithDisplaceBalance = AccountWithDisplayBalance.fromAccount(account, getFormattedAmountWithCurrency(-account.balance, account.currency) as string);
|
||||||
|
} else {
|
||||||
|
accountWithDisplaceBalance = AccountWithDisplayBalance.fromAccount(account, '***');
|
||||||
|
}
|
||||||
|
|
||||||
|
accountsWithDisplayBalance.push(accountWithDisplaceBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalTotalBalance = '';
|
||||||
|
|
||||||
|
if (showAccountBalance) {
|
||||||
|
const accountsBalance = getAllFilteredAccountsBalance(categorizedAccounts, account => account.category === accountCategory.category);
|
||||||
|
let totalBalance = 0;
|
||||||
|
let hasUnCalculatedAmount = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < accountsBalance.length; i++) {
|
||||||
|
if (accountsBalance[i].currency === defaultCurrency) {
|
||||||
|
if (accountsBalance[i].isAsset) {
|
||||||
|
totalBalance += accountsBalance[i].balance;
|
||||||
|
} else if (accountsBalance[i].isLiability) {
|
||||||
|
totalBalance -= accountsBalance[i].balance;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, defaultCurrency);
|
||||||
|
|
||||||
|
if (!isNumber(balance)) {
|
||||||
|
hasUnCalculatedAmount = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountsBalance[i].isAsset) {
|
||||||
|
totalBalance += Math.floor(balance);
|
||||||
|
} else if (accountsBalance[i].isLiability) {
|
||||||
|
totalBalance -= Math.floor(balance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalTotalBalance = totalBalance.toString();
|
||||||
|
|
||||||
|
if (hasUnCalculatedAmount) {
|
||||||
|
finalTotalBalance = finalTotalBalance + '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
finalTotalBalance = getFormattedAmountWithCurrency(finalTotalBalance, defaultCurrency) as string;
|
||||||
|
} else {
|
||||||
|
finalTotalBalance = '***';
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountCategoryWithDisplayBalance = CategorizedAccountWithDisplayBalance.of(accountCategory, accountsWithDisplayBalance, finalTotalBalance);
|
||||||
|
ret.push(accountCategoryWithDisplayBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
function setLanguage(languageKey: string | null, force?: boolean): LocaleDefaultSettings | null {
|
function setLanguage(languageKey: string | null, force?: boolean): LocaleDefaultSettings | null {
|
||||||
if (!languageKey) {
|
if (!languageKey) {
|
||||||
languageKey = getDefaultLanguage();
|
languageKey = getDefaultLanguage();
|
||||||
@@ -1547,6 +1642,7 @@ export function useI18n() {
|
|||||||
formatExchangeRateAmount: getFormattedExchangeRateAmount,
|
formatExchangeRateAmount: getFormattedExchangeRateAmount,
|
||||||
getAdaptiveAmountRate,
|
getAdaptiveAmountRate,
|
||||||
getAmountPrependAndAppendText,
|
getAmountPrependAndAppendText,
|
||||||
|
getCategorizedAccountsWithDisplayBalance,
|
||||||
// localization setting functions
|
// localization setting functions
|
||||||
setLanguage,
|
setLanguage,
|
||||||
setTimeZone,
|
setTimeZone,
|
||||||
|
|||||||
+53
-1
@@ -23,7 +23,7 @@ export class Account implements AccountInfoResponse {
|
|||||||
public visible: boolean;
|
public visible: boolean;
|
||||||
public childrenAccounts?: Account[];
|
public childrenAccounts?: Account[];
|
||||||
|
|
||||||
private constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, childrenAccounts?: Account[]) {
|
protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, childrenAccounts?: Account[]) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.parentId = parentId;
|
this.parentId = parentId;
|
||||||
@@ -303,6 +303,38 @@ export class Account implements AccountInfoResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AccountWithDisplayBalance extends Account {
|
||||||
|
public displayBalance: string;
|
||||||
|
|
||||||
|
private constructor(Account: Account, displayBalance: string) {
|
||||||
|
super(
|
||||||
|
Account.id,
|
||||||
|
Account.name,
|
||||||
|
Account.parentId,
|
||||||
|
Account.category,
|
||||||
|
Account.type,
|
||||||
|
Account.icon,
|
||||||
|
Account.color,
|
||||||
|
Account.currency,
|
||||||
|
Account.balance,
|
||||||
|
Account.comment,
|
||||||
|
Account.displayOrder,
|
||||||
|
Account.visible,
|
||||||
|
Account.balanceTime,
|
||||||
|
Account.creditCardStatementDate,
|
||||||
|
Account.isAsset,
|
||||||
|
Account.isLiability,
|
||||||
|
Account.childrenAccounts
|
||||||
|
);
|
||||||
|
|
||||||
|
this.displayBalance = displayBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromAccount(account: Account, displayBalance: string): AccountWithDisplayBalance {
|
||||||
|
return new AccountWithDisplayBalance(account, displayBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccountCreateRequest {
|
export interface AccountCreateRequest {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly category: number;
|
readonly category: number;
|
||||||
@@ -386,6 +418,26 @@ export interface CategorizedAccount {
|
|||||||
readonly accounts: Account[];
|
readonly accounts: Account[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CategorizedAccountWithDisplayBalance {
|
||||||
|
public category: number;
|
||||||
|
public name: string;
|
||||||
|
public icon: string;
|
||||||
|
public accounts: AccountWithDisplayBalance[];
|
||||||
|
public displayBalance: string;
|
||||||
|
|
||||||
|
private constructor(category: number, name: string, icon: string, accounts: AccountWithDisplayBalance[], displayBalance: string) {
|
||||||
|
this.category = category;
|
||||||
|
this.name = name;
|
||||||
|
this.icon = icon;
|
||||||
|
this.accounts = accounts;
|
||||||
|
this.displayBalance = displayBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static of(categorizedAccount: CategorizedAccount, accounts: AccountWithDisplayBalance[], displayBalance: string): CategorizedAccountWithDisplayBalance {
|
||||||
|
return new CategorizedAccountWithDisplayBalance(categorizedAccount.category, categorizedAccount.name, categorizedAccount.icon, accounts, displayBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface AccountCategoriesWithVisibleCount {
|
export interface AccountCategoriesWithVisibleCount {
|
||||||
readonly category: number;
|
readonly category: number;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="d-flex align-center justify-center">
|
<div class="d-flex align-center justify-center">
|
||||||
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'expenseCategory'">{{ $t('Batch Replace Selected Expense Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'expenseCategory'">{{ tt('Batch Replace Selected Expense Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'incomeCategory'">{{ $t('Batch Replace Selected Income Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'incomeCategory'">{{ tt('Batch Replace Selected Income Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'transferCategory'">{{ $t('Batch Replace Selected Transfer Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'transferCategory'">{{ tt('Batch Replace Selected Transfer Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'account'">{{ $t('Batch Replace Selected Accounts') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'account'">{{ tt('Batch Replace Selected Accounts') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'destinationAccount'">{{ $t('Batch Replace Selected Destination Accounts') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'batchReplace' && type === 'destinationAccount'">{{ tt('Batch Replace Selected Destination Accounts') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'expenseCategory'">{{ $t('Replace Invalid Expense Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'expenseCategory'">{{ tt('Replace Invalid Expense Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'incomeCategory'">{{ $t('Replace Invalid Income Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'incomeCategory'">{{ tt('Replace Invalid Income Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'transferCategory'">{{ $t('Replace Invalid Transfer Categories') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'transferCategory'">{{ tt('Replace Invalid Transfer Categories') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'account'">{{ $t('Replace Invalid Accounts') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'account'">{{ tt('Replace Invalid Accounts') }}</h4>
|
||||||
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'tag'">{{ $t('Replace Invalid Transaction Tags') }}</h4>
|
<h4 class="text-h4" v-if="mode === 'replaceInvalidItems' && type === 'tag'">{{ tt('Replace Invalid Transaction Tags') }}</h4>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory'">
|
<v-card-text class="my-md-4 w-100 d-flex justify-center" v-if="type === 'expenseCategory' || type === 'incomeCategory' || type === 'transferCategory'">
|
||||||
@@ -22,10 +22,10 @@
|
|||||||
item-title="name"
|
item-title="name"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
:label="$t('Invalid Category')"
|
:label="tt('Invalid Category')"
|
||||||
:placeholder="$t('Invalid Category')"
|
:placeholder="tt('Invalid Category')"
|
||||||
:items="invalidItems"
|
:items="invalidItems"
|
||||||
:no-data-text="$t('No available category')"
|
:no-data-text="tt('No available category')"
|
||||||
v-model="sourceItem">
|
v-model="sourceItem">
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -38,11 +38,11 @@
|
|||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!hasAvailableExpenseCategories"
|
:disabled="!hasAvailableExpenseCategories"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Expense])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Expense])"
|
||||||
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Expense])"
|
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(targetItem, allCategories[CategoryType.Expense])"
|
||||||
:label="$t('Target Category')"
|
:label="tt('Target Category')"
|
||||||
:placeholder="$t('Target Category')"
|
:placeholder="tt('Target Category')"
|
||||||
:items="allCategories[allCategoryTypes.Expense]"
|
:items="allCategories[CategoryType.Expense]"
|
||||||
v-model="targetItem"
|
v-model="targetItem"
|
||||||
v-if="type === 'expenseCategory'">
|
v-if="type === 'expenseCategory'">
|
||||||
</two-column-select>
|
</two-column-select>
|
||||||
@@ -54,11 +54,11 @@
|
|||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!hasAvailableIncomeCategories"
|
:disabled="!hasAvailableIncomeCategories"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Income])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Income])"
|
||||||
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Income])"
|
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(targetItem, allCategories[CategoryType.Income])"
|
||||||
:label="$t('Target Category')"
|
:label="tt('Target Category')"
|
||||||
:placeholder="$t('Target Category')"
|
:placeholder="tt('Target Category')"
|
||||||
:items="allCategories[allCategoryTypes.Income]"
|
:items="allCategories[CategoryType.Income]"
|
||||||
v-model="targetItem"
|
v-model="targetItem"
|
||||||
v-if="type === 'incomeCategory'">
|
v-if="type === 'incomeCategory'">
|
||||||
</two-column-select>
|
</two-column-select>
|
||||||
@@ -70,11 +70,11 @@
|
|||||||
secondary-hidden-field="hidden"
|
secondary-hidden-field="hidden"
|
||||||
:disabled="!hasAvailableTransferCategories"
|
:disabled="!hasAvailableTransferCategories"
|
||||||
:show-selection-primary-text="true"
|
:show-selection-primary-text="true"
|
||||||
:custom-selection-primary-text="getPrimaryCategoryName(targetItem, allCategories[allCategoryTypes.Transfer])"
|
:custom-selection-primary-text="getTransactionPrimaryCategoryName(targetItem, allCategories[CategoryType.Transfer])"
|
||||||
:custom-selection-secondary-text="getSecondaryCategoryName(targetItem, allCategories[allCategoryTypes.Transfer])"
|
:custom-selection-secondary-text="getTransactionSecondaryCategoryName(targetItem, allCategories[CategoryType.Transfer])"
|
||||||
:label="$t('Target Category')"
|
:label="tt('Target Category')"
|
||||||
:placeholder="$t('Target Category')"
|
:placeholder="tt('Target Category')"
|
||||||
:items="allCategories[allCategoryTypes.Transfer]"
|
:items="allCategories[CategoryType.Transfer]"
|
||||||
v-model="targetItem"
|
v-model="targetItem"
|
||||||
v-if="type === 'transferCategory'">
|
v-if="type === 'transferCategory'">
|
||||||
</two-column-select>
|
</two-column-select>
|
||||||
@@ -88,10 +88,10 @@
|
|||||||
item-title="name"
|
item-title="name"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
:label="$t('Invalid Account')"
|
:label="tt('Invalid Account')"
|
||||||
:placeholder="$t('Invalid Account')"
|
:placeholder="tt('Invalid Account')"
|
||||||
:items="invalidItems"
|
:items="invalidItems"
|
||||||
:no-data-text="$t('No available account')"
|
:no-data-text="tt('No available account')"
|
||||||
v-model="sourceItem">
|
v-model="sourceItem">
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
secondary-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
|
secondary-icon-field="icon" secondary-icon-type="account" secondary-color-field="color"
|
||||||
:disabled="!allVisibleAccounts.length"
|
:disabled="!allVisibleAccounts.length"
|
||||||
:custom-selection-primary-text="getAccountDisplayName(targetItem)"
|
:custom-selection-primary-text="getAccountDisplayName(targetItem)"
|
||||||
:label="$t('Target Account')"
|
:label="tt('Target Account')"
|
||||||
:placeholder="$t('Target Account')"
|
:placeholder="tt('Target Account')"
|
||||||
:items="allVisibleCategorizedAccounts"
|
:items="allVisibleCategorizedAccounts"
|
||||||
v-model="targetItem">
|
v-model="targetItem">
|
||||||
</two-column-select>
|
</two-column-select>
|
||||||
@@ -121,10 +121,10 @@
|
|||||||
item-title="name"
|
item-title="name"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
:label="$t('Invalid Tag')"
|
:label="tt('Invalid Tag')"
|
||||||
:placeholder="$t('Invalid Tag')"
|
:placeholder="tt('Invalid Tag')"
|
||||||
:items="invalidItems"
|
:items="invalidItems"
|
||||||
:no-data-text="$t('No available tag')"
|
:no-data-text="tt('No available tag')"
|
||||||
v-model="sourceItem">
|
v-model="sourceItem">
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -134,10 +134,10 @@
|
|||||||
item-value="id"
|
item-value="id"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
chips
|
chips
|
||||||
:label="$t('Target Tag')"
|
:label="tt('Target Tag')"
|
||||||
:placeholder="$t('Target Tag')"
|
:placeholder="tt('Target Tag')"
|
||||||
:items="allTags"
|
:items="allTags"
|
||||||
:no-data-text="$t('No available tag')"
|
:no-data-text="tt('No available tag')"
|
||||||
v-model="targetItem"
|
v-model="targetItem"
|
||||||
>
|
>
|
||||||
<template #chip="{ props, item }">
|
<template #chip="{ props, item }">
|
||||||
@@ -162,27 +162,29 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text class="overflow-y-visible">
|
<v-card-text class="overflow-y-visible">
|
||||||
<div class="w-100 d-flex justify-center gap-4">
|
<div class="w-100 d-flex justify-center gap-4">
|
||||||
<v-btn :disabled="(mode === 'replaceInvalidItems' && !sourceItem && sourceItem !== '') || (!targetItem && targetItem !== '')" @click="confirm">{{ $t('OK') }}</v-btn>
|
<v-btn :disabled="(mode === 'replaceInvalidItems' && !sourceItem && sourceItem !== '') || (!targetItem && targetItem !== '')" @click="confirm">{{ tt('OK') }}</v-btn>
|
||||||
<v-btn color="secondary" variant="tonal" @click="cancel">{{ $t('Cancel') }}</v-btn>
|
<v-btn color="secondary" variant="tonal" @click="cancel">{{ tt('Cancel') }}</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapStores } from 'pinia';
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
|
||||||
import { useSettingsStore } from '@/stores/setting.ts';
|
import { useSettingsStore } from '@/stores/setting.ts';
|
||||||
import { useUserStore } from '@/stores/user.ts';
|
|
||||||
import { useAccountsStore } from '@/stores/account.ts';
|
import { useAccountsStore } from '@/stores/account.ts';
|
||||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||||
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
||||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.ts';
|
|
||||||
|
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { CategoryType } from '@/core/category.ts';
|
||||||
import {
|
import { type CategorizedAccountWithDisplayBalance, Account } from '@/models/account.ts';
|
||||||
getNameByKeyValue
|
import type { TransactionCategory } from '@/models/transaction_category.ts';
|
||||||
} from '@/lib/common.ts';
|
import type { TransactionTag } from '@/models/transaction_tag.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getTransactionPrimaryCategoryName,
|
getTransactionPrimaryCategoryName,
|
||||||
getTransactionSecondaryCategoryName,
|
getTransactionSecondaryCategoryName,
|
||||||
@@ -193,137 +195,124 @@ import {
|
|||||||
mdiPound
|
mdiPound
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
|
||||||
export default {
|
interface BatchReplaceDialogInvalidItem {
|
||||||
props: [
|
name: string;
|
||||||
'persistent'
|
value: string;
|
||||||
],
|
}
|
||||||
expose: [
|
|
||||||
'open'
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showState: false,
|
|
||||||
mode: '',
|
|
||||||
type: '',
|
|
||||||
invalidItems: [],
|
|
||||||
sourceItem: null,
|
|
||||||
targetItem: null,
|
|
||||||
icons: {
|
|
||||||
tag: mdiPound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useExchangeRatesStore),
|
|
||||||
defaultCurrency() {
|
|
||||||
return this.userStore.currentUserDefaultCurrency;
|
|
||||||
},
|
|
||||||
allCategoryTypes() {
|
|
||||||
return CategoryType;
|
|
||||||
},
|
|
||||||
allAccounts() {
|
|
||||||
return this.accountsStore.allPlainAccounts;
|
|
||||||
},
|
|
||||||
allVisibleCategorizedAccounts() {
|
|
||||||
return this.$locale.getCategorizedAccountsWithDisplayBalance(this.allVisibleAccounts, this.showAccountBalance, this.defaultCurrency, this.settingsStore, this.userStore, this.exchangeRatesStore);
|
|
||||||
},
|
|
||||||
allVisibleAccounts() {
|
|
||||||
return this.accountsStore.allVisiblePlainAccounts;
|
|
||||||
},
|
|
||||||
allCategories() {
|
|
||||||
return this.transactionCategoriesStore.allTransactionCategories;
|
|
||||||
},
|
|
||||||
allTags() {
|
|
||||||
return this.transactionTagsStore.allTransactionTags;
|
|
||||||
},
|
|
||||||
allTagsMap() {
|
|
||||||
return this.transactionTagsStore.allTransactionTagsMap;
|
|
||||||
},
|
|
||||||
hasAvailableExpenseCategories() {
|
|
||||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Expense]);
|
interface BatchReplaceDialogResponse {
|
||||||
return firstAvailableCategoryId !== '';
|
sourceItem?: string;
|
||||||
},
|
targetItem?: string;
|
||||||
hasAvailableIncomeCategories() {
|
}
|
||||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Income] || !this.allCategories[this.allCategoryTypes.Income].length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Income]);
|
defineProps<{
|
||||||
return firstAvailableCategoryId !== '';
|
persistent?: boolean;
|
||||||
},
|
}>();
|
||||||
hasAvailableTransferCategories() {
|
|
||||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Transfer] || !this.allCategories[this.allCategoryTypes.Transfer].length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstAvailableCategoryId = getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Transfer]);
|
const { tt, getCategorizedAccountsWithDisplayBalance } = useI18n();
|
||||||
return firstAvailableCategoryId !== '';
|
|
||||||
},
|
|
||||||
showAccountBalance() {
|
|
||||||
return this.settingsStore.appSettings.showAccountBalance;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
open(options) {
|
|
||||||
const self = this;
|
|
||||||
self.mode = options.mode;
|
|
||||||
self.type = options.type;
|
|
||||||
self.sourceItem = null;
|
|
||||||
|
|
||||||
if (self.mode === 'batchReplace') {
|
const settingsStore = useSettingsStore();
|
||||||
self.invalidItems = null;
|
const accountsStore = useAccountsStore();
|
||||||
} else if (self.mode === 'replaceInvalidItems') {
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||||
self.invalidItems = options.invalidItems;
|
const transactionTagsStore = useTransactionTagsStore();
|
||||||
}
|
|
||||||
|
|
||||||
self.targetItem = null;
|
const icons = {
|
||||||
self.showState = true;
|
tag: mdiPound
|
||||||
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
const showState = ref<boolean>(false);
|
||||||
self.resolve = resolve;
|
const mode = ref<string>('');
|
||||||
self.reject = reject;
|
const type = ref<string>('');
|
||||||
});
|
const invalidItems = ref<BatchReplaceDialogInvalidItem[] | undefined>([]);
|
||||||
},
|
const sourceItem = ref<string | undefined>(undefined);
|
||||||
confirm() {
|
const targetItem = ref<string | undefined>(undefined);
|
||||||
if (this.resolve) {
|
|
||||||
if (this.mode === 'batchReplace') {
|
|
||||||
this.resolve({
|
|
||||||
targetItem: this.targetItem
|
|
||||||
});
|
|
||||||
} else if (this.mode === 'replaceInvalidItems') {
|
|
||||||
this.resolve({
|
|
||||||
sourceItem: this.sourceItem,
|
|
||||||
targetItem: this.targetItem
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showState = false;
|
let resolveFunc: ((response: BatchReplaceDialogResponse) => void) | null = null;
|
||||||
},
|
let rejectFunc: ((reason?: unknown) => void) | null = null;
|
||||||
cancel() {
|
|
||||||
if (this.reject) {
|
|
||||||
this.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showState = false;
|
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
|
||||||
},
|
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
|
||||||
getPrimaryCategoryName(categoryId, allCategories) {
|
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
|
||||||
return getTransactionPrimaryCategoryName(categoryId, allCategories);
|
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
|
||||||
},
|
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
|
||||||
getSecondaryCategoryName(categoryId, allCategories) {
|
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
|
||||||
return getTransactionSecondaryCategoryName(categoryId, allCategories);
|
|
||||||
},
|
const hasAvailableExpenseCategories = computed<boolean>(() => {
|
||||||
getAccountDisplayName(accountId) {
|
if (!allCategories.value || !allCategories.value[CategoryType.Expense] || !allCategories.value[CategoryType.Expense].length) {
|
||||||
if (accountId) {
|
return false;
|
||||||
return getNameByKeyValue(this.allAccounts, accountId, 'id', 'name');
|
}
|
||||||
} else {
|
|
||||||
return this.$t('None');
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(allCategories.value[CategoryType.Expense]);
|
||||||
}
|
return firstAvailableCategoryId !== '';
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const hasAvailableIncomeCategories = computed<boolean>(() => {
|
||||||
|
if (!allCategories.value || !allCategories.value[CategoryType.Income] || !allCategories.value[CategoryType.Income].length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(allCategories.value[CategoryType.Income]);
|
||||||
|
return firstAvailableCategoryId !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasAvailableTransferCategories = computed<boolean>(() => {
|
||||||
|
if (!allCategories.value || !allCategories.value[CategoryType.Transfer] || !allCategories.value[CategoryType.Transfer].length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstAvailableCategoryId = getFirstAvailableCategoryId(allCategories.value[CategoryType.Transfer]);
|
||||||
|
return firstAvailableCategoryId !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
function getAccountDisplayName(accountId?: string): string {
|
||||||
|
if (accountId) {
|
||||||
|
return Account.findAccountNameById(allAccounts.value, accountId) || '';
|
||||||
|
} else {
|
||||||
|
return tt('None');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function open(options: { mode: string; type: string; invalidItems?: BatchReplaceDialogInvalidItem[] }): Promise<BatchReplaceDialogResponse> {
|
||||||
|
mode.value = options.mode;
|
||||||
|
type.value = options.type;
|
||||||
|
sourceItem.value = undefined;
|
||||||
|
|
||||||
|
if (mode.value === 'batchReplace') {
|
||||||
|
invalidItems.value = undefined;
|
||||||
|
} else if (mode.value === 'replaceInvalidItems') {
|
||||||
|
invalidItems.value = options.invalidItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetItem.value = undefined;
|
||||||
|
showState.value = true;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolveFunc = resolve;
|
||||||
|
rejectFunc = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm(): void {
|
||||||
|
if (mode.value === 'batchReplace') {
|
||||||
|
resolveFunc?.({
|
||||||
|
targetItem: targetItem.value
|
||||||
|
});
|
||||||
|
} else if (mode.value === 'replaceInvalidItems') {
|
||||||
|
resolveFunc?.({
|
||||||
|
sourceItem: sourceItem.value,
|
||||||
|
targetItem: targetItem.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showState.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel(): void {
|
||||||
|
rejectFunc?.();
|
||||||
|
showState.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user