migrate transaction category preset page to composition API and typescript
This commit is contained in:
+1
-14
@@ -1,17 +1,4 @@
|
|||||||
import type { ColorValue } from '@/core/color.ts';
|
import type { PresetCategory } from '@/core/category.ts';
|
||||||
|
|
||||||
export interface PresetCategory {
|
|
||||||
readonly name: string;
|
|
||||||
readonly categoryIconId: string;
|
|
||||||
readonly color: ColorValue;
|
|
||||||
readonly subCategories: PresetSubCategory[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PresetSubCategory {
|
|
||||||
readonly name: string;
|
|
||||||
readonly categoryIconId: string;
|
|
||||||
readonly color: ColorValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_EXPENSE_CATEGORIES: PresetCategory[] = [
|
export const DEFAULT_EXPENSE_CATEGORIES: PresetCategory[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,41 @@
|
|||||||
|
import type { ColorValue } from '@/core/color.ts';
|
||||||
|
|
||||||
export enum CategoryType {
|
export enum CategoryType {
|
||||||
Income = 1,
|
Income = 1,
|
||||||
Expense = 2,
|
Expense = 2,
|
||||||
Transfer = 3
|
Transfer = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ALL_CATEGORY_TYPES: CategoryType[] = [
|
||||||
|
CategoryType.Income,
|
||||||
|
CategoryType.Expense,
|
||||||
|
CategoryType.Transfer
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface PresetCategory {
|
||||||
|
readonly name: string;
|
||||||
|
readonly categoryIconId: string;
|
||||||
|
readonly color: ColorValue;
|
||||||
|
readonly subCategories: PresetSubCategory[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PresetSubCategory {
|
||||||
|
readonly name: string;
|
||||||
|
readonly categoryIconId: string;
|
||||||
|
readonly color: ColorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalizedPresetCategory {
|
||||||
|
readonly name: string;
|
||||||
|
readonly type: CategoryType;
|
||||||
|
readonly icon: string;
|
||||||
|
readonly color: ColorValue;
|
||||||
|
readonly subCategories: LocalizedPresetSubCategory[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalizedPresetSubCategory {
|
||||||
|
readonly name: string;
|
||||||
|
readonly type: CategoryType;
|
||||||
|
readonly icon: string;
|
||||||
|
readonly color: ColorValue;
|
||||||
|
}
|
||||||
|
|||||||
+68
-2
@@ -1,7 +1,7 @@
|
|||||||
import { useI18n as useVueI18n } from 'vue-i18n';
|
import { useI18n as useVueI18n } from 'vue-i18n';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
import type { TypeAndName, TypeAndDisplayName, LocalizedSwitchOption } from '@/core/base.ts';
|
import type { PartialRecord, TypeAndName, TypeAndDisplayName, LocalizedSwitchOption } from '@/core/base.ts';
|
||||||
|
|
||||||
import { type LanguageInfo, type LanguageOption, ALL_LANGUAGES, DEFAULT_LANGUAGE } from '@/locales/index.ts';
|
import { type LanguageInfo, type LanguageOption, ALL_LANGUAGES, DEFAULT_LANGUAGE } from '@/locales/index.ts';
|
||||||
|
|
||||||
@@ -49,6 +49,14 @@ import {
|
|||||||
AccountCategory
|
AccountCategory
|
||||||
} from '@/core/account.ts';
|
} from '@/core/account.ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type PresetCategory,
|
||||||
|
type LocalizedPresetCategory,
|
||||||
|
type LocalizedPresetSubCategory,
|
||||||
|
CategoryType,
|
||||||
|
ALL_CATEGORY_TYPES
|
||||||
|
} from '@/core/category.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TransactionEditScopeType,
|
TransactionEditScopeType,
|
||||||
TransactionTagFilterType
|
TransactionTagFilterType
|
||||||
@@ -72,6 +80,7 @@ import type { ErrorResponse } from '@/core/api.ts';
|
|||||||
|
|
||||||
import { UTC_TIMEZONE, ALL_TIMEZONES } from '@/consts/timezone.ts';
|
import { UTC_TIMEZONE, ALL_TIMEZONES } from '@/consts/timezone.ts';
|
||||||
import { ALL_CURRENCIES } from '@/consts/currency.ts';
|
import { ALL_CURRENCIES } from '@/consts/currency.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 { LatestExchangeRateResponse, LocalizedLatestExchangeRate } from '@/models/exchange_rate.ts';
|
import type { LatestExchangeRateResponse, LocalizedLatestExchangeRate } from '@/models/exchange_rate.ts';
|
||||||
@@ -81,7 +90,8 @@ import {
|
|||||||
isObject,
|
isObject,
|
||||||
isString,
|
isString,
|
||||||
isNumber,
|
isNumber,
|
||||||
isBoolean
|
isBoolean,
|
||||||
|
copyArrayTo
|
||||||
} from '@/lib/common.ts';
|
} from '@/lib/common.ts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -842,6 +852,61 @@ export function useI18n() {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllTransactionDefaultCategories(categoryType: 0 | CategoryType, locale: string): PartialRecord<CategoryType, LocalizedPresetCategory[]> {
|
||||||
|
const allCategories: PartialRecord<CategoryType, LocalizedPresetCategory[]> = {};
|
||||||
|
const categoryTypes: CategoryType[] = [];
|
||||||
|
|
||||||
|
if (categoryType === 0) {
|
||||||
|
categoryTypes.push(...ALL_CATEGORY_TYPES);
|
||||||
|
} else {
|
||||||
|
categoryTypes.push(categoryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < categoryTypes.length; i++) {
|
||||||
|
const categories: LocalizedPresetCategory[] = [];
|
||||||
|
const categoryType = categoryTypes[i];
|
||||||
|
let defaultCategories: PresetCategory[] = [];
|
||||||
|
|
||||||
|
if (categoryType === CategoryType.Income) {
|
||||||
|
defaultCategories = copyArrayTo(DEFAULT_INCOME_CATEGORIES, []);
|
||||||
|
} else if (categoryType === CategoryType.Expense) {
|
||||||
|
defaultCategories = copyArrayTo(DEFAULT_EXPENSE_CATEGORIES, []);
|
||||||
|
} else if (categoryType === CategoryType.Transfer) {
|
||||||
|
defaultCategories = copyArrayTo(DEFAULT_TRANSFER_CATEGORIES, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j < defaultCategories.length; j++) {
|
||||||
|
const category = defaultCategories[j];
|
||||||
|
|
||||||
|
const submitCategory: LocalizedPresetCategory = {
|
||||||
|
name: t('category.' + category.name, {}, { locale: locale }),
|
||||||
|
type: categoryType,
|
||||||
|
icon: category.categoryIconId,
|
||||||
|
color: category.color,
|
||||||
|
subCategories: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let k = 0; k < category.subCategories.length; k++) {
|
||||||
|
const subCategory = category.subCategories[k];
|
||||||
|
const submitSubCategory: LocalizedPresetSubCategory = {
|
||||||
|
name: t('category.' + subCategory.name, {}, { locale: locale }),
|
||||||
|
type: categoryType,
|
||||||
|
icon: subCategory.categoryIconId,
|
||||||
|
color: subCategory.color
|
||||||
|
};
|
||||||
|
|
||||||
|
submitCategory.subCategories.push(submitSubCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.push(submitCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
allCategories[categoryType] = categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allCategories;
|
||||||
|
}
|
||||||
|
|
||||||
function getAllDisplayExchangeRates(exchangeRatesData?: LatestExchangeRateResponse): LocalizedLatestExchangeRate[] {
|
function getAllDisplayExchangeRates(exchangeRatesData?: LatestExchangeRateResponse): LocalizedLatestExchangeRate[] {
|
||||||
const availableExchangeRates: LocalizedLatestExchangeRate[] = [];
|
const availableExchangeRates: LocalizedLatestExchangeRate[] = [];
|
||||||
|
|
||||||
@@ -1269,6 +1334,7 @@ export function useI18n() {
|
|||||||
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
|
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
|
||||||
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
|
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
|
||||||
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
|
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
|
||||||
|
getAllTransactionDefaultCategories,
|
||||||
getAllDisplayExchangeRates,
|
getAllDisplayExchangeRates,
|
||||||
// get localized info
|
// get localized info
|
||||||
getMonthShortName,
|
getMonthShortName,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CategoryType } from '@/core/category.ts';
|
import { type LocalizedPresetCategory, CategoryType } from '@/core/category.ts';
|
||||||
import { DEFAULT_CATEGORY_ICON_ID } from '@/consts/icon.ts';
|
import { DEFAULT_CATEGORY_ICON_ID } from '@/consts/icon.ts';
|
||||||
import { DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
import { DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
||||||
|
|
||||||
@@ -131,16 +131,7 @@ export interface TransactionCategoryCreateRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionCategoryCreateBatchRequest {
|
export interface TransactionCategoryCreateBatchRequest {
|
||||||
readonly categories: TransactionCategoryCreateWithSubCategories[];
|
readonly categories: LocalizedPresetCategory[];
|
||||||
}
|
|
||||||
|
|
||||||
export interface TransactionCategoryCreateWithSubCategories {
|
|
||||||
readonly name: string;
|
|
||||||
readonly type: number;
|
|
||||||
readonly icon: string;
|
|
||||||
readonly color: string;
|
|
||||||
readonly comment: string;
|
|
||||||
readonly subCategories: TransactionCategoryCreateRequest[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionCategoryModifyRequest {
|
export interface TransactionCategoryModifyRequest {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<f7-page @page:afterin="onPageAfterIn">
|
<f7-page @page:afterin="onPageAfterIn">
|
||||||
<f7-navbar>
|
<f7-navbar>
|
||||||
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
|
<f7-nav-left :back-link="tt('Back')"></f7-nav-left>
|
||||||
<f7-nav-title :title="$t('Default Categories')"></f7-nav-title>
|
<f7-nav-title :title="tt('Default Categories')"></f7-nav-title>
|
||||||
<f7-nav-right>
|
<f7-nav-right>
|
||||||
<f7-link icon-f7="ellipsis" v-if="isPresetHasCategories" @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="isPresetHasCategories" @click="save"></f7-link>
|
<f7-link :text="tt('Save')" :class="{ 'disabled': submitting }" v-if="isPresetHasCategories" @click="save"></f7-link>
|
||||||
</f7-nav-right>
|
</f7-nav-right>
|
||||||
</f7-navbar>
|
</f7-navbar>
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
|
|
||||||
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
||||||
<f7-actions-group>
|
<f7-actions-group>
|
||||||
<f7-actions-button @click="showChangeLocaleSheet = true">{{ $t('Change Language') }}</f7-actions-button>
|
<f7-actions-button @click="showChangeLocaleSheet = true">{{ tt('Change Language') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
<f7-actions-group>
|
<f7-actions-group>
|
||||||
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
</f7-actions>
|
</f7-actions>
|
||||||
|
|
||||||
@@ -55,98 +55,92 @@
|
|||||||
</f7-page>
|
</f7-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||||
|
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { ref, computed } from 'vue';
|
||||||
|
import type { Router } from 'framework7/types';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
|
||||||
|
|
||||||
|
import type { PartialRecord } from '@/core/base.ts';
|
||||||
|
import type { LanguageOption } from '@/locales/index.ts';
|
||||||
|
import { type LocalizedPresetCategory, CategoryType } from '@/core/category.ts';
|
||||||
import { getObjectOwnFieldCount, categorizedArrayToPlainArray } from '@/lib/common.ts';
|
import { getObjectOwnFieldCount, categorizedArrayToPlainArray } from '@/lib/common.ts';
|
||||||
|
|
||||||
export default {
|
const props = defineProps<{
|
||||||
props: [
|
f7route: Router.Route;
|
||||||
'f7route',
|
f7router: Router.Router;
|
||||||
'f7router'
|
}>();
|
||||||
],
|
|
||||||
data() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
return {
|
const { tt, getCurrentLanguageTag, getAllLanguageOptions, getAllTransactionDefaultCategories } = useI18n();
|
||||||
loadingError: null,
|
const { showToast, routeBackOnError } = useI18nUIComponents();
|
||||||
currentLocale: self.$locale.getCurrentLanguageTag(),
|
|
||||||
categoryType: 0,
|
|
||||||
submitting: false,
|
|
||||||
showMoreActionSheet: false,
|
|
||||||
showChangeLocaleSheet: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useTransactionCategoriesStore),
|
|
||||||
allLanguages() {
|
|
||||||
return this.$locale.getAllLanguageInfoArray(false);
|
|
||||||
},
|
|
||||||
allPresetCategories() {
|
|
||||||
return this.$locale.getAllTransactionDefaultCategories(this.categoryType, this.currentLocale);
|
|
||||||
},
|
|
||||||
isPresetHasCategories() {
|
|
||||||
return getObjectOwnFieldCount(this.allPresetCategories);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
const self = this;
|
|
||||||
const query = self.f7route.query;
|
|
||||||
|
|
||||||
self.categoryType = parseInt(query.type);
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||||
|
|
||||||
if (self.categoryType !== 0 &&
|
const loadingError = ref<unknown | null>(null);
|
||||||
self.categoryType !== CategoryType.Income &&
|
const currentLocale = ref<string>(getCurrentLanguageTag());
|
||||||
self.categoryType !== CategoryType.Expense &&
|
const categoryType = ref<number>(0);
|
||||||
self.categoryType !== CategoryType.Transfer) {
|
const submitting = ref<boolean>(false);
|
||||||
self.$toast('Parameter Invalid');
|
const showMoreActionSheet = ref<boolean>(false);
|
||||||
self.loadingError = 'Parameter Invalid';
|
const showChangeLocaleSheet = ref<boolean>(false);
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onPageAfterIn() {
|
|
||||||
this.$routeBackOnError(this.f7router, 'loadingError');
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
const self = this;
|
|
||||||
const router = self.f7router;
|
|
||||||
|
|
||||||
self.submitting = true;
|
const allLanguages = computed<LanguageOption[]>(() => getAllLanguageOptions(false));
|
||||||
self.$showLoading(() => self.submitting);
|
const allPresetCategories = computed<PartialRecord<CategoryType, LocalizedPresetCategory[]>>(() => getAllTransactionDefaultCategories(categoryType.value, currentLocale.value));
|
||||||
|
const isPresetHasCategories = computed<boolean>(() => getObjectOwnFieldCount(allPresetCategories.value) > 0);
|
||||||
|
|
||||||
const submitCategories = categorizedArrayToPlainArray(self.allPresetCategories);
|
function getCategoryTypeName(categoryType: CategoryType): string {
|
||||||
|
switch (categoryType) {
|
||||||
self.transactionCategoriesStore.addCategories({
|
case CategoryType.Income:
|
||||||
categories: submitCategories
|
return tt('Income Categories');
|
||||||
}).then(() => {
|
case CategoryType.Expense:
|
||||||
self.submitting = false;
|
return tt('Expense Categories');
|
||||||
self.$hideLoading();
|
case CategoryType.Transfer:
|
||||||
|
return tt('Transfer Categories');
|
||||||
self.$toast('You have added preset categories');
|
default:
|
||||||
router.back();
|
return tt('Transaction Categories');
|
||||||
}).catch(error => {
|
|
||||||
self.submitting = false;
|
|
||||||
self.$hideLoading();
|
|
||||||
|
|
||||||
if (!error.processed) {
|
|
||||||
self.$toast(error.message || error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getCategoryTypeName(categoryType) {
|
|
||||||
switch (categoryType) {
|
|
||||||
case CategoryType.Income.toString():
|
|
||||||
return this.$t('Income Categories');
|
|
||||||
case CategoryType.Expense.toString():
|
|
||||||
return this.$t('Expense Categories');
|
|
||||||
case CategoryType.Transfer.toString():
|
|
||||||
return this.$t('Transfer Categories');
|
|
||||||
default:
|
|
||||||
return this.$t('Transaction Categories');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function save(): void {
|
||||||
|
const router = props.f7router;
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
showLoading(() => submitting.value);
|
||||||
|
|
||||||
|
const submitCategories = categorizedArrayToPlainArray(allPresetCategories.value);
|
||||||
|
|
||||||
|
transactionCategoriesStore.addCategories({
|
||||||
|
categories: submitCategories
|
||||||
|
}).then(() => {
|
||||||
|
submitting.value = false;
|
||||||
|
hideLoading();
|
||||||
|
|
||||||
|
showToast('You have added preset categories');
|
||||||
|
router.back();
|
||||||
|
}).catch(error => {
|
||||||
|
submitting.value = false;
|
||||||
|
hideLoading();
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
showToast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageAfterIn(): void {
|
||||||
|
routeBackOnError(props.f7router, loadingError);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = props.f7route.query;
|
||||||
|
categoryType.value = parseInt(query['type'] || '0');
|
||||||
|
|
||||||
|
if (categoryType.value !== 0 &&
|
||||||
|
categoryType.value !== CategoryType.Income &&
|
||||||
|
categoryType.value !== CategoryType.Expense &&
|
||||||
|
categoryType.value !== CategoryType.Transfer) {
|
||||||
|
showToast('Parameter Invalid');
|
||||||
|
loadingError.value = 'Parameter Invalid';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user