support changing account category order

This commit is contained in:
MaysWind
2026-01-04 22:50:13 +08:00
parent 6e369f39a4
commit 0ce66d9070
50 changed files with 575 additions and 72 deletions
+4 -2
View File
@@ -31,7 +31,7 @@
<v-tabs show-arrows class="account-category-tabs my-4" direction="vertical"
:disabled="loading" v-model="activeAccountCategoryType">
<v-tab class="tab-text-truncate" :key="accountCategory.type" :value="accountCategory.type"
v-for="accountCategory in AccountCategory.values()"
v-for="accountCategory in AccountCategory.values(customAccountCategoryOrder)"
v-show="!hideAccountCategoriesWithoutAccounts || (allCategorizedAccountsMap[accountCategory.type] && allCategorizedAccountsMap[accountCategory.type]!.accounts.length > 0)">
<ItemIcon icon-type="account" :icon-id="accountCategory.defaultAccountIconId" />
<div class="d-flex flex-column text-truncate ms-2">
@@ -372,6 +372,8 @@ const {
showHidden,
displayOrderModified,
showAccountBalance,
customAccountCategoryOrder,
defaultAccountCategory,
firstDayOfWeek,
fiscalYearStart,
allAccounts,
@@ -394,7 +396,7 @@ const reconciliationStatementDialog = useTemplateRef<ReconciliationStatementDial
const moveAllTransactionsDialog = useTemplateRef<MoveAllTransactionsDialogType>('moveAllTransactionsDialog');
const clearAllTransactionsDialog = useTemplateRef<ClearAllTransactionsDialogType>('clearAllTransactionsDialog');
const activeAccountCategoryType = ref<number>(AccountCategory.Default.type);
const activeAccountCategoryType = ref<number>(defaultAccountCategory.value.type);
const activeTab = ref<string>('accountPage');
const activeSubAccount = ref<Record<string, string>>({});
const accountToShowReconciliationStatement = ref<Account | null>(null);
@@ -229,6 +229,7 @@ type SnackBarType = InstanceType<typeof SnackBar>;
const { tt } = useI18n();
const {
defaultAccountCategory,
editAccountId,
clientSessionId,
loading,
@@ -279,7 +280,7 @@ const accountAmountTitle = computed<string>(() => {
const isAccountModified = computed<boolean>(() => {
if (!editAccountId.value) {
return !account.value.equals(Account.createNewAccount(userStore.currentUserDefaultCurrency, account.value.balanceTime ?? getCurrentUnixTimeForNewAccount()));
return !account.value.equals(Account.createNewAccount(defaultAccountCategory, userStore.currentUserDefaultCurrency, account.value.balanceTime ?? getCurrentUnixTimeForNewAccount()));
} else {
return true;
}
@@ -293,7 +294,7 @@ function open(options?: { id?: string, currentAccount?: Account, category?: numb
loading.value = true;
submitting.value = false;
const newAccount = Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
const newAccount = Account.createNewAccount(defaultAccountCategory, userStore.currentUserDefaultCurrency, getCurrentUnixTimeForNewAccount());
account.value.fillFrom(newAccount);
subAccounts.value = [];
currentAccountIndex.value = -1;
@@ -0,0 +1,113 @@
<template>
<v-dialog width="600" :persistent="isDisplayOrderModified()" v-model="showState">
<v-card class="pa-sm-1 pa-md-2">
<template #title>
<div class="d-flex align-center justify-center">
<div class="d-flex align-center">
<h4 class="text-h4">{{ tt('Account Category Order') }}</h4>
</div>
<v-spacer/>
<v-btn density="comfortable" color="default" variant="text" class="ms-2" :icon="true">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent">
<v-list>
<v-list-item :prepend-icon="mdiRestore"
:title="tt('Reset to Default')"
@click="resetDisplayOrderToDefault"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
</template>
<v-card-text class="d-flex flex-column flex-md-row flex-grow-1 overflow-y-auto">
<v-table hover density="comfortable" class="w-100 table-striped">
<draggable-list tag="tbody"
item-key="id"
handle=".drag-handle"
ghost-class="dragging-item"
v-model="accountCategories">
<template #item="{ element }">
<tr class="text-sm">
<td>
<div class="d-flex align-center">
<div class="d-flex align-center">
<span>{{ tt(element.name) }}</span>
</div>
<v-spacer/>
<span class="ms-2">
<v-icon class="drag-handle" :icon="mdiDrag"/>
<v-tooltip activator="parent">{{ tt('Drag to Reorder') }}</v-tooltip>
</span>
</div>
</td>
</tr>
</template>
</draggable-list>
</v-table>
</v-card-text>
<v-card-text class="overflow-y-visible">
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
<v-btn :disabled="!isDisplayOrderModified()" @click="saveDisplayOrder">{{ tt('Save') }}</v-btn>
<v-btn color="secondary" variant="tonal" @click="cancel">{{ tt('Cancel') }}</v-btn>
</div>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useAccountCategoryDisplayOrderSettingsPageBase } from '@/views/base/settings/AccountCategoryDisplayOrderSettingsPageBase.ts';
import {
mdiDotsVertical,
mdiRestore,
mdiDrag
} from '@mdi/js';
const { tt } = useI18n();
const {
accountCategories,
isDisplayOrderModified,
loadDisplayOrderFromSettings,
saveDisplayOrderToSettings,
resetDisplayOrderToDefault
} = useAccountCategoryDisplayOrderSettingsPageBase();
let resolveFunc: (() => void) | null = null;
let rejectFunc: (() => void) | null = null;
const showState = ref<boolean>(false);
function open(): Promise<void> {
loadDisplayOrderFromSettings();
showState.value = true;
return new Promise<void>((resolve, reject) => {
resolveFunc = resolve;
rejectFunc = reject;
});
}
function saveDisplayOrder(): void {
saveDisplayOrderToSettings();
resolveFunc?.();
showState.value = false;
}
function cancel(): void {
rejectFunc?.();
showState.value = false;
}
defineExpose({
open
});
</script>
@@ -277,6 +277,19 @@
@click="showAccountsIncludedInTotalDialog = true"
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
class="always-cursor-pointer"
item-title="displayName"
item-value="type"
persistent-placeholder
:readonly="true"
:label="tt('Account Category Order')"
:placeholder="tt('Account Category Order')"
:model-value="accountCategorysDisplayOrderContent"
@click="accountCategorysDisplayOrderDialog?.open()"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
@@ -332,6 +345,8 @@
@settings:change="showAccountsIncludedInTotalDialog = false" />
</v-dialog>
<account-category-display-order-dialog ref="accountCategorysDisplayOrderDialog" />
<snack-bar ref="snackbar" />
</template>
@@ -339,6 +354,7 @@
import SnackBar from '@/components/desktop/SnackBar.vue';
import AccountFilterSettingsCard from '@/views/desktop/common/cards/AccountFilterSettingsCard.vue';
import CategoryFilterSettingsCard from '@/views/desktop/common/cards/CategoryFilterSettingsCard.vue';
import AccountCategoryDisplayOrderDialog from '@/views/desktop/app/settings/dialogs/AccountCategoryDisplayOrderDialog.vue';
import { ref, computed, useTemplateRef } from 'vue';
import { useTheme } from 'vuetify';
@@ -358,6 +374,7 @@ import { CategoryType } from '@/core/category.ts';
import { getSystemTheme } from '@/lib/ui/common.ts';
type SnackBarType = InstanceType<typeof SnackBar>;
type AccountCategoryDisplayOrderDialogType = InstanceType<typeof AccountCategoryDisplayOrderDialog>;
const theme = useTheme();
@@ -386,6 +403,7 @@ const {
currencySortByInExchangeRatesPage,
accountsIncludedInHomePageOverviewDisplayContent,
accountsIncludedInTotalDisplayContent,
accountCategorysDisplayOrderContent,
transactionCategoriesIncludedInHomePageOverviewDisplayContent
} = useAppSettingPageBase();
@@ -394,6 +412,7 @@ const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const accountCategorysDisplayOrderDialog = useTemplateRef<AccountCategoryDisplayOrderDialogType>('accountCategorysDisplayOrderDialog');
const showAccountsIncludedInHomePageOverviewDialog = ref<boolean>(false);
const showTransactionCategoriesIncludedInHomePageOverviewDialog = ref<boolean>(false);
@@ -181,6 +181,7 @@ const {
title,
applyText,
allowHiddenAccount,
customAccountCategoryOrder,
allCategorizedAccounts,
allVisibleAccountMap,
hasAnyAvailableAccount,
@@ -194,7 +195,7 @@ const accountsStore = useAccountsStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const expandAccountCategories = ref<number[]>(AccountCategory.values().map(category => category.type));
const expandAccountCategories = ref<number[]>(AccountCategory.values(customAccountCategoryOrder.value).map(category => category.type));
function init(): void {
accountsStore.loadAllAccounts({
@@ -279,9 +279,10 @@ let resolveFunc: ((response: BatchReplaceAllTypesDialogResponse) => void) | null
let rejectFunc: ((reason?: unknown) => void) | null = null;
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const customAccountCategoryOrder = computed<string>(() => settingsStore.appSettings.accountCategoryOrders);
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value, customAccountCategoryOrder.value));
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
@@ -269,9 +269,10 @@ let resolveFunc: ((response: BatchReplaceDialogResponse) => void) | null = null;
let rejectFunc: ((reason?: unknown) => void) | null = null;
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const customAccountCategoryOrder = computed<string>(() => settingsStore.appSettings.accountCategoryOrders);
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value, customAccountCategoryOrder.value));
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);
const allTags = computed<TransactionTag[]>(() => transactionTagsStore.allTransactionTags);
@@ -539,13 +539,14 @@ const currentDescriptionFilterValue = ref<string | null>(null);
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const customAccountCategoryOrder = computed<string>(() => settingsStore.appSettings.accountCategoryOrders);
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const coordinateDisplayType = computed<number>(() => userStore.currentUserCoordinateDisplayType);
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value, customAccountCategoryOrder.value));
const allAccountsMap = computed<Record<string, Account>>(() => accountsStore.allAccountsMap);
const allAccountsMapByName = computed<Record<string, Account>>(() => getAccountMapByName(accountsStore.allAccounts));
const allCategories = computed<Record<number, TransactionCategory[]>>(() => transactionCategoriesStore.allTransactionCategories);