mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-21 10:14:26 +08:00
migrate transaction category edit page to composition API and typescript
This commit is contained in:
@@ -127,6 +127,16 @@ export class TransactionCategory implements TransactionCategoryInfoResponse {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static findNameById(categories: TransactionCategory[], id: string): string | null {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.id === id) {
|
||||||
|
return category.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static createNewCategory(type?: CategoryType, parentId?: string): TransactionCategory {
|
public static createNewCategory(type?: CategoryType, parentId?: string): TransactionCategory {
|
||||||
return new TransactionCategory('', '', parentId || '0', type || CategoryType.Income, DEFAULT_CATEGORY_ICON_ID, DEFAULT_CATEGORY_COLOR, '', 0, true);
|
return new TransactionCategory('', '', parentId || '0', type || CategoryType.Income, DEFAULT_CATEGORY_ICON_ID, DEFAULT_CATEGORY_COLOR, '', 0, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||||
|
|
||||||
|
import { CategoryType } from '@/core/category.ts';
|
||||||
|
import { TransactionCategory } from '@/models/transaction_category.ts';
|
||||||
|
|
||||||
|
import { allVisiblePrimaryTransactionCategoriesByType } from '@/lib/category.ts';
|
||||||
|
|
||||||
|
export function useCategoryEditPageBase(type?: CategoryType, parentId?: string) {
|
||||||
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||||
|
|
||||||
|
const editCategoryId = ref<string | null>(null);
|
||||||
|
const clientSessionId = ref<string>('');
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const submitting = ref<boolean>(false);
|
||||||
|
const category = ref<TransactionCategory>(TransactionCategory.createNewCategory(type, parentId));
|
||||||
|
|
||||||
|
const allAvailableCategories = computed<TransactionCategory[]>(() => allVisiblePrimaryTransactionCategoriesByType(transactionCategoriesStore.allTransactionCategories, category.value.type));
|
||||||
|
|
||||||
|
const title = computed<string>(() => {
|
||||||
|
if (!editCategoryId.value) {
|
||||||
|
if (category.value.parentId === '0') {
|
||||||
|
return 'Add Primary Category';
|
||||||
|
} else {
|
||||||
|
return 'Add Secondary Category';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'Edit Category';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveButtonTitle = computed<string>(() => {
|
||||||
|
if (!editCategoryId.value) {
|
||||||
|
return 'Add';
|
||||||
|
} else {
|
||||||
|
return 'Save';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputEmptyProblemMessage = computed<string | null>(() => {
|
||||||
|
if (!category.value.name) {
|
||||||
|
return 'Category name cannot be blank';
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputIsEmpty = computed<boolean>(() => !!inputEmptyProblemMessage.value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// states
|
||||||
|
editCategoryId,
|
||||||
|
clientSessionId,
|
||||||
|
loading,
|
||||||
|
submitting,
|
||||||
|
category,
|
||||||
|
// computed states
|
||||||
|
allAvailableCategories,
|
||||||
|
title,
|
||||||
|
saveButtonTitle,
|
||||||
|
inputEmptyProblemMessage,
|
||||||
|
inputIsEmpty
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<v-card class="pa-2 pa-sm-4 pa-md-8">
|
<v-card class="pa-2 pa-sm-4 pa-md-8">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="d-flex align-center justify-center">
|
<div class="d-flex align-center justify-center">
|
||||||
<h4 class="text-h4">{{ $t(title) }}</h4>
|
<h4 class="text-h4">{{ tt(title) }}</h4>
|
||||||
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
type="text"
|
type="text"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
:disabled="loading || submitting"
|
:disabled="loading || submitting"
|
||||||
:label="$t('Category Name')"
|
:label="tt('Category Name')"
|
||||||
:placeholder="$t('Category Name')"
|
:placeholder="tt('Category Name')"
|
||||||
v-model="category.name"
|
v-model="category.name"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
item-value="id"
|
item-value="id"
|
||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
:disabled="loading || submitting"
|
:disabled="loading || submitting"
|
||||||
:label="$t('Primary Category')"
|
:label="tt('Primary Category')"
|
||||||
:placeholder="$t('Primary Category')"
|
:placeholder="tt('Primary Category')"
|
||||||
:items="allAvailableCategories"
|
:items="allAvailableCategories"
|
||||||
:no-data-text="$t('No available primary category')"
|
:no-data-text="tt('No available primary category')"
|
||||||
v-model="category.parentId"
|
v-model="category.parentId"
|
||||||
>
|
>
|
||||||
<template #item="{ props, item }">
|
<template #item="{ props, item }">
|
||||||
@@ -47,15 +47,15 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<icon-select icon-type="category"
|
<icon-select icon-type="category"
|
||||||
:all-icon-infos="allCategoryIcons"
|
:all-icon-infos="ALL_CATEGORY_ICONS"
|
||||||
:label="$t('Category Icon')"
|
:label="tt('Category Icon')"
|
||||||
:color="category.color"
|
:color="category.color"
|
||||||
:disabled="loading || submitting"
|
:disabled="loading || submitting"
|
||||||
v-model="category.icon" />
|
v-model="category.icon" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<color-select :all-color-infos="allCategoryColors"
|
<color-select :all-color-infos="ALL_CATEGORY_COLORS"
|
||||||
:label="$t('Category Color')"
|
:label="tt('Category Color')"
|
||||||
:disabled="loading || submitting"
|
:disabled="loading || submitting"
|
||||||
v-model="category.color" />
|
v-model="category.color" />
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -65,14 +65,14 @@
|
|||||||
persistent-placeholder
|
persistent-placeholder
|
||||||
rows="3"
|
rows="3"
|
||||||
:disabled="loading || submitting"
|
:disabled="loading || submitting"
|
||||||
:label="$t('Description')"
|
:label="tt('Description')"
|
||||||
:placeholder="$t('Your category description (optional)')"
|
:placeholder="tt('Your category description (optional)')"
|
||||||
v-model="category.comment"
|
v-model="category.comment"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col class="py-0" cols="12" md="12" v-if="editCategoryId">
|
<v-col class="py-0" cols="12" md="12" v-if="editCategoryId">
|
||||||
<v-switch :disabled="loading || submitting"
|
<v-switch :disabled="loading || submitting"
|
||||||
:label="$t('Visible')" v-model="category.visible"/>
|
:label="tt('Visible')" v-model="category.visible"/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
@@ -80,11 +80,11 @@
|
|||||||
<v-card-text class="overflow-y-visible">
|
<v-card-text class="overflow-y-visible">
|
||||||
<div class="w-100 d-flex justify-center mt-2 mt-sm-4 mt-md-6 gap-4">
|
<div class="w-100 d-flex justify-center mt-2 mt-sm-4 mt-md-6 gap-4">
|
||||||
<v-btn :disabled="inputIsEmpty || loading || submitting" @click="save">
|
<v-btn :disabled="inputIsEmpty || loading || submitting" @click="save">
|
||||||
{{ $t(saveButtonTitle) }}
|
{{ tt(saveButtonTitle) }}
|
||||||
<v-progress-circular indeterminate size="22" class="ml-2" v-if="submitting"></v-progress-circular>
|
<v-progress-circular indeterminate size="22" class="ml-2" v-if="submitting"></v-progress-circular>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="secondary" variant="tonal"
|
<v-btn color="secondary" variant="tonal"
|
||||||
:disabled="loading || submitting" @click="cancel">{{ $t('Cancel') }}</v-btn>
|
:disabled="loading || submitting" @click="cancel">{{ tt('Cancel') }}</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -93,8 +93,14 @@
|
|||||||
<snack-bar ref="snackbar" />
|
<snack-bar ref="snackbar" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapStores } from 'pinia';
|
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||||
|
|
||||||
|
import { ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
import { useCategoryEditPageBase } from '@/views/base/categories/CategoryEditPageBase.ts';
|
||||||
|
|
||||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||||
|
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { CategoryType } from '@/core/category.ts';
|
||||||
@@ -103,176 +109,134 @@ import { ALL_CATEGORY_COLORS } from '@/consts/color.ts';
|
|||||||
import { TransactionCategory } from '@/models/transaction_category.ts';
|
import { TransactionCategory } from '@/models/transaction_category.ts';
|
||||||
|
|
||||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||||
import { allVisiblePrimaryTransactionCategoriesByType } from '@/lib/category.ts';
|
|
||||||
|
|
||||||
export default {
|
interface TransactionCategoryEditRespose {
|
||||||
props: [
|
message: string;
|
||||||
'persistent',
|
}
|
||||||
'show'
|
|
||||||
],
|
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||||
expose: [
|
|
||||||
'open'
|
defineProps<{
|
||||||
],
|
persistent?: boolean;
|
||||||
data() {
|
}>();
|
||||||
return {
|
|
||||||
showState: false,
|
const { tt } = useI18n();
|
||||||
editCategoryId: null,
|
const {
|
||||||
clientSessionId: '',
|
editCategoryId,
|
||||||
loading: false,
|
clientSessionId,
|
||||||
category: TransactionCategory.createNewCategory(),
|
loading,
|
||||||
submitting: false,
|
submitting,
|
||||||
resolve: null,
|
category,
|
||||||
reject: null
|
allAvailableCategories,
|
||||||
};
|
title,
|
||||||
},
|
saveButtonTitle,
|
||||||
computed: {
|
inputEmptyProblemMessage,
|
||||||
...mapStores(useTransactionCategoriesStore),
|
inputIsEmpty
|
||||||
allAvailableCategories() {
|
} = useCategoryEditPageBase();
|
||||||
return allVisiblePrimaryTransactionCategoriesByType(this.transactionCategoriesStore.allTransactionCategories, this.category.type);
|
|
||||||
},
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||||
title() {
|
|
||||||
if (!this.editCategoryId) {
|
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||||
if (this.category.parentId === '0') {
|
|
||||||
return 'Add Primary Category';
|
const showState = ref<boolean>(false);
|
||||||
} else {
|
|
||||||
return 'Add Secondary Category';
|
let resolveFunc: ((value: TransactionCategoryEditRespose) => void) | null = null;
|
||||||
}
|
let rejectFunc: ((reason?: unknown) => void) | null = null;
|
||||||
} else {
|
|
||||||
return 'Edit Category';
|
defineExpose({
|
||||||
}
|
open
|
||||||
},
|
});
|
||||||
saveButtonTitle() {
|
|
||||||
if (!this.editCategoryId) {
|
function open(options: { id?: string; parentId?: string; type?: CategoryType; currentCategory?: TransactionCategory }): Promise<TransactionCategoryEditRespose> {
|
||||||
return 'Add';
|
showState.value = true;
|
||||||
} else {
|
loading.value = true;
|
||||||
return 'Save';
|
submitting.value = false;
|
||||||
}
|
|
||||||
},
|
const newTransactionCategory = TransactionCategory.createNewCategory();
|
||||||
allCategoryIcons() {
|
category.value.from(newTransactionCategory);
|
||||||
return ALL_CATEGORY_ICONS;
|
|
||||||
},
|
if (options.id) {
|
||||||
allCategoryColors() {
|
if (options.currentCategory) {
|
||||||
return ALL_CATEGORY_COLORS;
|
category.value.from(options.currentCategory);
|
||||||
},
|
|
||||||
inputIsEmpty() {
|
|
||||||
return !!this.inputEmptyProblemMessage;
|
|
||||||
},
|
|
||||||
inputEmptyProblemMessage() {
|
|
||||||
if (!this.category.name) {
|
|
||||||
return 'Category name cannot be blank';
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
open(options) {
|
|
||||||
const self = this;
|
|
||||||
self.showState = true;
|
|
||||||
self.loading = true;
|
|
||||||
self.submitting = false;
|
|
||||||
|
|
||||||
const newTransactionCategory = TransactionCategory.createNewCategory();
|
editCategoryId.value = options.id;
|
||||||
self.category.from(newTransactionCategory);
|
transactionCategoriesStore.getCategory({
|
||||||
|
categoryId: editCategoryId.value
|
||||||
|
}).then(response => {
|
||||||
|
category.value.from(response);
|
||||||
|
loading.value = false;
|
||||||
|
}).catch(error => {
|
||||||
|
loading.value = false;
|
||||||
|
showState.value = false;
|
||||||
|
|
||||||
if (options.id) {
|
if (!error.processed) {
|
||||||
if (options.currentCategory) {
|
snackbar.value?.showError(error);
|
||||||
self.category.from(options.currentCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.editCategoryId = options.id;
|
|
||||||
self.transactionCategoriesStore.getCategory({
|
|
||||||
categoryId: self.editCategoryId
|
|
||||||
}).then(category => {
|
|
||||||
self.category.from(category);
|
|
||||||
self.loading = false;
|
|
||||||
}).catch(error => {
|
|
||||||
self.loading = false;
|
|
||||||
self.showState = false;
|
|
||||||
|
|
||||||
if (!error.processed) {
|
|
||||||
if (self.reject) {
|
|
||||||
self.reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (options.parentId) {
|
|
||||||
self.editCategoryId = null;
|
|
||||||
|
|
||||||
const categoryType = parseInt(options.type);
|
|
||||||
|
|
||||||
if (categoryType !== CategoryType.Income &&
|
|
||||||
categoryType !== CategoryType.Expense &&
|
|
||||||
categoryType !== CategoryType.Transfer) {
|
|
||||||
self.loading = false;
|
|
||||||
self.showState = false;
|
|
||||||
|
|
||||||
if (self.reject) {
|
|
||||||
self.reject('Parameter Invalid');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.category.type = categoryType;
|
|
||||||
self.category.parentId = options.parentId;
|
|
||||||
|
|
||||||
self.clientSessionId = generateRandomUUID();
|
|
||||||
self.loading = false;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
} else if (options.parentId) {
|
||||||
|
editCategoryId.value = null;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
const categoryType = options.type;
|
||||||
self.resolve = resolve;
|
|
||||||
self.reject = reject;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
const problemMessage = self.inputEmptyProblemMessage;
|
if (categoryType !== CategoryType.Income &&
|
||||||
|
categoryType !== CategoryType.Expense &&
|
||||||
|
categoryType !== CategoryType.Transfer) {
|
||||||
|
loading.value = false;
|
||||||
|
showState.value = false;
|
||||||
|
|
||||||
if (problemMessage) {
|
return Promise.reject('Parameter Invalid');
|
||||||
self.$refs.snackbar.showMessage(problemMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.submitting = true;
|
|
||||||
|
|
||||||
self.transactionCategoriesStore.saveCategory({
|
|
||||||
category: self.category,
|
|
||||||
isEdit: !!self.editCategoryId,
|
|
||||||
clientSessionId: self.clientSessionId
|
|
||||||
}).then(() => {
|
|
||||||
self.submitting = false;
|
|
||||||
|
|
||||||
let message = 'You have saved this category';
|
|
||||||
|
|
||||||
if (!self.editCategoryId) {
|
|
||||||
message = 'You have added a new category';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.resolve) {
|
|
||||||
self.resolve({
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.showState = false;
|
|
||||||
}).catch(error => {
|
|
||||||
self.submitting = false;
|
|
||||||
|
|
||||||
if (!error.processed) {
|
|
||||||
self.$refs.snackbar.showError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
cancel() {
|
|
||||||
if (this.reject) {
|
|
||||||
this.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showState = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
category.value.type = categoryType;
|
||||||
|
category.value.parentId = options.parentId;
|
||||||
|
|
||||||
|
clientSessionId.value = generateRandomUUID();
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolveFunc = resolve;
|
||||||
|
rejectFunc = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(): void {
|
||||||
|
const problemMessage = inputEmptyProblemMessage.value;
|
||||||
|
|
||||||
|
if (problemMessage) {
|
||||||
|
snackbar.value?.showMessage(problemMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
|
||||||
|
transactionCategoriesStore.saveCategory({
|
||||||
|
category: category.value,
|
||||||
|
isEdit: !!editCategoryId.value,
|
||||||
|
clientSessionId: clientSessionId.value
|
||||||
|
}).then(() => {
|
||||||
|
submitting.value = false;
|
||||||
|
|
||||||
|
let message = 'You have saved this category';
|
||||||
|
|
||||||
|
if (!editCategoryId.value) {
|
||||||
|
message = 'You have added a new category';
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveFunc?.({ message });
|
||||||
|
showState.value = false;
|
||||||
|
}).catch(error => {
|
||||||
|
submitting.value = false;
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
snackbar.value?.showError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel(): void {
|
||||||
|
rejectFunc?.();
|
||||||
|
showState.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<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(title)"></f7-nav-title>
|
<f7-nav-title :title="tt(title)"></f7-nav-title>
|
||||||
<f7-nav-right>
|
<f7-nav-right>
|
||||||
<f7-link :class="{ 'disabled': inputIsEmpty || submitting }" :text="$t(saveButtonTitle)" @click="save"></f7-link>
|
<f7-link :class="{ 'disabled': inputIsEmpty || submitting }" :text="tt(saveButtonTitle)" @click="save"></f7-link>
|
||||||
</f7-nav-right>
|
</f7-nav-right>
|
||||||
</f7-navbar>
|
</f7-navbar>
|
||||||
|
|
||||||
@@ -57,15 +57,15 @@
|
|||||||
<f7-list-input
|
<f7-list-input
|
||||||
type="text"
|
type="text"
|
||||||
clear-button
|
clear-button
|
||||||
:label="$t('Category Name')"
|
:label="tt('Category Name')"
|
||||||
:placeholder="$t('Your category name')"
|
:placeholder="tt('Your category name')"
|
||||||
v-model:value="category.name"
|
v-model:value="category.name"
|
||||||
></f7-list-input>
|
></f7-list-input>
|
||||||
|
|
||||||
<f7-list-item
|
<f7-list-item
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
class="list-item-with-header-and-title"
|
class="list-item-with-header-and-title"
|
||||||
:header="$t('Primary Category')"
|
:header="tt('Primary Category')"
|
||||||
:title="getPrimaryCategoryName(category.parentId)"
|
:title="getPrimaryCategoryName(category.parentId)"
|
||||||
@click="showPrimaryCategorySheet = true"
|
@click="showPrimaryCategorySheet = true"
|
||||||
v-if="editCategoryId && category.parentId && category.parentId !== '0'"
|
v-if="editCategoryId && category.parentId && category.parentId !== '0'"
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="item-inner">
|
<div class="item-inner">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<span>{{ $t('Category Icon') }}</span>
|
<span>{{ tt('Category Icon') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-title">
|
<div class="item-title">
|
||||||
<div class="list-item-custom-title no-padding">
|
<div class="list-item-custom-title no-padding">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<icon-selection-sheet :all-icon-infos="allCategoryIcons"
|
<icon-selection-sheet :all-icon-infos="ALL_CATEGORY_ICONS"
|
||||||
:color="category.color"
|
:color="category.color"
|
||||||
v-model:show="showIconSelectionSheet"
|
v-model:show="showIconSelectionSheet"
|
||||||
v-model="category.icon"
|
v-model="category.icon"
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="item-inner">
|
<div class="item-inner">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<span>{{ $t('Category Color') }}</span>
|
<span>{{ tt('Category Color') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-title">
|
<div class="item-title">
|
||||||
<div class="list-item-custom-title no-padding">
|
<div class="list-item-custom-title no-padding">
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<color-selection-sheet :all-color-infos="allCategoryColors"
|
<color-selection-sheet :all-color-infos="ALL_CATEGORY_COLORS"
|
||||||
v-model:show="showColorSelectionSheet"
|
v-model:show="showColorSelectionSheet"
|
||||||
v-model="category.color"
|
v-model="category.color"
|
||||||
></color-selection-sheet>
|
></color-selection-sheet>
|
||||||
@@ -129,15 +129,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</f7-list-item>
|
</f7-list-item>
|
||||||
|
|
||||||
<f7-list-item :title="$t('Visible')" v-if="editCategoryId">
|
<f7-list-item :title="tt('Visible')" v-if="editCategoryId">
|
||||||
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
|
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
|
||||||
</f7-list-item>
|
</f7-list-item>
|
||||||
|
|
||||||
<f7-list-input
|
<f7-list-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
style="height: auto"
|
style="height: auto"
|
||||||
:label="$t('Description')"
|
:label="tt('Description')"
|
||||||
:placeholder="$t('Your category description (optional)')"
|
:placeholder="tt('Your category description (optional)')"
|
||||||
v-textarea-auto-size
|
v-textarea-auto-size
|
||||||
v-model:value="category.comment"
|
v-model:value="category.comment"
|
||||||
></f7-list-input>
|
></f7-list-input>
|
||||||
@@ -145,8 +145,14 @@
|
|||||||
</f7-page>
|
</f7-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
import { mapStores } from 'pinia';
|
import { ref } from 'vue';
|
||||||
|
import type { Router } from 'framework7/types';
|
||||||
|
|
||||||
|
import { useI18n } from '@/locales/helpers.ts';
|
||||||
|
import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
|
||||||
|
import { useCategoryEditPageBase } from '@/views/base/categories/CategoryEditPageBase.ts';
|
||||||
|
|
||||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||||
|
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { CategoryType } from '@/core/category.ts';
|
||||||
@@ -154,157 +160,121 @@ import { ALL_CATEGORY_ICONS } from '@/consts/icon.ts';
|
|||||||
import { ALL_CATEGORY_COLORS } from '@/consts/color.ts';
|
import { ALL_CATEGORY_COLORS } from '@/consts/color.ts';
|
||||||
import { TransactionCategory } from '@/models/transaction_category.ts';
|
import { TransactionCategory } from '@/models/transaction_category.ts';
|
||||||
|
|
||||||
import { getNameByKeyValue } from '@/lib/common.ts';
|
|
||||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||||
import { allVisiblePrimaryTransactionCategoriesByType } from '@/lib/category.ts';
|
|
||||||
|
|
||||||
export default {
|
const props = defineProps<{
|
||||||
props: [
|
f7route: Router.Route;
|
||||||
'f7route',
|
f7router: Router.Router;
|
||||||
'f7router'
|
}>();
|
||||||
],
|
|
||||||
data() {
|
|
||||||
const query = this.f7route.query;
|
|
||||||
|
|
||||||
return {
|
const query = props.f7route.query;
|
||||||
editCategoryId: null,
|
|
||||||
clientSessionId: '',
|
|
||||||
loading: false,
|
|
||||||
loadingError: null,
|
|
||||||
category: TransactionCategory.createNewCategory(parseInt(query.type), query.parentId),
|
|
||||||
showPrimaryCategorySheet: false,
|
|
||||||
showIconSelectionSheet: false,
|
|
||||||
showColorSelectionSheet: false,
|
|
||||||
submitting: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useTransactionCategoriesStore),
|
|
||||||
allAvailableCategories() {
|
|
||||||
return allVisiblePrimaryTransactionCategoriesByType(this.transactionCategoriesStore.allTransactionCategories, this.category.type);
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
if (!this.editCategoryId) {
|
|
||||||
if (this.category.parentId === '0') {
|
|
||||||
return 'Add Primary Category';
|
|
||||||
} else {
|
|
||||||
return 'Add Secondary Category';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 'Edit Category';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveButtonTitle() {
|
|
||||||
if (!this.editCategoryId) {
|
|
||||||
return 'Add';
|
|
||||||
} else {
|
|
||||||
return 'Save';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
allCategoryIcons() {
|
|
||||||
return ALL_CATEGORY_ICONS;
|
|
||||||
},
|
|
||||||
allCategoryColors() {
|
|
||||||
return ALL_CATEGORY_COLORS;
|
|
||||||
},
|
|
||||||
inputIsEmpty() {
|
|
||||||
return !!this.inputEmptyProblemMessage;
|
|
||||||
},
|
|
||||||
inputEmptyProblemMessage() {
|
|
||||||
if (!this.category.name) {
|
|
||||||
return 'Category name cannot be blank';
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
const self = this;
|
|
||||||
const query = self.f7route.query;
|
|
||||||
|
|
||||||
if (!query.id && !query.parentId) {
|
const { tt } = useI18n();
|
||||||
self.$toast('Parameter Invalid');
|
const { showAlert, showToast, routeBackOnError } = useI18nUIComponents();
|
||||||
self.loadingError = 'Parameter Invalid';
|
const {
|
||||||
|
editCategoryId,
|
||||||
|
clientSessionId,
|
||||||
|
loading,
|
||||||
|
submitting,
|
||||||
|
category,
|
||||||
|
allAvailableCategories,
|
||||||
|
title,
|
||||||
|
saveButtonTitle,
|
||||||
|
inputEmptyProblemMessage,
|
||||||
|
inputIsEmpty
|
||||||
|
} = useCategoryEditPageBase(query['type'] ? parseInt(query['type']) as CategoryType : undefined, query['parentId']);
|
||||||
|
|
||||||
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
||||||
|
|
||||||
|
const loadingError = ref<unknown | null>(null);
|
||||||
|
const showPrimaryCategorySheet = ref<boolean>(false);
|
||||||
|
const showIconSelectionSheet = ref<boolean>(false);
|
||||||
|
const showColorSelectionSheet = ref<boolean>(false);
|
||||||
|
|
||||||
|
function getPrimaryCategoryName(parentId: string): string | null {
|
||||||
|
return TransactionCategory.findNameById(allAvailableCategories.value, parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(): void {
|
||||||
|
if (!query['id'] && !query['parentId']) {
|
||||||
|
showToast('Parameter Invalid');
|
||||||
|
loadingError.value = 'Parameter Invalid';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query['id']) {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
editCategoryId.value = query['id'];
|
||||||
|
transactionCategoriesStore.getCategory({
|
||||||
|
categoryId: editCategoryId.value
|
||||||
|
}).then(response => {
|
||||||
|
category.value.from(response);
|
||||||
|
loading.value = false;
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.processed) {
|
||||||
|
loading.value = false;
|
||||||
|
} else {
|
||||||
|
loadingError.value = error;
|
||||||
|
showToast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (query['parentId']) {
|
||||||
|
const categoryType = query['type'] ? parseInt(query['type']) as CategoryType : undefined;
|
||||||
|
|
||||||
|
if (categoryType !== CategoryType.Income &&
|
||||||
|
categoryType !== CategoryType.Expense &&
|
||||||
|
categoryType !== CategoryType.Transfer) {
|
||||||
|
showToast('Parameter Invalid');
|
||||||
|
loadingError.value = 'Parameter Invalid';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.id) {
|
clientSessionId.value = generateRandomUUID();
|
||||||
self.loading = true;
|
loading.value = false;
|
||||||
|
|
||||||
self.editCategoryId = query.id;
|
|
||||||
self.transactionCategoriesStore.getCategory({
|
|
||||||
categoryId: self.editCategoryId
|
|
||||||
}).then(category => {
|
|
||||||
self.category.from(category);
|
|
||||||
self.loading = false;
|
|
||||||
}).catch(error => {
|
|
||||||
if (error.processed) {
|
|
||||||
self.loading = false;
|
|
||||||
} else {
|
|
||||||
self.loadingError = error;
|
|
||||||
self.$toast(error.message || error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (query.parentId) {
|
|
||||||
const categoryType = parseInt(query.type);
|
|
||||||
|
|
||||||
if (categoryType !== CategoryType.Income &&
|
|
||||||
categoryType !== CategoryType.Expense &&
|
|
||||||
categoryType !== CategoryType.Transfer) {
|
|
||||||
self.$toast('Parameter Invalid');
|
|
||||||
self.loadingError = 'Parameter Invalid';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.clientSessionId = generateRandomUUID();
|
|
||||||
self.loading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onPageAfterIn() {
|
|
||||||
this.$routeBackOnError(this.f7router, 'loadingError');
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
const self = this;
|
|
||||||
const router = self.f7router;
|
|
||||||
|
|
||||||
const problemMessage = self.inputEmptyProblemMessage;
|
|
||||||
|
|
||||||
if (problemMessage) {
|
|
||||||
self.$alert(problemMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.submitting = true;
|
|
||||||
self.$showLoading(() => self.submitting);
|
|
||||||
|
|
||||||
self.transactionCategoriesStore.saveCategory({
|
|
||||||
category: self.category,
|
|
||||||
isEdit: !!self.editCategoryId,
|
|
||||||
clientSessionId: self.clientSessionId
|
|
||||||
}).then(() => {
|
|
||||||
self.submitting = false;
|
|
||||||
self.$hideLoading();
|
|
||||||
|
|
||||||
if (!self.editCategoryId) {
|
|
||||||
self.$toast('You have added a new category');
|
|
||||||
} else {
|
|
||||||
self.$toast('You have saved this category');
|
|
||||||
}
|
|
||||||
|
|
||||||
router.back();
|
|
||||||
}).catch(error => {
|
|
||||||
self.submitting = false;
|
|
||||||
self.$hideLoading();
|
|
||||||
|
|
||||||
if (!error.processed) {
|
|
||||||
self.$toast(error.message || error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getPrimaryCategoryName(parentId) {
|
|
||||||
return getNameByKeyValue(this.allAvailableCategories, parentId, 'id', 'name');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function save(): void {
|
||||||
|
const router = props.f7router;
|
||||||
|
const problemMessage = inputEmptyProblemMessage.value;
|
||||||
|
|
||||||
|
if (problemMessage) {
|
||||||
|
showAlert(problemMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
showLoading(() => submitting.value);
|
||||||
|
|
||||||
|
transactionCategoriesStore.saveCategory({
|
||||||
|
category: category.value,
|
||||||
|
isEdit: !!editCategoryId.value,
|
||||||
|
clientSessionId: clientSessionId.value
|
||||||
|
}).then(() => {
|
||||||
|
submitting.value = false;
|
||||||
|
hideLoading();
|
||||||
|
|
||||||
|
if (!editCategoryId.value) {
|
||||||
|
showToast('You have added a new category');
|
||||||
|
} else {
|
||||||
|
showToast('You have saved this category');
|
||||||
|
}
|
||||||
|
|
||||||
|
router.back();
|
||||||
|
}).catch(error => {
|
||||||
|
submitting.value = false;
|
||||||
|
hideLoading();
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
showToast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageAfterIn(): void {
|
||||||
|
routeBackOnError(props.f7router, loadingError);
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user