Upgrade to vue3 (#16)

* upgrade to vue 3.x and framework7 8.x
* change calendar plugin to vue-datepicker
* disable export button when user does not hava any transaction
* implement new pin code input
* append thousands separator in amount in exchange rates page
This commit is contained in:
mayswind
2023-04-21 01:45:00 +08:00
committed by GitHub
parent 4b0f7d45e8
commit b1c765eb51
89 changed files with 8353 additions and 16671 deletions
+14 -19
View File
@@ -2,31 +2,26 @@
<f7-page ptr @ptr:refresh="reload" @page:afterin="onPageAfterIn">
<f7-navbar :title="$t('Transaction Categories')" :back-link="$t('Back')"></f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Expense" link="#"></f7-list-item>
<f7-list-item title="Income" link="#"></f7-list-item>
<f7-list-item title="Transfer" link="#"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item title="Expense" link="#"></f7-list-item>
<f7-list-item title="Income" link="#"></f7-list-item>
<f7-list-item title="Transfer" link="#"></f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item :title="$t('Expense')" link="/category/list?type=2"></f7-list-item>
<f7-list-item :title="$t('Income')" link="/category/list?type=1"></f7-list-item>
<f7-list-item :title="$t('Transfer')" link="/category/list?type=3"></f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-item :title="$t('Expense')" link="/category/list?type=2"></f7-list-item>
<f7-list-item :title="$t('Income')" link="/category/list?type=1"></f7-list-item>
<f7-list-item :title="$t('Transfer')" link="/category/list?type=3"></f7-list-item>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7router'
],
data() {
return {
loading: true,
@@ -53,7 +48,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
const self = this;
+119 -89
View File
@@ -8,91 +8,134 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-input label="Category Name" placeholder="Your category name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title" header="Category Icon" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Category Color" link="#">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</f7-block>
</f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your category description (optional)"></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-input label="Category Name" placeholder="Your category name"></f7-list-input>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Category Icon</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>Category Color</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"></f7-icon>
</div>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
</f7-list-item>
<f7-list-item class="list-item-toggle" header="Visible" after="True"></f7-list-item>
<f7-list-input label="Description" type="textarea" placeholder="Your category description (optional)"></f7-list-input>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list form>
<f7-list-input
type="text"
clear-button
:label="$t('Category Name')"
:placeholder="$t('Your category name')"
:value="category.name"
@input="category.name = $event.target.value"
></f7-list-input>
<f7-list form strong inset dividers class="margin-top" v-else-if="!loading">
<f7-list-input
type="text"
clear-button
:label="$t('Category Name')"
:placeholder="$t('Your category name')"
v-model:value="category.name"
></f7-list-input>
<f7-list-item class="list-item-with-header-and-title"
key="singleTypeCategoryIconSelection" link="#"
:header="$t('Category Icon')"
@click="category.showIconSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon :icon="category.icon | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<icon-selection-sheet :all-icon-infos="allCategoryIcons"
:show.sync="category.showIconSelectionSheet"
:color="category.color"
v-model="category.icon"
></icon-selection-sheet>
</f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-with-multi-item">
<template #default>
<div class="grid grid-cols-2">
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="category.showIconSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Category Icon') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item class="list-item-with-header-and-title"
key="singleTypeCategoryColorSelection" link="#"
:header="$t('Category Color')"
@click="category.showColorSelectionSheet = true">
<f7-block slot="title" class="list-item-custom-title no-padding">
<f7-icon f7="app_fill"
:style="category.color | categoryIconStyle('var(--default-icon-color)')"></f7-icon>
</f7-block>
<color-selection-sheet :all-color-infos="allCategoryColors"
:show.sync="category.showColorSelectionSheet"
v-model="category.color"
></color-selection-sheet>
</f7-list-item>
<icon-selection-sheet :all-icon-infos="allCategoryIcons"
:color="category.color"
v-model:show="category.showIconSelectionSheet"
v-model="category.icon"
></icon-selection-sheet>
</div>
<div class="list-item-subitem no-chevron">
<a class="item-link" href="#" @click="category.showColorSelectionSheet = true">
<div class="item-content">
<div class="item-inner">
<div class="item-header">
<span>{{ $t('Category Color') }}</span>
</div>
<div class="item-title">
<div class="list-item-custom-title no-padding">
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="category.color"></ItemIcon>
</div>
</div>
</div>
</div>
</a>
<f7-list-item :header="$t('Visible')" v-if="editCategoryId">
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
</f7-list-item>
<color-selection-sheet :all-color-infos="allCategoryColors"
v-model:show="category.showColorSelectionSheet"
v-model="category.color"
></color-selection-sheet>
</div>
</div>
</template>
</f7-list-item>
<f7-list-input
type="textarea"
class="textarea-auto-size"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your category description (optional)')"
:value="category.comment"
@input="category.comment = $event.target.value"
></f7-list-input>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list-item :header="$t('Visible')" v-if="editCategoryId">
<f7-toggle :checked="category.visible" @toggle:change="category.visible = $event"></f7-toggle>
</f7-list-item>
<f7-list-input
type="textarea"
style="height: auto"
:label="$t('Description')"
:placeholder="$t('Your category description (optional)')"
v-textarea-auto-size
v-model:value="category.comment"
></f7-list-input>
</f7-list>
</f7-page>
</template>
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
return {
editCategoryId: null,
@@ -150,7 +193,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
if (!query.id && !query.parentId) {
self.$toast('Parameter Invalid');
@@ -197,16 +240,13 @@ export default {
self.loading = false;
}
},
updated: function () {
this.autoChangeCommentTextareaSize();
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
const problemMessage = self.inputEmptyProblemMessage;
@@ -253,16 +293,6 @@ export default {
self.$toast(error.message || error);
}
});
},
autoChangeCommentTextareaSize() {
const app = this.$f7;
const $$ = app.$;
$$('.textarea-auto-size textarea').each((idx, el) => {
el.scrollTop = 0;
el.style.height = '';
el.style.height = el.scrollHeight + 'px';
});
}
}
}
+74 -70
View File
@@ -10,63 +10,57 @@
</f7-nav-right>
</f7-navbar>
<f7-card class="skeleton-text" v-if="loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item title="Category Name">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item title="Category Name 2">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
<f7-list-item title="Category Name 3">
<f7-icon slot="media" f7="app_fill"></f7-icon>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top skeleton-text" v-if="loading">
<f7-list-item title="Category Name"
:link="hasSubCategories ? '#' : null"
:key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<template #media>
<f7-icon f7="app_fill"></f7-icon>
</template>
</f7-list-item>
</f7-list>
<f7-card v-else-if="!loading">
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list v-if="noAvailableCategory">
<f7-list-item :title="$t('No available category')"></f7-list-item>
<f7-list-button v-if="hasSubCategories"
:title="$t('Add Default Categories')"
:href="'/category/preset?type=' + categoryType"></f7-list-button>
</f7-list>
<f7-list strong inset dividers class="margin-top" v-if="!loading && noAvailableCategory">
<f7-list-item :title="$t('No available category')"></f7-list-item>
<f7-list-button v-if="hasSubCategories"
:title="$t('Add Default Categories')"
:href="'/category/preset?type=' + categoryType"></f7-list-button>
</f7-list>
<f7-list class="category-list" sortable :sortable-enabled="sortable" @sortable:sort="onSort">
<f7-list-item v-for="category in categories"
:key="category.id"
:id="category | categoryDomId"
:title="category.name"
:footer="category.comment"
:link="hasSubCategories ? '/category/list?type=' + categoryType + '&id=' + category.id : null"
v-show="showHidden || !category.hidden"
swipeout @taphold.native="setSortable()">
<f7-icon slot="media"
:icon="category.icon | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
<f7-badge color="gray" class="right-bottom-icon" v-if="category.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</f7-icon>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="category.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(category, !category.hidden)">
<f7-icon :f7="category.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(category)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(category, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers sortable class="margin-top category-list"
:sortable-enabled="sortable"
v-if="!loading"
@sortable:sort="onSort">
<f7-list-item swipeout
:id="getCategoryDomId(category)"
:title="category.name"
:footer="category.comment"
:link="hasSubCategories ? '/category/list?type=' + categoryType + '&id=' + category.id : null"
:key="category.id"
v-for="category in categories"
v-show="showHidden || !category.hidden"
@taphold="setSortable()">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.icon" :color="category.color">
<f7-badge color="gray" class="right-bottom-icon" v-if="category.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
</ItemIcon>
</template>
<f7-swipeout-actions left v-if="sortable">
<f7-swipeout-button :color="category.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
overswipe close @click="hide(category, !category.hidden)">
<f7-icon :f7="category.hidden ? 'eye' : 'eye_slash'"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
<f7-swipeout-actions right v-if="!sortable">
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(category)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(category, false)">
<f7-icon f7="trash"></f7-icon>
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -91,6 +85,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
return {
hasSubCategories: false,
@@ -166,7 +164,7 @@ export default {
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.categoryType = parseInt(query.type);
@@ -207,7 +205,7 @@ export default {
this.reload(null);
}
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
reload(done) {
if (this.sortable) {
@@ -245,12 +243,17 @@ export default {
onSort(event) {
const self = this;
if (!event || !event.el || !event.el.id || event.el.id.indexOf('category_') !== 0) {
this.$toast('Unable to move category');
if (!event || !event.el || !event.el.id) {
self.$toast('Unable to move category');
return;
}
const id = event.el.id.substring(9); // category_
const id = self.parseCategoryIdFromDomId(event.el.id);
if (!id) {
self.$toast('Unable to move category');
return;
}
self.$store.dispatch('changeCategoryDisplayOrder', {
categoryId: id,
@@ -294,7 +297,7 @@ export default {
});
},
edit(category) {
this.$f7router.navigate('/category/edit?id=' + category.id);
this.f7router.navigate('/category/edit?id=' + category.id);
},
hide(category, hidden) {
const self = this;
@@ -316,8 +319,6 @@ export default {
},
remove(category, confirm) {
const self = this;
const app = self.$f7;
const $$ = app.$;
if (!category) {
self.$alert('An error has occurred');
@@ -337,9 +338,7 @@ export default {
self.$store.dispatch('deleteCategory', {
category: category,
beforeResolve: (done) => {
app.swipeout.delete($$(`#${self.$options.filters.categoryDomId(category)}`), () => {
done();
});
self.$ui.onSwipeoutDeleted(self.getCategoryDomId(category), done);
}
}).then(() => {
self.$hideLoading();
@@ -350,11 +349,16 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
categoryDomId(category) {
},
getCategoryDomId(category) {
return 'category_' + category.id;
},
parseCategoryIdFromDomId(domId) {
if (!domId || domId.indexOf('category_') !== 0) {
return null;
}
return domId.substring(9); // category_
}
}
};
+45 -50
View File
@@ -4,44 +4,37 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t('Default Categories')"></f7-nav-title>
<f7-nav-right>
<f7-link icon-f7="ellipsis" @click="showMoreActionSheet = true"></f7-link>
<f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" @click="save"></f7-link>
<f7-link icon-f7="ellipsis" v-if="allCategories && allCategories.length" @click="showMoreActionSheet = true"></f7-link>
<f7-link :text="$t('Save')" :class="{ 'disabled': submitting }" v-if="allCategories && allCategories.length" @click="save"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-card v-for="categoryInfo in allCategories" :key="categoryInfo.type">
<f7-card-header>
<small class="card-header-content">
<span>{{ categoryInfo.type | categoryTypeName($constants.category.allCategoryTypes) | localized }}</span>
</small>
</f7-card-header>
<f7-card-content class="no-safe-areas" :padding="false">
<f7-list>
<f7-list-item v-for="(category, idx) in categoryInfo.categories"
:key="idx"
:accordion-item="!!category.subCategories.length"
:title="$t('category.' + category.name, currentLocale)">
<f7-icon slot="media"
:icon="category.categoryIconId | categoryIcon"
:style="category.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
<f7-block class="no-padding no-margin" :key="categoryInfo.type" v-for="categoryInfo in allCategories">
<f7-block-title class="margin-top margin-horizontal">{{ getCategoryTypeName(categoryInfo.type) }}</f7-block-title>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item v-for="(subCategory, subIdx) in category.subCategories"
:key="subIdx"
:title="$t('category.' + subCategory.name, currentLocale)">
<f7-icon slot="media"
:icon="subCategory.categoryIconId | categoryIcon"
:style="subCategory.color | categoryIconStyle('var(--default-icon-color)')">
</f7-icon>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-card-content>
</f7-card>
<f7-list strong inset dividers class="margin-top">
<f7-list-item :title="$t('category.' + category.name, currentLocale)"
:accordion-item="!!category.subCategories.length"
:key="idx"
v-for="(category, idx) in categoryInfo.categories">
<template #media>
<ItemIcon icon-type="category" :icon-id="category.categoryIconId" :color="category.color"></ItemIcon>
</template>
<f7-accordion-content v-if="category.subCategories.length" class="padding-left">
<f7-list>
<f7-list-item :title="$t('category.' + subCategory.name, currentLocale)"
:key="subIdx"
v-for="(subCategory, subIdx) in category.subCategories">
<template #media>
<ItemIcon icon-type="category" :icon-id="subCategory.categoryIconId" :color="subCategory.color"></ItemIcon>
</template>
</f7-list-item>
</f7-list>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</f7-block>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
@@ -55,7 +48,7 @@
<list-item-selection-sheet value-type="index"
title-field="displayName"
:items="allLanguages"
:show.sync="showChangeLocaleSheet"
v-model:show="showChangeLocaleSheet"
v-model="currentLocale">
</list-item-selection-sheet>
</f7-page>
@@ -63,6 +56,10 @@
<script>
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
@@ -78,12 +75,12 @@ export default {
},
computed: {
allLanguages() {
return this.$locale.getAllLanguages();
return this.$locale.getAllLanguageInfos();
}
},
created() {
const self = this;
const query = self.$f7route.query;
const query = self.f7route.query;
self.categoryType = parseInt(query.type);
@@ -112,7 +109,7 @@ export default {
},
methods: {
onPageAfterIn() {
this.$routeBackOnError('loadingError');
this.$routeBackOnError(this.f7router, 'loadingError');
},
getDefaultCategories(categoryType) {
switch (categoryType) {
@@ -128,7 +125,7 @@ export default {
},
save() {
const self = this;
const router = self.$f7router;
const router = self.f7router;
self.submitting = true;
self.$showLoading(() => self.submitting);
@@ -178,19 +175,17 @@ export default {
self.$toast(error.message || error);
}
});
}
},
filters: {
categoryTypeName(categoryType, allCategoryTypes) {
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case allCategoryTypes.Income:
return 'Income Categories';
case allCategoryTypes.Expense:
return 'Expense Categories';
case allCategoryTypes.Transfer:
return 'Transfer Categories';
case this.$constants.category.allCategoryTypes.Income:
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense:
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer:
return this.$t('Transfer Categories');
default:
return 'Transaction Categories';
return this.$t('Transaction Categories');
}
}
}