add category preset for desktop page

This commit is contained in:
MaysWind
2023-08-03 22:47:52 +08:00
parent 8c7875d7ea
commit 19d3d80013
5 changed files with 140 additions and 157 deletions
+36
View File
@@ -77,6 +77,24 @@ export function isEquals(obj1, obj2) {
} }
} }
export function getObjectOwnFieldCount(object) {
let count = 0;
if (!object || !isObject(object)) {
return count;
}
for (let field in object) {
if (!Object.prototype.hasOwnProperty.call(object, field)) {
continue;
}
count++;
}
return count;
}
export function appendThousandsSeparator(value, enable) { export function appendThousandsSeparator(value, enable) {
if (!enable || value.length <= 3) { if (!enable || value.length <= 3) {
return value; return value;
@@ -284,6 +302,24 @@ export function copyArrayTo(fromArray, toArray) {
return toArray; return toArray;
} }
export function categoriedArrayToPlainArray(object) {
const ret = [];
for (let field in object) {
if (!Object.prototype.hasOwnProperty.call(object, field)) {
continue;
}
const array = object[field];
for (let i = 0; i < array.length; i++) {
ret.push(array[i]);
}
}
return ret;
}
export function arrangeArrayWithNewStartIndex(array, startIndex) { export function arrangeArrayWithNewStartIndex(array, startIndex) {
if (startIndex <= 0 || startIndex >= array.length) { if (startIndex <= 0 || startIndex >= array.length) {
return array; return array;
+59 -1
View File
@@ -4,11 +4,13 @@ import { defaultLanguage, allLanguages } from '@/locales/index.js';
import datetime from '@/consts/datetime.js'; import datetime from '@/consts/datetime.js';
import timezone from '@/consts/timezone.js'; import timezone from '@/consts/timezone.js';
import currency from '@/consts/currency.js'; import currency from '@/consts/currency.js';
import category from '@/consts/category.js';
import statistics from '@/consts/statistics.js'; import statistics from '@/consts/statistics.js';
import { import {
isString, isString,
isNumber isNumber,
copyArrayTo
} from './common.js'; } from './common.js';
import { import {
@@ -747,6 +749,61 @@ function getAllTransactionEditScopeTypes(translateFn) {
}]; }];
} }
function getAllTransactionDefaultCategories(categoryType, locale, translateFn) {
const allCategories = {};
const categoryTypes = [];
if (categoryType === 0) {
for (let i = category.allCategoryTypes.Income; i <= category.allCategoryTypes.Transfer; i++) {
categoryTypes.push(i);
}
} else {
categoryTypes.push(categoryType);
}
for (let i = 0; i < categoryTypes.length; i++) {
const categories = [];
const categoryType = categoryTypes[i];
let defaultCategories = [];
if (categoryType === category.allCategoryTypes.Income) {
defaultCategories = copyArrayTo(category.defaultIncomeCategories, []);
} else if (categoryType === category.allCategoryTypes.Expense) {
defaultCategories = copyArrayTo(category.defaultExpenseCategories, []);
} else if (categoryType === category.allCategoryTypes.Transfer) {
defaultCategories = copyArrayTo(category.defaultTransferCategories, []);
}
for (let j = 0; j < defaultCategories.length; j++) {
const category = defaultCategories[j];
const submitCategory = {
name: translateFn('category.' + category.name, locale),
type: categoryType,
icon: category.categoryIconId,
color: category.color,
subCategories: []
}
for (let k = 0; k < category.subCategories.length; k++) {
const subCategory = category.subCategories[k];
submitCategory.subCategories.push({
name: translateFn('category.' + subCategory.name, locale),
type: categoryType,
icon: subCategory.categoryIconId,
color: subCategory.color
});
}
categories.push(submitCategory);
}
allCategories[categoryType] = categories;
}
return allCategories;
}
function getAllDisplayExchangeRates(exchangeRatesData, translateFn) { function getAllDisplayExchangeRates(exchangeRatesData, translateFn) {
if (!exchangeRatesData || !exchangeRatesData.exchangeRates) { if (!exchangeRatesData || !exchangeRatesData.exchangeRates) {
return []; return [];
@@ -1069,6 +1126,7 @@ export function i18nFunctions(i18nGlobal) {
getAllStatisticsChartDataTypes: () => getAllStatisticsChartDataTypes(i18nGlobal.t), getAllStatisticsChartDataTypes: () => getAllStatisticsChartDataTypes(i18nGlobal.t),
getAllStatisticsSortingTypes: () => getAllStatisticsSortingTypes(i18nGlobal.t), getAllStatisticsSortingTypes: () => getAllStatisticsSortingTypes(i18nGlobal.t),
getAllTransactionEditScopeTypes: () => getAllTransactionEditScopeTypes(i18nGlobal.t), getAllTransactionEditScopeTypes: () => getAllTransactionEditScopeTypes(i18nGlobal.t),
getAllTransactionDefaultCategories: (categoryType, locale) => getAllTransactionDefaultCategories(categoryType, locale, i18nGlobal.t),
getAllDisplayExchangeRates: (exchangeRatesData) => getAllDisplayExchangeRates(exchangeRatesData, i18nGlobal.t), getAllDisplayExchangeRates: (exchangeRatesData) => getAllDisplayExchangeRates(exchangeRatesData, i18nGlobal.t),
getEnableDisableOptions: () => getEnableDisableOptions(i18nGlobal.t), getEnableDisableOptions: () => getEnableDisableOptions(i18nGlobal.t),
getDisplayCurrency: (value, currencyCode, options) => getDisplayCurrency(value, currencyCode, options, i18nGlobal.t), getDisplayCurrency: (value, currencyCode, options) => getDisplayCurrency(value, currencyCode, options, i18nGlobal.t),
+12 -43
View File
@@ -179,15 +179,15 @@
</v-row> </v-row>
<div class="overflow-y-auto px-3" :class="{ 'disabled': !usePresetCategories || submitting }" style="max-height: 323px"> <div class="overflow-y-auto px-3" :class="{ 'disabled': !usePresetCategories || submitting }" style="max-height: 323px">
<v-row :key="categoryType" v-for="(categories, categoryType) in presetCategories"> <v-row :key="categoryType" v-for="(categories, categoryType) in allPresetCategories">
<v-col cols="12" md="12"> <v-col cols="12" md="12">
<h4 class="mb-3">{{ getCategoryTypeName(categoryType) }}</h4> <h4 class="mb-3">{{ getCategoryTypeName(categoryType) }}</h4>
<v-expansion-panels class="border rounded" variant="accordion" multiple> <v-expansion-panels class="border rounded" variant="accordion" multiple>
<v-expansion-panel :key="idx" v-for="(category, idx) in categories"> <v-expansion-panel :key="idx" v-for="(category, idx) in categories">
<v-expansion-panel-title class="py-0"> <v-expansion-panel-title class="py-0">
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
<span class="ml-3">{{ $t('category.' + category.name) }}</span> <span class="ml-3">{{ category.name }}</span>
</v-expansion-panel-title> </v-expansion-panel-title>
<v-expansion-panel-text v-if="category.subCategories.length"> <v-expansion-panel-text v-if="category.subCategories.length">
<v-list rounded density="comfortable" class="pa-0"> <v-list rounded density="comfortable" class="pa-0">
@@ -195,9 +195,9 @@
v-for="(subCategory, subIdx) in category.subCategories"> v-for="(subCategory, subIdx) in category.subCategories">
<v-list-item> <v-list-item>
<template #prepend> <template #prepend>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color"></ItemIcon>
</template> </template>
<span class="ml-3">{{ $t('category.' + subCategory.name) }}</span> <span class="ml-3">{{ subCategory.name }}</span>
</v-list-item> </v-list-item>
<v-divider v-if="subIdx !== category.subCategories.length - 1"/> <v-divider v-if="subIdx !== category.subCategories.length - 1"/>
</template> </template>
@@ -250,7 +250,7 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import assetConstants from '@/consts/asset.js'; import assetConstants from '@/consts/asset.js';
import categoryConstants from '@/consts/category.js'; import categoryConstants from '@/consts/category.js';
import { copyArrayTo } from '@/lib/common.js'; import { categoriedArrayToPlainArray } from '@/lib/common.js';
import { import {
mdiArrowLeft, mdiArrowLeft,
@@ -280,11 +280,6 @@ export default {
isPasswordVisible: false, isPasswordVisible: false,
isConfirmPasswordVisible: false, isConfirmPasswordVisible: false,
submitting: false, submitting: false,
presetCategories: {
[categoryConstants.allCategoryTypes.Income]: copyArrayTo(categoryConstants.defaultIncomeCategories, []),
[categoryConstants.allCategoryTypes.Expense]: copyArrayTo(categoryConstants.defaultExpenseCategories, []),
[categoryConstants.allCategoryTypes.Transfer]: copyArrayTo(categoryConstants.defaultTransferCategories, [])
},
usePresetCategories: false, usePresetCategories: false,
icons: { icons: {
previous: mdiArrowLeft, previous: mdiArrowLeft,
@@ -309,6 +304,9 @@ export default {
allWeekDays() { allWeekDays() {
return this.$locale.getAllWeekDays(); return this.$locale.getAllWeekDays();
}, },
allPresetCategories() {
return this.$locale.getAllTransactionDefaultCategories(0, this.currentLocale);
},
currentLocale: { currentLocale: {
get: function () { get: function () {
return this.$locale.getCurrentLanguageCode(); return this.$locale.getCurrentLanguageCode();
@@ -418,39 +416,10 @@ export default {
self.submitting = true; self.submitting = true;
const allCategories = []; let submitCategories = [];
if (self.usePresetCategories) { if (self.usePresetCategories) {
for (let categoryType in self.presetCategories) { submitCategories = categoriedArrayToPlainArray(self.allPresetCategories);
if (!Object.prototype.hasOwnProperty.call(self.presetCategories, categoryType)) {
continue;
}
const categories = self.presetCategories[categoryType];
for (let j = 0; j < categories.length; j++) {
const category = categories[j];
const submitCategory = {
name: self.$t('category.' + category.name),
type: parseInt(categoryType),
icon: category.categoryIconId,
color: category.color,
subCategories: []
}
for (let k = 0; k < category.subCategories.length; k++) {
const subCategory = category.subCategories[k];
submitCategory.subCategories.push({
name: self.$t('category.' + subCategory.name),
type: parseInt(categoryType),
icon: subCategory.categoryIconId,
color: subCategory.color
});
}
allCategories.push(submitCategory);
}
}
} }
self.rootStore.register({ self.rootStore.register({
@@ -487,7 +456,7 @@ export default {
} }
self.transactionCategoriesStore.addCategories({ self.transactionCategoriesStore.addCategories({
categories: allCategories categories: submitCategories
}).then(() => { }).then(() => {
self.submitting = false; self.submitting = false;
+12 -43
View File
@@ -127,24 +127,24 @@
</f7-nav-right> </f7-nav-right>
</f7-navbar> </f7-navbar>
<f7-block class="no-padding no-margin" <f7-block class="no-padding no-margin"
:key="categoryType" v-for="(categories, categoryType) in presetCategories"> :key="categoryType" v-for="(categories, categoryType) in allPresetCategories">
<f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryType) }}</f7-block-title> <f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryType) }}</f7-block-title>
<f7-list strong inset dividers v-if="showPresetCategories"> <f7-list strong inset dividers v-if="showPresetCategories">
<f7-list-item :title="$t('category.' + category.name)" <f7-list-item :title="category.name"
:accordion-item="!!category.subCategories.length" :accordion-item="!!category.subCategories.length"
:key="idx" :key="idx"
v-for="(category, idx) in categories"> v-for="(category, idx) in categories">
<template #media> <template #media>
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
</template> </template>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left"> <f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list> <f7-list>
<f7-list-item :title="$t('category.' + subCategory.name)" <f7-list-item :title="subCategory.name"
:key="subIdx" :key="subIdx"
v-for="(subCategory, subIdx) in category.subCategories"> v-for="(subCategory, subIdx) in category.subCategories">
<template #media> <template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color"></ItemIcon>
</template> </template>
</f7-list-item> </f7-list-item>
</f7-list> </f7-list>
@@ -181,7 +181,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import categoryConstants from '@/consts/category.js'; import categoryConstants from '@/consts/category.js';
import { getNameByKeyValue, copyArrayTo } from '@/lib/common.js'; import { getNameByKeyValue, categoriedArrayToPlainArray } from '@/lib/common.js';
export default { export default {
props: [ props: [
@@ -203,11 +203,6 @@ export default {
firstDayOfWeek: settingsStore.localeDefaultSettings.firstDayOfWeek, firstDayOfWeek: settingsStore.localeDefaultSettings.firstDayOfWeek,
}, },
submitting: false, submitting: false,
presetCategories: {
[categoryConstants.allCategoryTypes.Income]: copyArrayTo(categoryConstants.defaultIncomeCategories, []),
[categoryConstants.allCategoryTypes.Expense]: copyArrayTo(categoryConstants.defaultExpenseCategories, []),
[categoryConstants.allCategoryTypes.Transfer]: copyArrayTo(categoryConstants.defaultTransferCategories, [])
},
usePresetCategories: false, usePresetCategories: false,
showPresetCategories: false, showPresetCategories: false,
showPresetCategoriesMoreActionSheet: false, showPresetCategoriesMoreActionSheet: false,
@@ -225,6 +220,9 @@ export default {
allWeekDays() { allWeekDays() {
return this.$locale.getAllWeekDays(); return this.$locale.getAllWeekDays();
}, },
allPresetCategories() {
return this.$locale.getAllTransactionDefaultCategories(0, this.currentLocale);
},
currentLocale: { currentLocale: {
get: function () { get: function () {
return this.$locale.getCurrentLanguageCode(); return this.$locale.getCurrentLanguageCode();
@@ -305,39 +303,10 @@ export default {
self.submitting = true; self.submitting = true;
self.$showLoading(() => self.submitting); self.$showLoading(() => self.submitting);
const allCategories = []; let submitCategories = [];
if (self.usePresetCategories) { if (self.usePresetCategories) {
for (let categoryType in self.presetCategories) { submitCategories = categoriedArrayToPlainArray(self.allPresetCategories);
if (!Object.prototype.hasOwnProperty.call(self.presetCategories, categoryType)) {
continue;
}
const categories = self.presetCategories[categoryType];
for (let j = 0; j < categories.length; j++) {
const category = categories[j];
const submitCategory = {
name: self.$t('category.' + category.name),
type: parseInt(categoryType),
icon: category.categoryIconId,
color: category.color,
subCategories: []
}
for (let k = 0; k < category.subCategories.length; k++) {
const subCategory = category.subCategories[k];
submitCategory.subCategories.push({
name: self.$t('category.' + subCategory.name),
type: parseInt(categoryType),
icon: subCategory.categoryIconId,
color: subCategory.color
});
}
allCategories.push(submitCategory);
}
}
} }
self.rootStore.register({ self.rootStore.register({
@@ -376,7 +345,7 @@ export default {
} }
self.transactionCategoriesStore.addCategories({ self.transactionCategoriesStore.addCategories({
categories: allCategories categories: submitCategories
}).then(() => { }).then(() => {
self.submitting = false; self.submitting = false;
self.$hideLoading(); self.$hideLoading();
+21 -70
View File
@@ -4,30 +4,30 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left> <f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t('Default Categories')"></f7-nav-title> <f7-nav-title :title="$t('Default Categories')"></f7-nav-title>
<f7-nav-right> <f7-nav-right>
<f7-link icon-f7="ellipsis" v-if="allCategories && allCategories.length" @click="showMoreActionSheet = true"></f7-link> <f7-link icon-f7="ellipsis" v-if="isPresetHasCategories" @click="showMoreActionSheet = true"></f7-link>
<f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" v-if="allCategories && allCategories.length" @click="save"></f7-link> <f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" v-if="isPresetHasCategories" @click="save"></f7-link>
</f7-nav-right> </f7-nav-right>
</f7-navbar> </f7-navbar>
<f7-block class="no-padding no-margin" :key="categoryInfo.type" v-for="categoryInfo in allCategories"> <f7-block class="no-padding no-margin" :key="categoryType" v-for="(categories, categoryType) in allPresetCategories">
<f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryInfo.type) }}</f7-block-title> <f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryType) }}</f7-block-title>
<f7-list strong inset dividers class="margin-top"> <f7-list strong inset dividers class="margin-top">
<f7-list-item :title="$t('category.' + category.name, currentLocale)" <f7-list-item :title="category.name"
:accordion-item="!!category.subCategories.length" :accordion-item="!!category.subCategories.length"
:key="idx" :key="idx"
v-for="(category, idx) in categoryInfo.categories"> v-for="(category, idx) in categories">
<template #media> <template #media>
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
</template> </template>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left"> <f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list> <f7-list>
<f7-list-item :title="$t('category.' + subCategory.name, currentLocale)" <f7-list-item :title="subCategory.name"
:key="subIdx" :key="subIdx"
v-for="(subCategory, subIdx) in category.subCategories"> v-for="(subCategory, subIdx) in category.subCategories">
<template #media> <template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon> <ItemIcon icon-type="category" :icon-id="subCategory.icon" :color="subCategory.color"></ItemIcon>
</template> </template>
</f7-list-item> </f7-list-item>
</f7-list> </f7-list>
@@ -59,7 +59,7 @@ import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import categoryConstants from '@/consts/category.js'; import categoryConstants from '@/consts/category.js';
import { copyArrayTo } from '@/lib/common.js'; import { getObjectOwnFieldCount, categoriedArrayToPlainArray } from '@/lib/common.js';
export default { export default {
props: [ props: [
@@ -73,7 +73,6 @@ export default {
loadingError: null, loadingError: null,
currentLocale: self.$locale.getCurrentLanguageCode(), currentLocale: self.$locale.getCurrentLanguageCode(),
categoryType: 0, categoryType: 0,
allCategories: [],
submitting: false, submitting: false,
showMoreActionSheet: false, showMoreActionSheet: false,
showChangeLocaleSheet: false showChangeLocaleSheet: false
@@ -83,6 +82,12 @@ export default {
...mapStores(useTransactionCategoriesStore), ...mapStores(useTransactionCategoriesStore),
allLanguages() { allLanguages() {
return this.$locale.getAllLanguageInfos(); return this.$locale.getAllLanguageInfos();
},
allPresetCategories() {
return this.$locale.getAllTransactionDefaultCategories(this.categoryType, this.currentLocale);
},
isPresetHasCategories() {
return getObjectOwnFieldCount(this.allPresetCategories);
} }
}, },
created() { created() {
@@ -97,39 +102,12 @@ export default {
self.categoryType !== categoryConstants.allCategoryTypes.Transfer) { self.categoryType !== categoryConstants.allCategoryTypes.Transfer) {
self.$toast('Parameter Invalid'); self.$toast('Parameter Invalid');
self.loadingError = 'Parameter Invalid'; self.loadingError = 'Parameter Invalid';
return;
}
if (self.categoryType === 0) {
for (let i = 1; i <= 3; i++) {
self.allCategories.push({
type: i,
categories: copyArrayTo(self.getDefaultCategories(i), [])
});
}
} else {
self.allCategories.push({
type: self.categoryType,
categories: copyArrayTo(self.getDefaultCategories(self.categoryType), [])
});
} }
}, },
methods: { methods: {
onPageAfterIn() { onPageAfterIn() {
this.$routeBackOnError(this.f7router, 'loadingError'); this.$routeBackOnError(this.f7router, 'loadingError');
}, },
getDefaultCategories(categoryType) {
switch (categoryType) {
case categoryConstants.allCategoryTypes.Income:
return categoryConstants.defaultIncomeCategories;
case categoryConstants.allCategoryTypes.Expense:
return categoryConstants.defaultExpenseCategories;
case categoryConstants.allCategoryTypes.Transfer:
return categoryConstants.defaultTransferCategories;
default:
return [];
}
},
save() { save() {
const self = this; const self = this;
const router = self.f7router; const router = self.f7router;
@@ -137,37 +115,10 @@ export default {
self.submitting = true; self.submitting = true;
self.$showLoading(() => self.submitting); self.$showLoading(() => self.submitting);
const categories = []; const submitCategories = categoriedArrayToPlainArray(self.allPresetCategories);
for (let i = 0; i < self.allCategories.length; i++) {
const categoryInfo = self.allCategories[i];
for (let j = 0; j < categoryInfo.categories.length; j++) {
const category = categoryInfo.categories[j];
const submitCategory = {
name: self.$t('category.' + category.name, self.currentLocale),
type: categoryInfo.type,
icon: category.categoryIconId,
color: category.color,
subCategories: []
}
for (let k = 0; k < category.subCategories.length; k++) {
const subCategory = category.subCategories[k];
submitCategory.subCategories.push({
name: self.$t('category.' + subCategory.name, self.currentLocale),
type: categoryInfo.type,
icon: subCategory.categoryIconId,
color: subCategory.color
});
}
categories.push(submitCategory);
}
}
self.transactionCategoriesStore.addCategories({ self.transactionCategoriesStore.addCategories({
categories: categories categories: submitCategories
}).then(() => { }).then(() => {
self.submitting = false; self.submitting = false;
self.$hideLoading(); self.$hideLoading();
@@ -185,11 +136,11 @@ export default {
}, },
getCategoryTypeName(categoryType) { getCategoryTypeName(categoryType) {
switch (categoryType) { switch (categoryType) {
case categoryConstants.allCategoryTypes.Income: case categoryConstants.allCategoryTypes.Income.toString():
return this.$t('Income Categories'); return this.$t('Income Categories');
case categoryConstants.allCategoryTypes.Expense: case categoryConstants.allCategoryTypes.Expense.toString():
return this.$t('Expense Categories'); return this.$t('Expense Categories');
case categoryConstants.allCategoryTypes.Transfer: case categoryConstants.allCategoryTypes.Transfer.toString():
return this.$t('Transfer Categories'); return this.$t('Transfer Categories');
default: default:
return this.$t('Transaction Categories'); return this.$t('Transaction Categories');