use pinia to replace vuex, code refactor

This commit is contained in:
MaysWind
2023-06-10 23:13:31 +08:00
parent 0d84f2857f
commit 46d85e92cd
80 changed files with 4972 additions and 4859 deletions
+52 -13
View File
@@ -23,13 +23,13 @@
"line-awesome": "^1.3.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"pinia": "^2.1.3",
"register-service-worker": "^1.7.2",
"skeleton-elements": "^4.0.1",
"swiper": "^9.3.2",
"ua-parser-js": "^1.0.35",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vuex": "^4.1.0"
"vue-i18n": "^9.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
@@ -5634,6 +5634,56 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pinia": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.3.tgz",
"integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.4.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
@@ -7238,17 +7288,6 @@
"vue": "^3.0.0"
}
},
"node_modules/vuex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+2 -2
View File
@@ -32,13 +32,13 @@
"line-awesome": "^1.3.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"pinia": "^2.1.3",
"register-service-worker": "^1.7.2",
"skeleton-elements": "^4.0.1",
"swiper": "^9.3.2",
"ua-parser-js": "^1.0.35",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vuex": "^4.1.0"
"vue-i18n": "^9.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.0",
+12 -3
View File
@@ -9,6 +9,12 @@
import { f7ready } from 'framework7-vue';
import routes from './router/mobile.js';
import { mapStores } from 'pinia';
import { useTokensStore } from '@/stores/token.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import { isModalShowing } from '@/lib/ui.mobile.js';
export default {
data() {
const self = this;
@@ -81,13 +87,16 @@ export default {
hasBackdrop: undefined
}
},
computed: {
...mapStores(useTokensStore, useExchangeRatesStore),
},
created() {
const self = this;
if (self.$user.isUserLogined()) {
if (!self.$settings.isEnableApplicationLock()) {
// refresh token if user is logined
self.$store.dispatch('refreshTokenAndRevokeOldToken').then(response => {
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
if (response.user && response.user.language) {
self.$locale.setLanguage(response.user.language);
}
@@ -95,7 +104,7 @@ export default {
// auto refresh exchange rates data
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
}
}
@@ -117,7 +126,7 @@ export default {
f7.on('sheetClose', (event) => this.onBackdropChanged(event));
f7.on('pageBeforeOut', () => {
if (this.$ui.isModalShowing()) {
if (isModalShowing()) {
f7.actions.close('.actions-modal.modal-in', false);
f7.dialog.close('.dialog.modal-in', false);
f7.popover.close('.popover.modal-in', false);
@@ -48,6 +48,25 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import datetimeConstants from '@/consts/datetime.js';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.js';
import {
getCurrentUnixTime,
getCurrentDateTime,
getUnixTime,
getLocalDatetimeFromUnixTime,
getTodayFirstUnixTime,
getYear,
getDummyUnixTimeForLocalUsage,
getActualUnixTimeForStore,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getDateRangeByDateType
} from '@/lib/datetime.js';
export default {
props: [
'minTime',
@@ -62,8 +81,8 @@ export default {
],
data() {
const self = this;
let minDate = self.$utilities.getTodayFirstUnixTime();
let maxDate = self.$utilities.getCurrentUnixTime();
let minDate = getTodayFirstUnixTime();
let maxDate = getCurrentUnixTime();
if (self.minTime) {
minDate = self.minTime;
@@ -76,53 +95,54 @@ export default {
return {
yearRange: [
2000,
this.$utilities.getYear(this.$utilities.getCurrentDateTime()) + 1
getYear(getCurrentDateTime()) + 1
],
dateRange: [
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(minDate, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes())),
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(maxDate, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()))
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(minDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(maxDate, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
]
}
},
computed: {
...mapStores(useUserStore),
isDarkMode() {
return this.$root.isDarkMode;
},
firstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
return this.userStore.currentUserFirstDayOfWeek;
},
dayNames() {
return this.$utilities.arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
return arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
},
is24Hour() {
return this.$locale.isLongTime24HourFormat();
return this.$locale.isLongTime24HourFormat(this.userStore);
},
beginDateTime() {
const actualBeginUnixTime = this.$utilities.getActualUnixTimeForStore(this.$utilities.getUnixTime(this.dateRange[0]), this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
return this.$utilities.formatUnixTime(actualBeginUnixTime, this.$locale.getLongDateTimeFormat());
const actualBeginUnixTime = getActualUnixTimeForStore(getUnixTime(this.dateRange[0]), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, actualBeginUnixTime);
},
endDateTime() {
const actualEndUnixTime = this.$utilities.getActualUnixTimeForStore(this.$utilities.getUnixTime(this.dateRange[1]), this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
return this.$utilities.formatUnixTime(actualEndUnixTime, this.$locale.getLongDateTimeFormat());
const actualEndUnixTime = getActualUnixTimeForStore(getUnixTime(this.dateRange[1]), getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, actualEndUnixTime);
},
presetRanges() {
const presetRanges = [];
[
this.$constants.datetime.allDateRanges.Today,
this.$constants.datetime.allDateRanges.LastSevenDays,
this.$constants.datetime.allDateRanges.LastThirtyDays,
this.$constants.datetime.allDateRanges.ThisWeek,
this.$constants.datetime.allDateRanges.ThisMonth,
this.$constants.datetime.allDateRanges.ThisYear
datetimeConstants.allDateRanges.Today,
datetimeConstants.allDateRanges.LastSevenDays,
datetimeConstants.allDateRanges.LastThirtyDays,
datetimeConstants.allDateRanges.ThisWeek,
datetimeConstants.allDateRanges.ThisMonth,
datetimeConstants.allDateRanges.ThisYear
].forEach(dateRangeType => {
const dateRange = this.$utilities.getDateRangeByDateType(dateRangeType.type, this.firstDayOfWeek);
const dateRange = getDateRangeByDateType(dateRangeType.type, this.firstDayOfWeek);
presetRanges.push({
label: this.$t(dateRangeType.name),
range: [
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(dateRange.minTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes())),
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(dateRange.maxTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()))
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes())),
getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(dateRange.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
]
});
});
@@ -133,11 +153,11 @@ export default {
methods: {
onSheetOpen() {
if (this.minTime) {
this.dateRange[0] = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(this.minTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()));
this.dateRange[0] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(this.minTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
}
if (this.maxTime) {
this.dateRange[1] = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(this.maxTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()));
this.dateRange[1] = getLocalDatetimeFromUnixTime(getDummyUnixTimeForLocalUsage(this.maxTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()));
}
},
onSheetClosed() {
@@ -151,16 +171,16 @@ export default {
const currentMinDate = this.dateRange[0];
const currentMaxDate = this.dateRange[1];
let minUnixTime = this.$utilities.getUnixTime(currentMinDate);
let maxUnixTime = this.$utilities.getUnixTime(currentMaxDate);
let minUnixTime = getUnixTime(currentMinDate);
let maxUnixTime = getUnixTime(currentMaxDate);
if (minUnixTime < 0 || maxUnixTime < 0) {
this.$toast('Date is too early');
return;
}
minUnixTime = this.$utilities.getActualUnixTimeForStore(minUnixTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
maxUnixTime = this.$utilities.getActualUnixTimeForStore(maxUnixTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
minUnixTime = getActualUnixTimeForStore(minUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
maxUnixTime = getActualUnixTimeForStore(maxUnixTime, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes());
this.$emit('dateRange:change', minUnixTime, maxUnixTime);
},
@@ -34,6 +34,18 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.js';
import {
getCurrentUnixTime,
getCurrentDateTime,
getUnixTime,
getLocalDatetimeFromUnixTime,
getYear
} from '@/lib/datetime.js';
export default {
props: [
'modelValue',
@@ -45,7 +57,7 @@ export default {
],
data() {
const self = this;
let value = self.$utilities.getCurrentUnixTime();
let value = getCurrentUnixTime();
if (self.modelValue) {
value = self.modelValue;
@@ -54,43 +66,44 @@ export default {
return {
yearRange: [
2000,
this.$utilities.getYear(this.$utilities.getCurrentDateTime()) + 1
getYear(getCurrentDateTime()) + 1
],
dateTime: this.$utilities.getLocalDatetimeFromUnixTime(value),
dateTime: getLocalDatetimeFromUnixTime(value),
}
},
computed: {
...mapStores(useUserStore),
isDarkMode() {
return this.$root.isDarkMode;
},
firstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
return this.userStore.currentUserFirstDayOfWeek;
},
dayNames() {
return this.$utilities.arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
return arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
},
is24Hour() {
return this.$locale.isLongTime24HourFormat();
return this.$locale.isLongTime24HourFormat(this.userStore);
}
},
methods: {
onSheetOpen() {
if (this.modelValue) {
this.dateTime = this.$utilities.getLocalDatetimeFromUnixTime(this.modelValue)
this.dateTime = getLocalDatetimeFromUnixTime(this.modelValue)
}
},
onSheetClosed() {
this.$emit('update:show', false);
},
setCurrentTime() {
this.dateTime = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getCurrentUnixTime())
this.dateTime = getLocalDatetimeFromUnixTime(getCurrentUnixTime())
},
confirm() {
if (!this.dateTime) {
return;
}
const unixTime = this.$utilities.getUnixTime(this.dateTime);
const unixTime = getUnixTime(this.dateTime);
if (unixTime < 0) {
this.$toast('Date is too early');
+4 -2
View File
@@ -25,6 +25,8 @@
</template>
<script>
import { makeButtonCopyToClipboard, changeClipboardObjectText } from '@/lib/misc.js';
export default {
props: [
'title',
@@ -52,7 +54,7 @@ export default {
watch: {
'information': function (newValue) {
if (this.clipboardHolder) {
this.$utilities.changeClipboardObjectText(this.clipboardHolder, newValue);
changeClipboardObjectText(this.clipboardHolder, newValue);
}
}
},
@@ -71,7 +73,7 @@ export default {
}
if (self.$refs.copyToClipboardIcon) {
self.clipboardHolder = self.$utilities.makeButtonCopyToClipboard({
self.clipboardHolder = makeButtonCopyToClipboard({
el: '#copy-to-clipboard-icon',
text: self.information,
successCallback: function () {
+15 -11
View File
@@ -5,6 +5,10 @@
</template>
<script>
import iconConstatns from '@/consts/icon.js';
import colorConstatns from '@/consts/color.js';
import { isNumber } from '@/lib/common.js';
export default {
props: [
'iconType',
@@ -50,29 +54,29 @@ export default {
},
methods: {
getAccountIcon(iconId) {
if (this.$utilities.isNumber(iconId)) {
if (isNumber(iconId)) {
iconId = iconId.toString();
}
if (!this.$constants.icons.allAccountIcons[iconId]) {
return this.$constants.icons.defaultAccountIcon.icon;
if (!iconConstatns.allAccountIcons[iconId]) {
return iconConstatns.defaultAccountIcon.icon;
}
return this.$constants.icons.allAccountIcons[iconId].icon;
return iconConstatns.allAccountIcons[iconId].icon;
},
getCategoryIcon(iconId) {
if (this.$utilities.isNumber(iconId)) {
if (isNumber(iconId)) {
iconId = iconId.toString();
}
if (!this.$constants.icons.allCategoryIcons[iconId]) {
return this.$constants.icons.defaultCategoryIcon.icon;
if (!iconConstatns.allCategoryIcons[iconId]) {
return iconConstatns.defaultCategoryIcon.icon;
}
return this.$constants.icons.allCategoryIcons[iconId].icon;
return iconConstatns.allCategoryIcons[iconId].icon;
},
getAccountIconStyle(color, defaultColor, additionalColorAttr) {
if (color && color !== this.$constants.colors.defaultAccountColor) {
if (color && color !== colorConstatns.defaultAccountColor) {
color = '#' + color;
} else {
color = defaultColor;
@@ -89,7 +93,7 @@ export default {
return ret;
},
getCategoryIconStyle(color, defaultColor, additionalColorAttr) {
if (color && color !== this.$constants.colors.defaultCategoryColor) {
if (color && color !== colorConstatns.defaultCategoryColor) {
color = '#' + color;
} else {
color = defaultColor;
@@ -106,7 +110,7 @@ export default {
return ret;
},
getDefaultIconStyle(color, defaultColor, additionalColorAttr) {
if (color && color !== this.$constants.colors.defaultColor) {
if (color && color !== colorConstatns.defaultColor) {
color = '#' + color;
} else {
color = defaultColor;
+19 -16
View File
@@ -63,6 +63,9 @@
</template>
<script>
import { isString, appendThousandsSeparator } from '@/lib/common.js';
import { numericCurrencyToString, stringCurrencyToNumeric } from '@/lib/currency.js';
export default {
props: [
'modelValue',
@@ -85,8 +88,8 @@ export default {
},
computed: {
currentDisplay() {
const previousValue = this.$utilities.appendThousandsSeparator(this.previousValue);
const currentValue = this.$utilities.appendThousandsSeparator(this.currentValue);
const previousValue = appendThousandsSeparator(this.previousValue);
const currentValue = appendThousandsSeparator(this.currentValue);
if (this.currentSymbol) {
return `${previousValue} ${this.currentSymbol} ${currentValue}`;
@@ -115,7 +118,7 @@ export default {
},
methods: {
getStringValue(value) {
let str = this.$utilities.numericCurrencyToString(value);
let str = numericCurrencyToString(value);
if (str.indexOf(',')) {
str = str.replaceAll(/,/g, '');
@@ -173,18 +176,18 @@ export default {
const newValue = this.currentValue + num.toString();
if (this.$utilities.isString(this.minValue) && this.minValue !== '') {
const min = this.$utilities.stringCurrencyToNumeric(this.minValue);
const current = this.$utilities.stringCurrencyToNumeric(newValue);
if (isString(this.minValue) && this.minValue !== '') {
const min = stringCurrencyToNumeric(this.minValue);
const current = stringCurrencyToNumeric(newValue);
if (current < min) {
return;
}
}
if (this.$utilities.isString(this.maxValue) && this.maxValue !== '') {
const max = this.$utilities.stringCurrencyToNumeric(this.maxValue);
const current = this.$utilities.stringCurrencyToNumeric(newValue);
if (isString(this.maxValue) && this.maxValue !== '') {
const max = stringCurrencyToNumeric(this.maxValue);
const current = stringCurrencyToNumeric(newValue);
if (current > max) {
return;
@@ -247,8 +250,8 @@ export default {
},
confirm() {
if (this.currentSymbol && this.currentValue.length >= 1) {
const previousValue = this.$utilities.stringCurrencyToNumeric(this.previousValue);
const currentValue = this.$utilities.stringCurrencyToNumeric(this.currentValue);
const previousValue = stringCurrencyToNumeric(this.previousValue);
const currentValue = stringCurrencyToNumeric(this.currentValue);
let finalValue = 0;
switch (this.currentSymbol) {
@@ -265,8 +268,8 @@ export default {
finalValue = previousValue;
}
if (this.$utilities.isString(this.minValue) && this.minValue !== '') {
const min = this.$utilities.stringCurrencyToNumeric(this.minValue);
if (isString(this.minValue) && this.minValue !== '') {
const min = stringCurrencyToNumeric(this.minValue);
if (finalValue < min) {
this.$toast('Numeric Overflow');
@@ -274,8 +277,8 @@ export default {
}
}
if (this.$utilities.isString(this.maxValue) && this.maxValue !== '') {
const max = this.$utilities.stringCurrencyToNumeric(this.maxValue);
if (isString(this.maxValue) && this.maxValue !== '') {
const max = stringCurrencyToNumeric(this.maxValue);
if (finalValue > max) {
this.$toast('Numeric Overflow');
@@ -295,7 +298,7 @@ export default {
return true;
} else {
const value = this.$utilities.stringCurrencyToNumeric(this.currentValue);
const value = stringCurrencyToNumeric(this.currentValue);
this.$emit('update:modelValue', value);
this.close();
+13 -5
View File
@@ -49,7 +49,7 @@
<span class="skeleton-text">Percent</span>
</f7-chip>
<f7-chip outline
:text="$utilities.formatPercent(selectedItem.percent, 2, '&lt;0.01')"
:text="selectedItem.displayPercent"
:style="getColorStyle(selectedItem ? selectedItem.color : '', '--f7-chip-outline-border-color')"
v-else-if="!skeleton"></f7-chip>
</p>
@@ -60,7 +60,7 @@
<span class="skeleton-text" v-if="skeleton">Name</span>
<span v-else-if="!skeleton && selectedItem.name">{{ selectedItem.name }}</span>
<span class="skeleton-text" v-if="skeleton">Value</span>
<span v-else-if="!skeleton && showValue" :style="getColorStyle(selectedItem ? selectedItem.color : '')">{{ $locale.getDisplayCurrency(selectedItem.value, (selectedItem.currency || defaultCurrency)) }}</span>
<span v-else-if="!skeleton && showValue" :style="getColorStyle(selectedItem ? selectedItem.color : '')">{{ selectedItem.displayValue }}</span>
<f7-icon class="item-navigate-icon" f7="chevron_right" v-if="enableClickItem"></f7-icon>
</f7-link>
<f7-link :no-link-class="true" v-else-if="!validItems || !validItems.length">
@@ -77,6 +77,9 @@
</template>
<script>
import colorConstants from '@/consts/color.js';
import { formatPercent } from '@/lib/common.js';
const defaultColors = [
'cc4a66',
'e3564a',
@@ -140,7 +143,7 @@ export default {
if (item[this.valueField] && item[this.valueField] > 0 &&
(!this.hiddenField || !item[this.hiddenField]) &&
(!this.minValidPercent || item[this.valueField] / totalValidValue > this.minValidPercent)) {
validItems.push({
const finalItem = {
name: item[this.nameField],
value: item[this.valueField],
percent: (item[this.percentField] > 0 || item[this.percentField] === 0 || item[this.percentField] === '0') ? item[this.percentField] : (item[this.valueField] / totalValidValue * 100),
@@ -148,7 +151,12 @@ export default {
currency: item[this.currencyField],
color: item[this.colorField] ? item[this.colorField] : defaultColors[validItems.length % defaultColors.length],
sourceItem: item
});
};
finalItem.displayPercent = formatPercent(finalItem.percent, 2, '&lt;0.01');
finalItem.displayValue = this.$locale.getDisplayCurrency(finalItem.value, (finalItem.currency || this.defaultCurrency));
validItems.push(finalItem);
}
}
@@ -225,7 +233,7 @@ export default {
}
},
getColor: function (color) {
if (color && color !== this.$constants.colors.defaultColor) {
if (color && color !== colorConstants.defaultColor) {
color = '#' + color;
} else {
color = 'var(--default-icon-color)';
@@ -41,6 +41,9 @@
</template>
<script>
import { copyArrayTo } from '@/lib/common.js';
import { elements } from '@/lib/ui.mobile.js';
export default {
props: [
'modelValue',
@@ -55,7 +58,7 @@ export default {
const self = this;
return {
selectedItemIds: self.$utilities.copyArrayTo(self.modelValue, [])
selectedItemIds: copyArrayTo(self.modelValue, [])
}
},
computed: {
@@ -78,7 +81,7 @@ export default {
this.$emit('update:show', false);
},
onSheetOpen(event) {
this.selectedItemIds = this.$utilities.copyArrayTo(this.modelValue, []);
this.selectedItemIds = copyArrayTo(this.modelValue, []);
this.scrollToSelectedItem(event.$el);
},
onSheetClosed() {
@@ -120,8 +123,8 @@ export default {
let lastSelectedItem = selectedItem;
if (selectedItem.length > 0) {
firstSelectedItem = this.$ui.elements(selectedItem[0]);
lastSelectedItem = this.$ui.elements(selectedItem[selectedItem.length - 1]);
firstSelectedItem = elements(selectedItem[0]);
lastSelectedItem = elements(selectedItem[selectedItem.length - 1]);
}
let firstSelectedItemInTop = firstSelectedItem.offset().top - container.offset().top - parseInt(container.css('padding-top'), 10);
@@ -39,6 +39,8 @@
</template>
<script>
import { isArray } from '@/lib/common.js';
export default {
props: [
'modelValue',
@@ -73,7 +75,7 @@ export default {
},
computed: {
hugeTreeViewItems() {
if (this.$utilities.isArray(this.items)) {
if (isArray(this.items)) {
return this.items.length > 10;
} else {
let count = 0;
@@ -60,6 +60,8 @@
</template>
<script>
import { isArray } from '@/lib/common.js';
export default {
props: [
'modelValue',
@@ -104,7 +106,7 @@ export default {
computed: {
selectedPrimaryItem() {
if (this.primaryValueField) {
if (this.$utilities.isArray(this.items)) {
if (isArray(this.items)) {
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
@@ -181,7 +183,7 @@ export default {
},
getPrimaryValueBySecondaryValue(secondaryValue) {
if (this.primarySubItemsField) {
if (this.$utilities.isArray(this.items)) {
if (isArray(this.items)) {
for (let i = 0; i < this.items.length; i++) {
const primaryItem = this.items[i];
+10
View File
@@ -39,8 +39,18 @@ const allAccountTypes = {
SingleAccount: 1,
MultiSubAccounts: 2
};
const allAccountTypesArray = [
{
id: allAccountTypes.SingleAccount,
name: 'Single Account'
}, {
id: allAccountTypes.MultiSubAccounts,
name: 'Multi Sub Accounts'
}
];
export default {
allCategories: allAccountCategories,
allAccountTypes: allAccountTypes,
allAccountTypesArray: allAccountTypesArray,
};
+1 -1
View File
@@ -1,4 +1,4 @@
import { autoChangeTextareaSize } from '../../lib/mobile/ui.js';
import { autoChangeTextareaSize } from '@/lib/ui.mobile.js';
export default {
mounted(el) {
@@ -1,4 +1,4 @@
import accountConstants from '../../consts/account.js';
import accountConstants from '@/consts/account.js';
export function getAccountCategoryInfo(categoryId) {
for (let i = 0; i < accountConstants.allCategories.length; i++) {
@@ -1,5 +1,5 @@
import categoryConstants from '../../consts/category.js';
import transactionConstants from '../../consts/transaction.js';
import categoryConstants from '@/consts/category.js';
import transactionConstants from '@/consts/transaction.js';
export function transactionTypeToCategoryType(transactionType) {
if (transactionType === transactionConstants.allTransactionTypes.Income) {
@@ -1,4 +1,4 @@
import settings from "../../lib/settings";
import settings from './settings.js';
export function isFunction(val) {
return typeof(val) === 'function';
@@ -227,9 +227,9 @@ export function copyObjectTo(fromObject, toObject) {
const toValue = toObject[key];
if (isArray(fromValue)) {
toObject[key] = this.copyArrayTo(fromValue, toValue);
toObject[key] = copyArrayTo(fromValue, toValue);
} else if (isObject(fromValue)) {
toObject[key] = this.copyObjectTo(fromValue, toValue);
toObject[key] = copyObjectTo(fromValue, toValue);
} else {
if (fromValue !== toValue) {
toObject[key] = fromValue;
@@ -256,9 +256,9 @@ export function copyArrayTo(fromArray, toArray) {
const toValue = toArray[i];
if (isArray(fromValue)) {
toArray[i] = this.copyArrayTo(fromValue, toValue);
toArray[i] = copyArrayTo(fromValue, toValue);
} else if (isObject(fromValue)) {
toArray[i] = this.copyObjectTo(fromValue, toValue);
toArray[i] = copyObjectTo(fromValue, toValue);
} else {
if (fromValue !== toValue) {
toArray[i] = fromValue;
@@ -266,9 +266,9 @@ export function copyArrayTo(fromArray, toArray) {
}
} else {
if (isArray(fromValue)) {
toArray.push(this.copyArrayTo(fromValue, []));
toArray.push(copyArrayTo(fromValue, []));
} else if (isObject(fromValue)) {
toArray.push(this.copyObjectTo(fromValue, {}));
toArray.push(copyObjectTo(fromValue, {}));
} else {
toArray.push(fromValue);
}
@@ -1,4 +1,4 @@
import { isNumber, appendThousandsSeparator } from "./common.js";
import { isNumber, appendThousandsSeparator } from './common.js';
export function numericCurrencyToString(num) {
let str = num.toString();
@@ -1,7 +1,7 @@
import moment from 'moment';
import dateTimeConstants from '../../consts/datetime.js';
import { isNumber } from "./common.js";
import dateTimeConstants from '@/consts/datetime.js';
import { isNumber } from './common.js';
export function getUtcOffsetMinutesByUtcOffset(utcOffset) {
if (!utcOffset) {
+32 -19
View File
@@ -1,9 +1,22 @@
import { defaultLanguage, allLanguages } from '../locales/index.js';
import datetime from "../consts/datetime.js";
import timezone from "../consts/timezone.js";
import currency from "../consts/currency.js";
import settings from "./settings.js";
import utilities from './utilities/index.js';
import { defaultLanguage, allLanguages } from '@/locales/index.js';
import datetime from '@/consts/datetime.js';
import timezone from '@/consts/timezone.js';
import currency from '@/consts/currency.js';
import settings from './settings.js';
import {
isString,
isNumber
} from './common.js';
import {
formatTime,
getCurrentDateTime,
getTimezoneOffset,
getTimezoneOffsetMinutes,
getDateTimeFormatType
} from './datetime.js';
import {
numericCurrencyToString
} from './currency.js';
const apiNotFoundErrorCode = 100001;
const specifiedApiNotFoundErrors = {
@@ -332,27 +345,27 @@ export function getI18nShortTimeFormat(translateFn, formatTypeValue) {
export function isLongTime24HourFormat(translateFn, formatTypeValue) {
const defaultLongTimeFormatTypeName = translateFn('default.longTimeFormat');
const type = utilities.getDateTimeFormatType(datetime.allLongTimeFormat, datetime.allLongTimeFormatArray, defaultLongTimeFormatTypeName, datetime.defaultLongTimeFormat, formatTypeValue);
const type = getDateTimeFormatType(datetime.allLongTimeFormat, datetime.allLongTimeFormatArray, defaultLongTimeFormatTypeName, datetime.defaultLongTimeFormat, formatTypeValue);
return type.is24HourFormat;
}
export function isShortTime24HourFormat(translateFn, formatTypeValue) {
const defaultShortTimeFormatTypeName = translateFn('default.shortTimeFormat');
const type = utilities.getDateTimeFormatType(datetime.allShortTimeFormat, datetime.allShortTimeFormatArray, defaultShortTimeFormatTypeName, datetime.defaultShortTimeFormat, formatTypeValue);
const type = getDateTimeFormatType(datetime.allShortTimeFormat, datetime.allShortTimeFormatArray, defaultShortTimeFormatTypeName, datetime.defaultShortTimeFormat, formatTypeValue);
return type.is24HourFormat;
}
export function getAllTimezones(includeSystemDefault, translateFn) {
const defaultTimezoneOffset = utilities.getTimezoneOffset();
const defaultTimezoneOffsetMinutes = utilities.getTimezoneOffsetMinutes();
const defaultTimezoneOffset = getTimezoneOffset();
const defaultTimezoneOffsetMinutes = getTimezoneOffsetMinutes();
const allTimezones = timezone.all;
const allTimezoneInfos = [];
for (let i = 0; i < allTimezones.length; i++) {
allTimezoneInfos.push({
name: allTimezones[i].timezoneName,
utcOffset: (allTimezones[i].timezoneName !== timezone.utcTimezoneName ? utilities.getTimezoneOffset(allTimezones[i].timezoneName) : ''),
utcOffsetMinutes: utilities.getTimezoneOffsetMinutes(allTimezones[i].timezoneName),
utcOffset: (allTimezones[i].timezoneName !== timezone.utcTimezoneName ? getTimezoneOffset(allTimezones[i].timezoneName) : ''),
utcOffsetMinutes: getTimezoneOffsetMinutes(allTimezones[i].timezoneName),
displayName: translateFn(`timezone.${allTimezones[i].displayName}`)
});
}
@@ -403,22 +416,22 @@ export function getAllCurrencies(translateFn) {
}
export function getDisplayCurrency(value, currencyCode, notConvertValue, translateFn) {
if (!utilities.isNumber(value) && !utilities.isString(value)) {
if (!isNumber(value) && !isString(value)) {
return value;
}
if (utilities.isNumber(value)) {
if (isNumber(value)) {
value = value.toString();
}
if (!notConvertValue) {
const hasIncompleteFlag = utilities.isString(value) && value.charAt(value.length - 1) === '+';
const hasIncompleteFlag = isString(value) && value.charAt(value.length - 1) === '+';
if (hasIncompleteFlag) {
value = value.substring(0, value.length - 1);
}
value = utilities.numericCurrencyToString(value);
value = numericCurrencyToString(value);
if (hasIncompleteFlag) {
value = value + '+';
@@ -563,7 +576,7 @@ function getDateTimeFormats(translateFn, allFormatMap, allFormatArray, localeFor
ret.push({
type: datetime.defaultDateTimeFormatValue,
format: defaultFormat,
displayName: `${translateFn('Language Default')} (${utilities.formatTime(utilities.getCurrentDateTime(), defaultFormat)})`
displayName: `${translateFn('Language Default')} (${formatTime(getCurrentDateTime(), defaultFormat)})`
});
for (let i = 0; i < allFormatArray.length; i++) {
@@ -573,7 +586,7 @@ function getDateTimeFormats(translateFn, allFormatMap, allFormatArray, localeFor
ret.push({
type: formatType.type,
format: format,
displayName: utilities.formatTime(utilities.getCurrentDateTime(), format)
displayName: formatTime(getCurrentDateTime(), format)
});
}
@@ -581,7 +594,7 @@ function getDateTimeFormats(translateFn, allFormatMap, allFormatArray, localeFor
}
function getDateTimeFormat(translateFn, allFormatMap, allFormatArray, localeFormatPathPrefix, localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue) {
const type = utilities.getDateTimeFormatType(allFormatMap, allFormatArray,
const type = getDateTimeFormatType(allFormatMap, allFormatArray,
localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue);
return translateFn(`${localeFormatPathPrefix}.${type.key}`);
}
+3 -3
View File
@@ -1,9 +1,9 @@
import axios from 'axios';
import api from '../consts/api.js';
import api from '@/consts/api.js';
import userState from './userstate.js';
import settings from './settings.js';
import utilities from './utilities/index.js';
import { getTimezoneOffsetMinutes } from './datetime.js';
let needBlockRequest = false;
let blockedRequests = [];
@@ -17,7 +17,7 @@ axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
}
config.headers['X-Timezone-Offset'] = utilities.getTimezoneOffsetMinutes();
config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes();
if (needBlockRequest && !config.ignoreBlocked) {
return new Promise(resolve => {
+2 -2
View File
@@ -1,7 +1,7 @@
import Cookies from 'js-cookie';
import currencyConstants from '../consts/currency.js';
import statisticsConstants from '../consts/statistics.js';
import currencyConstants from '@/consts/currency.js';
import statisticsConstants from '@/consts/statistics.js';
const settingsLocalStorageKey = 'ebk_app_settings';
const serverSettingsCookieKey = 'ebk_server_settings';
@@ -1,10 +1,10 @@
import { f7, f7ready } from 'framework7-vue';
import settings from "../settings.js";
import settings from './settings.js';
import {
getLocalizedError,
getLocalizedErrorParameters
} from "../i18n.js";
} from './i18n.js';
export function showAlert(message, confirmCallback, translateFn) {
let parameters = {};
+3 -3
View File
@@ -1,7 +1,7 @@
import CryptoJS from 'crypto-js';
import settings from './settings.js';
import utilities from './utilities/index.js';
import { isString, isObject } from './common.js';
const appLockSecretBaseStringPrefix = 'EBK_LOCK_SECRET_';
@@ -141,7 +141,7 @@ function isCorrectPinCode(pinCode) {
}
function updateToken(token) {
if (utilities.isString(token)) {
if (isString(token)) {
if (settings.isEnableApplicationLock()) {
sessionStorage.setItem(tokenSessionStorageKey, token);
@@ -155,7 +155,7 @@ function updateToken(token) {
}
function updateUserInfo(user) {
if (utilities.isObject(user)) {
if (isObject(user)) {
localStorage.setItem(userInfoLocalStorageKey, JSON.stringify(user));
}
}
-179
View File
@@ -1,179 +0,0 @@
import {
isFunction,
isObject,
isArray,
isString,
isNumber,
isBoolean,
isEquals,
appendThousandsSeparator,
formatPercent,
limitText,
base64encode,
arrayBufferToString,
stringToArrayBuffer,
getNameByKeyValue,
copyObjectTo,
copyArrayTo,
arrangeArrayWithNewStartIndex,
} from './common.js'
import {
getUtcOffsetMinutesByUtcOffset,
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getActualUnixTimeForStore,
getDummyUnixTimeForLocalUsage,
getCurrentUnixTime,
getCurrentDateTime,
parseDateFromUnixTime,
formatUnixTime,
formatTime,
getUnixTime,
getYear,
getMonth,
getYearAndMonth,
getDay,
getDayOfWeekName,
getHour,
getMinute,
getSecond,
getUnixTimeBeforeUnixTime,
getUnixTimeAfterUnixTime,
getMinuteFirstUnixTime,
getMinuteLastUnixTime,
getTodayFirstUnixTime,
getTodayLastUnixTime,
getThisWeekFirstUnixTime,
getThisWeekLastUnixTime,
getThisMonthFirstUnixTime,
getThisMonthLastUnixTime,
getThisYearFirstUnixTime,
getThisYearLastUnixTime,
getDateTimeFormatType,
getShiftedDateRange,
getDateRangeByDateType,
isDateRangeMatchFullYears,
isDateRangeMatchFullMonths,
} from './datetime.js'
import {
numericCurrencyToString,
stringCurrencyToNumeric,
getExchangedAmount,
} from './currency.js'
import {
generateRandomString,
parseUserAgent,
parseDeviceInfo,
makeButtonCopyToClipboard,
changeClipboardObjectText,
} from './misc.js'
import {
transactionTypeToCategoryType,
categoryTypeToTransactionType,
getTransactionPrimaryCategoryName,
getTransactionSecondaryCategoryName,
allVisibleTransactionCategories,
} from './category.js'
import {
getAccountCategoryInfo,
getCategorizedAccounts,
getVisibleCategorizedAccounts,
getAllFilteredAccountsBalance,
} from './account.js'
export default {
// common.js
isFunction,
isObject,
isArray,
isString,
isNumber,
isBoolean,
isEquals,
appendThousandsSeparator,
formatPercent,
limitText,
base64encode,
arrayBufferToString,
stringToArrayBuffer,
getNameByKeyValue,
copyObjectTo,
copyArrayTo,
arrangeArrayWithNewStartIndex,
// datetime.js
getUtcOffsetMinutesByUtcOffset,
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffset,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getLocalDatetimeFromUnixTime,
getUnixTimeFromLocalDatetime,
getActualUnixTimeForStore,
getDummyUnixTimeForLocalUsage,
getCurrentUnixTime,
getCurrentDateTime,
parseDateFromUnixTime,
formatUnixTime,
formatTime,
getUnixTime,
getYear,
getMonth,
getYearAndMonth,
getDay,
getDayOfWeekName,
getHour,
getMinute,
getSecond,
getUnixTimeBeforeUnixTime,
getUnixTimeAfterUnixTime,
getMinuteFirstUnixTime,
getMinuteLastUnixTime,
getTodayFirstUnixTime,
getTodayLastUnixTime,
getThisWeekFirstUnixTime,
getThisWeekLastUnixTime,
getThisMonthFirstUnixTime,
getThisMonthLastUnixTime,
getThisYearFirstUnixTime,
getThisYearLastUnixTime,
getDateTimeFormatType,
getShiftedDateRange,
getDateRangeByDateType,
isDateRangeMatchFullYears,
isDateRangeMatchFullMonths,
// currency.js
numericCurrencyToString,
stringCurrencyToNumeric,
getExchangedAmount,
// misc.js
generateRandomString,
parseUserAgent,
parseDeviceInfo,
makeButtonCopyToClipboard,
changeClipboardObjectText,
// category.js
transactionTypeToCategoryType,
categoryTypeToTransactionType,
getTransactionPrimaryCategoryName,
getTransactionSecondaryCategoryName,
allVisibleTransactionCategories,
// account.js
getAccountCategoryInfo,
getCategorizedAccounts,
getVisibleCategorizedAccounts,
getAllFilteredAccountsBalance,
};
+19 -11
View File
@@ -1,6 +1,14 @@
import CBOR from 'cbor-js';
import logger from './logger.js';
import utilities from './utilities/index.js';
import {
isFunction,
stringToArrayBuffer,
arrayBufferToString,
base64encode
} from './common.js';
import {
generateRandomString
} from './misc.js';
const publicKeyCredentialCreationOptionsBaseTemplate = {
attestation: "none",
@@ -28,7 +36,7 @@ const publicKeyCredentialRequestOptionsBaseTemplate = {
function isSupported() {
return !!window.PublicKeyCredential
&& !!navigator.credentials
&& utilities.isFunction(window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable);
&& isFunction(window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable);
}
function isCompletelySupported() {
@@ -52,17 +60,17 @@ function registerCredential({ username, secret }, { nickname }) {
});
}
const challenge = utilities.generateRandomString();
const challenge = generateRandomString();
const userId = `${username}|${secret}`; // username 32bytes(max) + secret 24bytes = 56bytes(max)
const publicKeyCredentialCreationOptions = Object.assign({}, publicKeyCredentialCreationOptionsBaseTemplate, {
challenge: utilities.stringToArrayBuffer(challenge),
challenge: stringToArrayBuffer(challenge),
rp: {
name: window.location.hostname,
id: window.location.hostname
},
user: {
id: utilities.stringToArrayBuffer(userId),
id: stringToArrayBuffer(userId),
name: username,
displayName: nickname
}
@@ -83,7 +91,7 @@ function registerCredential({ username, secret }, { nickname }) {
if (rawCredential && rawCredential.rawId &&
clientData && clientData.type === 'webauthn.create' && challengeFromClientData === challenge) {
const ret = {
id: utilities.base64encode(rawCredential.rawId),
id: base64encode(rawCredential.rawId),
clientData: clientData,
publicKey: publicKey,
rawCredential: rawCredential
@@ -133,12 +141,12 @@ function verifyCredential({ username }, credentialId) {
});
}
const challenge = utilities.generateRandomString();
const challenge = generateRandomString();
const publicKeyCredentialRequestOptions = Object.assign({}, publicKeyCredentialRequestOptionsBaseTemplate, {
challenge: utilities.stringToArrayBuffer(challenge),
challenge: stringToArrayBuffer(challenge),
rpId: window.location.hostname
});
publicKeyCredentialRequestOptions.allowCredentials[0].id = utilities.stringToArrayBuffer(atob(credentialId));
publicKeyCredentialRequestOptions.allowCredentials[0].id = stringToArrayBuffer(atob(credentialId));
logger.debug('webauthn get options', publicKeyCredentialRequestOptions);
@@ -147,7 +155,7 @@ function verifyCredential({ username }, credentialId) {
}).then(rawCredential => {
const clientData = rawCredential ? parseClientData(rawCredential) : null;
const challengeFromClientData = clientData && clientData.challenge ? atob(clientData.challenge) : null;
const userIdParts = rawCredential && rawCredential.response && rawCredential.response.userHandle ? utilities.arrayBufferToString(rawCredential.response.userHandle).split('|') : null;
const userIdParts = rawCredential && rawCredential.response && rawCredential.response.userHandle ? arrayBufferToString(rawCredential.response.userHandle).split('|') : null;
logger.debug('webauthn get raw response', rawCredential);
@@ -155,7 +163,7 @@ function verifyCredential({ username }, credentialId) {
clientData && clientData.type === 'webauthn.get' && challengeFromClientData === challenge &&
userIdParts && userIdParts.length === 2 && userIdParts[0] === username) {
const ret = {
id: utilities.base64encode(rawCredential.rawId),
id: base64encode(rawCredential.rawId),
userName: userIdParts[0],
userSecret: userIdParts[1],
clientData: clientData,
+63 -92
View File
@@ -1,8 +1,8 @@
import { createApp } from 'vue';
import { createStore } from 'vuex';
import { createPinia } from 'pinia';
import { createI18n } from 'vue-i18n';
import moment from "moment-timezone";
import moment from 'moment-timezone';
import Framework7 from 'framework7/lite';
import Framework7Dialog from 'framework7/components/dialog';
@@ -76,24 +76,18 @@ import '@vuepic/vue-datepicker/dist/main.css';
import * as Leaflet from 'leaflet/dist/leaflet-src.esm.js';
import 'leaflet/dist/leaflet.css';
import api from './consts/api.js';
import datetime from './consts/datetime.js';
import currency from './consts/currency.js';
import colors from './consts/color.js';
import icons from './consts/icon.js';
import account from './consts/account.js';
import transaction from './consts/transaction.js';
import category from './consts/category.js';
import statistics from './consts/statistics.js';
import datetimeConstants from '@/consts/datetime.js';
import licenses from './lib/licenses.js';
import version from './lib/version.js';
import logger from './lib/logger.js';
import settings from './lib/settings.js';
import services from './lib/services.js';
import userstate from './lib/userstate.js';
import webauthn from './lib/webauthn.js';
import utilities from './lib/utilities/index.js';
import version from '@/lib/version.js';
import logger from '@/lib/logger.js';
import settings from '@/lib/settings.js';
import services from '@/lib/services.js';
import userstate from '@/lib/userstate.js';
import {
formatUnixTime,
formatTime,
getTimezoneOffset
} from '@/lib/datetime.js';
import {
getAllLanguageInfos,
getLanguageInfo,
@@ -124,42 +118,40 @@ import {
getAllCurrencies,
getDisplayCurrency,
getI18nOptions,
} from './lib/i18n.js';
} from '@/lib/i18n.js';
import {
showAlert,
showConfirm,
showToast,
showLoading,
hideLoading,
routeBackOnError,
elements,
isModalShowing,
onSwipeoutDeleted
} from './lib/mobile/ui.js';
routeBackOnError
} from '@/lib/ui.mobile.js';
import stores from './store/index.js';
import ItemIcon from '@/components/mobile/ItemIcon.vue';
import PieChart from '@/components/mobile/PieChart.vue';
import PinCodeInput from '@/components/mobile/PinCodeInput.vue';
import PinCodeInputSheet from '@/components/mobile/PinCodeInputSheet.vue';
import PasswordInputSheet from '@/components/mobile/PasswordInputSheet.vue';
import PasscodeInputSheet from '@/components/mobile/PasscodeInputSheet.vue';
import DateTimeSelectionSheet from '@/components/mobile/DateTimeSelectionSheet.vue';
import DateRangeSelectionSheet from '@/components/mobile/DateRangeSelectionSheet.vue';
import ListItemSelectionSheet from '@/components/mobile/ListItemSelectionSheet.vue';
import TwoColumnListItemSelectionSheet from '@/components/mobile/TwoColumnListItemSelectionSheet.vue';
import TreeViewSelectionSheet from '@/components/mobile/TreeViewSelectionSheet.vue';
import IconSelectionSheet from '@/components/mobile/IconSelectionSheet.vue';
import ColorSelectionSheet from '@/components/mobile/ColorSelectionSheet.vue';
import InformationSheet from '@/components/mobile/InformationSheet.vue';
import NumberPadSheet from '@/components/mobile/NumberPadSheet.vue';
import MapSheet from '@/components/mobile/MapSheet.vue';
import TransactionTagSelectionSheet from '@/components/mobile/TransactionTagSelectionSheet.vue';
import ItemIcon from './components/mobile/ItemIcon.vue';
import PieChart from './components/mobile/PieChart.vue';
import PinCodeInput from './components/mobile/PinCodeInput.vue';
import PinCodeInputSheet from './components/mobile/PinCodeInputSheet.vue';
import PasswordInputSheet from './components/mobile/PasswordInputSheet.vue';
import PasscodeInputSheet from './components/mobile/PasscodeInputSheet.vue';
import DateTimeSelectionSheet from './components/mobile/DateTimeSelectionSheet.vue';
import DateRangeSelectionSheet from './components/mobile/DateRangeSelectionSheet.vue';
import ListItemSelectionSheet from './components/mobile/ListItemSelectionSheet.vue';
import TwoColumnListItemSelectionSheet from './components/mobile/TwoColumnListItemSelectionSheet.vue';
import TreeViewSelectionSheet from './components/mobile/TreeViewSelectionSheet.vue';
import IconSelectionSheet from './components/mobile/IconSelectionSheet.vue';
import ColorSelectionSheet from './components/mobile/ColorSelectionSheet.vue';
import InformationSheet from './components/mobile/InformationSheet.vue';
import NumberPadSheet from './components/mobile/NumberPadSheet.vue';
import MapSheet from './components/mobile/MapSheet.vue';
import TransactionTagSelectionSheet from './components/mobile/TransactionTagSelectionSheet.vue';
import TextareaAutoSize from '@/directives/mobile/textareaAutoSize.js';
import TextareaAutoSize from "./directives/mobile/textareaAutoSize.js";
import { useSettingsStore } from '@/stores/setting.js';
import { useUserStore } from '@/stores/user.js';
import App from './MobileApp.vue';
import App from '@/MobileApp.vue';
Framework7.use([
Framework7Dialog,
@@ -195,10 +187,10 @@ Framework7.use([
]);
const app = createApp(App);
const store = createStore(stores);
const pinia = createPinia();
const i18n = createI18n(getI18nOptions());
registerComponents(app);
app.use(store);
app.use(pinia);
app.use(i18n);
function setLanguage(locale) {
@@ -238,13 +230,14 @@ function setLanguage(locale) {
const defaultCurrency = i18n.global.t('default.currency');
const defaultFirstDayOfWeekName = i18n.global.t('default.firstDayOfWeek');
let defaultFirstDayOfWeek = datetime.defaultFirstDayOfWeek;
let defaultFirstDayOfWeek = datetimeConstants.defaultFirstDayOfWeek;
if (datetime.allWeekDays[defaultFirstDayOfWeekName]) {
defaultFirstDayOfWeek = datetime.allWeekDays[defaultFirstDayOfWeekName].type;
if (datetimeConstants.allWeekDays[defaultFirstDayOfWeekName]) {
defaultFirstDayOfWeek = datetimeConstants.allWeekDays[defaultFirstDayOfWeekName].type;
}
store.dispatch('updateLocalizedDefaultSettings', { defaultCurrency, defaultFirstDayOfWeek });
const settingsStore = useSettingsStore();
settingsStore.updateLocalizedDefaultSettings({ defaultCurrency, defaultFirstDayOfWeek });
return locale;
}
@@ -260,7 +253,8 @@ function setTimezone(timezone) {
}
function initLocale() {
const lastUserLanguage = store.getters.currentUserLanguage;
const userStore = useUserStore();
const lastUserLanguage = userStore.currentUserLanguage;
if (lastUserLanguage && getLanguageInfo(lastUserLanguage)) {
logger.info(`Last user language is ${lastUserLanguage}`);
@@ -273,7 +267,7 @@ function initLocale() {
logger.info(`Current timezone is ${settings.getTimezone()}`);
setTimezone(settings.getTimezone());
} else {
logger.info(`No timezone is set, use browser default ${utilities.getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`);
logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`);
}
}
@@ -302,26 +296,6 @@ app.directive('TextareaAutoSize', TextareaAutoSize);
app.config.globalProperties.$version = version.getVersion();
app.config.globalProperties.$buildTime = version.getBuildTime();
app.config.globalProperties.$licenses = {
license: licenses.getLicense(),
thirdPartyLicenses: licenses.getThirdPartyLicenses()
};
app.config.globalProperties.$constants = {
api: api,
datetime: datetime,
currency: currency,
colors: colors,
icons: icons,
account: account,
transaction: transaction,
category: category,
statistics: statistics,
};
app.config.globalProperties.$utilities = utilities;
app.config.globalProperties.$logger = logger;
app.config.globalProperties.$webauthn = webauthn;
app.config.globalProperties.$settings = settings;
app.config.globalProperties.$locale = {
getDefaultLanguage: getDefaultLanguage,
@@ -336,20 +310,22 @@ app.config.globalProperties.$locale = {
getAllShortDateFormats: () => getAllShortDateFormats(i18n.global.t),
getAllLongTimeFormats: () => getAllLongTimeFormats(i18n.global.t),
getAllShortTimeFormats: () => getAllShortTimeFormats(i18n.global.t),
getLongDateTimeFormat: () => getI18nLongDateFormat(i18n.global.t, store.getters.currentUserLongDateFormat) + ' ' + getI18nLongTimeFormat(i18n.global.t, store.getters.currentUserLongTimeFormat),
getShortDateTimeFormat: () => getI18nShortDateFormat(i18n.global.t, store.getters.currentUserShortDateFormat) + ' ' + getI18nShortTimeFormat(i18n.global.t, store.getters.currentUserShortTimeFormat),
getLongDateFormat: () => getI18nLongDateFormat(i18n.global.t, store.getters.currentUserLongDateFormat),
getShortDateFormat: () => getI18nShortDateFormat(i18n.global.t, store.getters.currentUserShortDateFormat),
getLongYearFormat: () => getI18nLongYearFormat(i18n.global.t, store.getters.currentUserLongDateFormat),
getShortYearFormat: () => getI18nShortYearFormat(i18n.global.t, store.getters.currentUserShortDateFormat),
getLongYearMonthFormat: () => getI18nLongYearMonthFormat(i18n.global.t, store.getters.currentUserLongDateFormat),
getShortYearMonthFormat: () => getI18nShortYearMonthFormat(i18n.global.t, store.getters.currentUserShortDateFormat),
getLongMonthDayFormat: () => getI18nLongMonthDayFormat(i18n.global.t, store.getters.currentUserLongDateFormat),
getShortMonthDayFormat: () => getI18nShortMonthDayFormat(i18n.global.t, store.getters.currentUserShortDateFormat),
getLongTimeFormat: () => getI18nLongTimeFormat(i18n.global.t, store.getters.currentUserLongTimeFormat),
getShortTimeFormat: () => getI18nShortTimeFormat(i18n.global.t, store.getters.currentUserShortTimeFormat),
isLongTime24HourFormat: () => isLongTime24HourFormat(i18n.global.t, store.getters.currentUserLongTimeFormat),
isShortTime24HourFormat: () => isShortTime24HourFormat(i18n.global.t, store.getters.currentUserShortTimeFormat),
formatUnixTimeToLongDateTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongDateFormat(i18n.global.t, userStore.currentUserLongDateFormat) + ' ' + getI18nLongTimeFormat(i18n.global.t, userStore.currentUserLongTimeFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortDateTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortDateFormat(i18n.global.t, userStore.currentUserShortDateFormat) + ' ' + getI18nShortTimeFormat(i18n.global.t, userStore.currentUserShortTimeFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongDate: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongDateFormat(i18n.global.t, userStore.currentUserLongDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortDate: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortDateFormat(i18n.global.t, userStore.currentUserShortDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongYear: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongYearFormat(i18n.global.t, userStore.currentUserLongDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortYear: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortYearFormat(i18n.global.t, userStore.currentUserShortDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongYearMonth: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongYearMonthFormat(i18n.global.t, userStore.currentUserLongDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortYearMonth: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortYearMonthFormat(i18n.global.t, userStore.currentUserShortDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongMonthDay: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongMonthDayFormat(i18n.global.t, userStore.currentUserLongDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortMonthDay: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortMonthDayFormat(i18n.global.t, userStore.currentUserShortDateFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongTimeFormat(i18n.global.t, userStore.currentUserLongTimeFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortTimeFormat(i18n.global.t, userStore.currentUserShortTimeFormat), utcOffset, currentUtcOffset),
formatTimeToLongYearMonth: (userStore, dateTime) => formatTime(dateTime, getI18nLongYearMonthFormat(i18n.global.t, userStore.currentUserLongDateFormat)),
formatTimeToShortYearMonth: (userStore, dateTime) => formatTime(dateTime, getI18nShortYearMonthFormat(i18n.global.t, userStore.currentUserShortDateFormat)),
isLongTime24HourFormat: (userStore) => isLongTime24HourFormat(i18n.global.t, userStore.currentUserLongTimeFormat),
isShortTime24HourFormat: (userStore) => isShortTime24HourFormat(i18n.global.t, userStore.currentUserShortTimeFormat),
setLanguage: setLanguage,
getTimezone: settings.getTimezone,
setTimezone: setTimezone,
@@ -370,11 +346,6 @@ app.config.globalProperties.$toast = (message, timeout) => showToast(message, ti
app.config.globalProperties.$showLoading = showLoading;
app.config.globalProperties.$hideLoading = hideLoading;
app.config.globalProperties.$routeBackOnError = routeBackOnError;
app.config.globalProperties.$ui = {
elements: elements,
isModalShowing: isModalShowing,
onSwipeoutDeleted: onSwipeoutDeleted
};
app.config.globalProperties.$user = userstate;
+26 -26
View File
@@ -1,37 +1,37 @@
import userState from '../lib/userstate.js';
import userState from '@/lib/userstate.js';
import HomePage from '../views/mobile/HomePage.vue';
import LoginPage from '../views/mobile/LoginPage.vue';
import SignUpPage from '../views/mobile/SignupPage.vue';
import UnlockPage from '../views/mobile/UnlockPage.vue';
import HomePage from '@/views/mobile/HomePage.vue';
import LoginPage from '@/views/mobile/LoginPage.vue';
import SignUpPage from '@/views/mobile/SignupPage.vue';
import UnlockPage from '@/views/mobile/UnlockPage.vue';
import TransactionListPage from '../views/mobile/transactions/ListPage.vue';
import TransactionEditPage from '../views/mobile/transactions/EditPage.vue';
import TransactionListPage from '@/views/mobile/transactions/ListPage.vue';
import TransactionEditPage from '@/views/mobile/transactions/EditPage.vue';
import AccountListPage from '../views/mobile/accounts/ListPage.vue';
import AccountEditPage from '../views/mobile/accounts/EditPage.vue';
import AccountListPage from '@/views/mobile/accounts/ListPage.vue';
import AccountEditPage from '@/views/mobile/accounts/EditPage.vue';
import StatisticsTransactionPage from '../views/mobile/statistics/TransactionPage.vue';
import StatisticsSettingsPage from '../views/mobile/statistics/SettingsPage.vue';
import StatisticsAccountFilterSettingsPage from '../views/mobile/statistics/AccountFilterSettingsPage.vue';
import StatisticsCategoryFilterSettingsPage from '../views/mobile/statistics/CategoryFilterSettingsPage.vue';
import StatisticsTransactionPage from '@/views/mobile/statistics/TransactionPage.vue';
import StatisticsSettingsPage from '@/views/mobile/statistics/SettingsPage.vue';
import StatisticsAccountFilterSettingsPage from '@/views/mobile/statistics/AccountFilterSettingsPage.vue';
import StatisticsCategoryFilterSettingsPage from '@/views/mobile/statistics/CategoryFilterSettingsPage.vue';
import SettingsPage from '../views/mobile/SettingsPage.vue';
import ApplicationLockPage from '../views/mobile/ApplicationLockPage.vue';
import ExchangeRatesPage from '../views/mobile/ExchangeRatesPage.vue';
import AboutPage from '../views/mobile/AboutPage.vue';
import SettingsPage from '@/views/mobile/SettingsPage.vue';
import ApplicationLockPage from '@/views/mobile/ApplicationLockPage.vue';
import ExchangeRatesPage from '@/views/mobile/ExchangeRatesPage.vue';
import AboutPage from '@/views/mobile/AboutPage.vue';
import UserProfilePage from '../views/mobile/users/UserProfilePage.vue';
import DataManagementPage from '../views/mobile/users/DataManagementPage.vue';
import TwoFactorAuthPage from '../views/mobile/users/TwoFactorAuthPage.vue';
import SessionListPage from '../views/mobile/users/SessionListPage.vue';
import UserProfilePage from '@/views/mobile/users/UserProfilePage.vue';
import DataManagementPage from '@/views/mobile/users/DataManagementPage.vue';
import TwoFactorAuthPage from '@/views/mobile/users/TwoFactorAuthPage.vue';
import SessionListPage from '@/views/mobile/users/SessionListPage.vue';
import CategoryAllPage from '../views/mobile/categories/AllPage.vue';
import CategoryListPage from '../views/mobile/categories/ListPage.vue';
import CategoryEditPage from '../views/mobile/categories/EditPage.vue';
import CategoryPresetPage from '../views/mobile/categories/PresetPage.vue';
import CategoryAllPage from '@/views/mobile/categories/AllPage.vue';
import CategoryListPage from '@/views/mobile/categories/ListPage.vue';
import CategoryEditPage from '@/views/mobile/categories/EditPage.vue';
import CategoryPresetPage from '@/views/mobile/categories/PresetPage.vue';
import TagListPage from '../views/mobile/tags/ListPage.vue';
import TagListPage from '@/views/mobile/tags/ListPage.vue';
function asyncResolve(component) {
return function({ resolve }) {
-363
View File
@@ -1,363 +0,0 @@
import accountConstants from '../consts/account.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import {
LOAD_ACCOUNT_LIST,
ADD_ACCOUNT_TO_ACCOUNT_LIST,
SAVE_ACCOUNT_IN_ACCOUNT_LIST,
CHANGE_ACCOUNT_DISPLAY_ORDER_IN_ACCOUNT_LIST,
UPDATE_ACCOUNT_VISIBILITY_IN_ACCOUNT_LIST,
REMOVE_ACCOUNT_FROM_ACCOUNT_LIST,
UPDATE_ACCOUNT_LIST_INVALID_STATE
} from './mutations.js';
export function loadAllAccounts(context, { force }) {
if (!force && !context.state.accountListStateInvalid) {
return new Promise((resolve) => {
resolve(context.state.allAccounts);
});
}
return new Promise((resolve, reject) => {
services.getAllAccounts({
visibleOnly: false
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account list' });
return;
}
context.commit(LOAD_ACCOUNT_LIST, data.result);
if (context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load account list', error);
} else {
logger.error('failed to load account list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get account list' });
} else {
reject(error);
}
});
});
}
export function getAccount(context, { accountId }) {
return new Promise((resolve, reject) => {
services.getAccount({
id: accountId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load account info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get account' });
} else {
reject(error);
}
});
});
}
export function saveAccount(context, { account }) {
const oldAccount = account.id ? context.state.allAccountsMap[account.id] : null;
return new Promise((resolve, reject) => {
let promise = null;
if (!account.id) {
promise = services.addAccount(account);
} else {
promise = services.modifyAccount(account);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
return;
}
if (!account.id) {
context.commit(ADD_ACCOUNT_TO_ACCOUNT_LIST, data.result);
} else {
if (oldAccount && oldAccount.category === data.result.category) {
context.commit(SAVE_ACCOUNT_IN_ACCOUNT_LIST, data.result);
} else {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save account', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
} else {
reject(error);
}
});
});
}
export function changeAccountDisplayOrder(context, { accountId, from, to }) {
const account = context.state.allAccountsMap[accountId];
return new Promise((resolve, reject) => {
if (!account ||
!context.state.allCategorizedAccounts[account.category] ||
!context.state.allCategorizedAccounts[account.category].accounts ||
!context.state.allCategorizedAccounts[account.category].accounts[to]) {
reject({ message: 'Unable to move account' });
return;
}
if (!context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
context.commit(CHANGE_ACCOUNT_DISPLAY_ORDER_IN_ACCOUNT_LIST, {
account: account,
from: from,
to: to
});
resolve();
});
}
export function updateAccountDisplayOrders(context) {
const newDisplayOrders = [];
for (let category in context.state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(context.state.allCategorizedAccounts, category)) {
continue;
}
const accountList = context.state.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
newDisplayOrders.push({
id: accountList[i].id,
displayOrder: i + 1
});
}
}
return new Promise((resolve, reject) => {
services.moveAccount({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move account' });
return;
}
if (context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save accounts display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move account' });
} else {
reject(error);
}
});
});
}
export function hideAccount(context, { account, hidden }) {
return new Promise((resolve, reject) => {
services.hideAccount({
id: account.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
return;
}
context.commit(UPDATE_ACCOUNT_VISIBILITY_IN_ACCOUNT_LIST, {
account: account,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change account visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
} else {
reject(error);
}
});
});
}
export function deleteAccount(context, { account, beforeResolve }) {
return new Promise((resolve, reject) => {
services.deleteAccount({
id: account.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this account' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
context.commit(REMOVE_ACCOUNT_FROM_ACCOUNT_LIST, account);
});
} else {
context.commit(REMOVE_ACCOUNT_FROM_ACCOUNT_LIST, account);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete account', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this account' });
} else {
reject(error);
}
});
});
}
export function allPlainAccounts(state) {
const allAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allAccounts.push(subAccount);
}
}
}
return allAccounts;
}
export function allVisiblePlainAccounts(state) {
const allVisibleAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.hidden) {
continue;
}
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allVisibleAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allVisibleAccounts.push(subAccount);
}
}
}
return allVisibleAccounts;
}
export function allAvailableAccountsCount(state) {
let allAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
allAccountCount += state.allCategorizedAccounts[category].accounts.length;
}
return allAccountCount;
}
export function allVisibleAccountsCount(state) {
let shownAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
const accountList = state.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (!accountList[i].hidden) {
shownAccountCount++;
}
}
}
return shownAccountCount;
}
-107
View File
@@ -1,107 +0,0 @@
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import {
STORE_LATEST_EXCHANGE_RATES
} from './mutations.js';
const exchangeRatesLocalStorageKey = 'ebk_app_exchange_rates';
export function getLatestExchangeRates(context, { silent, force }) {
const currentExchangeRateData = context.state.latestExchangeRates;
const now = utilities.getCurrentUnixTime();
if (!force) {
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
utilities.formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === utilities.formatUnixTime(now, 'YYYY-MM-DD')) {
return currentExchangeRateData.data;
}
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
utilities.formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === utilities.formatUnixTime(now, 'YYYY-MM-DD HH')) {
return currentExchangeRateData.data;
}
}
return new Promise((resolve, reject) => {
services.getLatestExchangeRates({
ignoreError: silent
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get exchange rates data' });
return;
}
const currentData = getExchangeRatesFromLocalStorage();
if (currentData && currentData.data && utilities.isEquals(currentData.data, data.result)) {
reject({ message: 'Exchange rates data is up to date' });
return;
}
context.commit(STORE_LATEST_EXCHANGE_RATES, {
time: now,
data: data.result
});
resolve(data.result);
}).catch(error => {
logger.error('failed to get latest exchange rates data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to get exchange rates data' });
}
});
});
}
export function exchangeRatesLastUpdateTime(state) {
const exchangeRates = state.latestExchangeRates || {};
return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null;
}
export function getExchangedAmount(state) {
return (amount, fromCurrency, toCurrency) => {
if (!state.latestExchangeRates || !state.latestExchangeRates.data || !state.latestExchangeRates.data.exchangeRates) {
return null;
}
const exchangeRates = state.latestExchangeRates.data.exchangeRates;
const exchangeRateMap = {};
for (let i = 0; i < exchangeRates.length; i++) {
const exchangeRate = exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
const fromCurrencyExchangeRate = exchangeRateMap[fromCurrency];
const toCurrencyExchangeRate = exchangeRateMap[toCurrency];
if (!fromCurrencyExchangeRate || !toCurrencyExchangeRate) {
return null;
}
return utilities.getExchangedAmount(amount, fromCurrencyExchangeRate.rate, toCurrencyExchangeRate.rate)
};
}
export function getExchangeRatesFromLocalStorage() {
const storageData = localStorage.getItem(exchangeRatesLocalStorageKey) || '{}';
return JSON.parse(storageData);
}
export function setExchangeRatesToLocalStorage(value) {
const storageData = JSON.stringify(value);
localStorage.setItem(exchangeRatesLocalStorageKey, storageData);
}
export function clearExchangeRatesFromLocalStorage() {
localStorage.removeItem(exchangeRatesLocalStorageKey);
}
-1033
View File
File diff suppressed because it is too large Load Diff
-48
View File
@@ -1,48 +0,0 @@
export const RESET_STATE = 'RESET_STATE';
export const UPDATE_DEFAULT_SETTING = 'UPDATE_DEFAULT_SETTING';
export const STORE_USER_INFO = 'STORE_USER_INFO';
export const CLEAR_USER_INFO = 'CLEAR_USER_INFO';
export const STORE_LATEST_EXCHANGE_RATES = 'STORE_LATEST_EXCHANGE_RATES';
export const LOAD_ACCOUNT_LIST = 'LOAD_ACCOUNT_LIST';
export const ADD_ACCOUNT_TO_ACCOUNT_LIST = 'ADD_ACCOUNT_TO_ACCOUNT_LIST';
export const SAVE_ACCOUNT_IN_ACCOUNT_LIST = 'SAVE_ACCOUNT_IN_ACCOUNT_LIST';
export const CHANGE_ACCOUNT_DISPLAY_ORDER_IN_ACCOUNT_LIST = 'CHANGE_ACCOUNT_DISPLAY_ORDER_IN_ACCOUNT_LIST';
export const UPDATE_ACCOUNT_VISIBILITY_IN_ACCOUNT_LIST = 'UPDATE_ACCOUNT_VISIBILITY_IN_ACCOUNT_LIST';
export const REMOVE_ACCOUNT_FROM_ACCOUNT_LIST = 'REMOVE_ACCOUNT_FROM_ACCOUNT_LIST';
export const UPDATE_ACCOUNT_LIST_INVALID_STATE = 'UPDATE_ACCOUNT_LIST_INVALID_STATE';
export const LOAD_TRANSACTION_LIST = 'LOAD_TRANSACTION_LIST';
export const INIT_TRANSACTION_LIST_FILTER = 'INIT_TRANSACTION_LIST_FILTER';
export const UPDATE_TRANSACTION_LIST_FILTER = 'UPDATE_TRANSACTION_LIST_FILTER';
export const COLLAPSE_MONTH_IN_TRANSACTION_LIST = 'COLLAPSE_MONTH_IN_TRANSACTION_LIST';
export const SAVE_TRANSACTION_IN_TRANSACTION_LIST = 'SAVE_TRANSACTION_IN_TRANSACTION_LIST';
export const REMOVE_TRANSACTION_FROM_TRANSACTION_LIST = 'REMOVE_TRANSACTION_FROM_TRANSACTION_LIST';
export const UPDATE_TRANSACTION_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_LIST_INVALID_STATE';
export const LOAD_TRANSACTION_CATEGORY_LIST = 'LOAD_TRANSACTION_CATEGORY_LIST';
export const ADD_CATEGORY_TO_TRANSACTION_CATEGORY_LIST = 'ADD_CATEGORY_TO_TRANSACTION_CATEGORY_LIST';
export const SAVE_CATEGORY_IN_TRANSACTION_CATEGORY_LIST = 'SAVE_CATEGORY_IN_TRANSACTION_CATEGORY_LIST';
export const CHANGE_CATEGORY_DISPLAY_ORDER_IN_CATEGORY_LIST = 'CHANGE_CATEGORY_DISPLAY_ORDER_IN_CATEGORY_LIST';
export const UPDATE_CATEGORY_VISIBILITY_IN_TRANSACTION_CATEGORY_LIST = 'UPDATE_CATEGORY_VISIBILITY_IN_TRANSACTION_CATEGORY_LIST';
export const REMOVE_CATEGORY_FROM_TRANSACTION_CATEGORYLIST = 'REMOVE_CATEGORY_FROM_TRANSACTION_CATEGORYLIST';
export const UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE';
export const LOAD_TRANSACTION_TAG_LIST = 'LOAD_TRANSACTION_TAG_LIST';
export const ADD_TAG_TO_TRANSACTION_TAG_LIST = 'ADD_TAG_TO_TRANSACTION_TAG_LIST';
export const SAVE_TAG_IN_TRANSACTION_TAG_LIST = 'SAVE_TAG_IN_TRANSACTION_TAG_LIST';
export const CHANGE_TAG_DISPLAY_ORDER_IN_TRANSACTION_TAG_LIST = 'CHANGE_TAG_DISPLAY_ORDER_IN_TRANSACTION_TAG_LIST';
export const UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST = 'UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST';
export const REMOVE_TAG_FROM_TRANSACTION_TAG_LIST = 'REMOVE_TAG_FROM_TRANSACTION_TAG_LIST';
export const UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE';
export const LOAD_TRANSACTION_OVERVIEW = 'LOAD_TRANSACTION_OVERVIEW';
export const UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE = 'UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE';
export const LOAD_TRANSACTION_STATISTICS = 'LOAD_TRANSACTION_STATISTICS';
export const INIT_TRANSACTION_STATISTICS_FILTER = 'INIT_TRANSACTION_STATISTICS_FILTER';
export const UPDATE_TRANSACTION_STATISTICS_FILTER = 'UPDATE_TRANSACTION_STATISTICS_FILTER';
export const UPDATE_TRANSACTION_STATISTICS_INVALID_STATE = 'UPDATE_TRANSACTION_STATISTICS_INVALID_STATE';
-104
View File
@@ -1,104 +0,0 @@
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import { getExchangedAmount } from './exchangeRates.js';
import {
LOAD_TRANSACTION_OVERVIEW,
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
} from './mutations.js';
export function loadTransactionOverview(context, { defaultCurrency, dateRange, force }) {
if (!force && !context.state.transactionOverviewStateInvalid) {
return new Promise((resolve) => {
resolve(context.state.transactionOverview);
});
}
return new Promise((resolve, reject) => {
services.getTransactionAmounts({
today: dateRange.today,
thisWeek: dateRange.thisWeek,
thisMonth: dateRange.thisMonth,
thisYear: dateRange.thisYear
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction overview' });
return;
}
const overview = data.result;
for (let field in overview) {
if (!Object.prototype.hasOwnProperty.call(overview, field)) {
continue;
}
const item = overview[field];
if (!item.amounts || !item.amounts.length) {
item.amounts = [];
}
let totalIncomeAmount = 0;
let totalExpenseAmount = 0;
let hasUnCalculatedTotalIncome = false;
let hasUnCalculatedTotalExpense = false;
for (let i = 0; i < item.amounts.length; i++) {
const amount = item.amounts[i];
if (amount.currency !== defaultCurrency) {
const incomeAmount = getExchangedAmount(context.state)(amount.incomeAmount, amount.currency, defaultCurrency);
const expenseAmount = getExchangedAmount(context.state)(amount.expenseAmount, amount.currency, defaultCurrency);
if (utilities.isNumber(incomeAmount)) {
totalIncomeAmount += Math.floor(incomeAmount);
} else {
hasUnCalculatedTotalIncome = true;
}
if (utilities.isNumber(expenseAmount)) {
totalExpenseAmount += Math.floor(expenseAmount);
} else {
hasUnCalculatedTotalExpense = true;
}
} else {
totalIncomeAmount += amount.incomeAmount;
totalExpenseAmount += amount.expenseAmount;
}
}
item.incomeAmount = totalIncomeAmount;
item.expenseAmount = totalExpenseAmount;
item.incompleteIncomeAmount = hasUnCalculatedTotalIncome;
item.incompleteExpenseAmount = hasUnCalculatedTotalExpense;
}
context.commit(LOAD_TRANSACTION_OVERVIEW, overview);
if (context.state.transactionOverviewStateInvalid) {
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, false);
}
resolve(overview);
}).catch(error => {
if (force) {
logger.error('failed to force load transaction overview', error);
} else {
logger.error('failed to load transaction overview', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction overview' });
} else {
reject(error);
}
});
});
}
-10
View File
@@ -1,10 +0,0 @@
import {
UPDATE_DEFAULT_SETTING
} from './mutations.js';
export function updateLocalizedDefaultSettings(context, { defaultCurrency, defaultFirstDayOfWeek }) {
context.commit(UPDATE_DEFAULT_SETTING, {
defaultCurrency,
defaultFirstDayOfWeek,
});
}
-268
View File
@@ -1,268 +0,0 @@
import statisticsConstants from '../consts/statistics.js';
import categoryConstants from '../consts/category.js';
import iconConstants from '../consts/icon.js';
import colorConstants from '../consts/color.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import { getExchangedAmount } from './exchangeRates.js';
import {
LOAD_TRANSACTION_STATISTICS,
INIT_TRANSACTION_STATISTICS_FILTER,
UPDATE_TRANSACTION_STATISTICS_FILTER,
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE
} from './mutations.js';
export function loadTransactionStatistics(context, { defaultCurrency }) {
return new Promise((resolve, reject) => {
services.getTransactionStatistics({
startTime: context.state.transactionStatisticsFilter.startTime,
endTime: context.state.transactionStatisticsFilter.endTime
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction statistics' });
return;
}
context.commit(LOAD_TRANSACTION_STATISTICS, {
statistics: data.result,
defaultCurrency: defaultCurrency
});
if (context.state.transactionStatisticsStateInvalid) {
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get transaction statistics', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction statistics' });
} else {
reject(error);
}
});
});
}
export function initTransactionStatisticsFilter(context, filter) {
context.commit(INIT_TRANSACTION_STATISTICS_FILTER, filter);
}
export function updateTransactionStatisticsFilter(context, filter) {
context.commit(UPDATE_TRANSACTION_STATISTICS_FILTER, filter);
}
export function statisticsItemsByTransactionStatisticsData(state) {
if (!state.transactionStatistics || !state.transactionStatistics.items) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < state.transactionStatistics.items.length; i++) {
const item = state.transactionStatistics.items[i];
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Expense) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Income) {
continue;
}
} else {
continue;
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[item.account.id]) {
continue;
}
if (state.transactionStatisticsFilter.filterCategoryIds && state.transactionStatisticsFilter.filterCategoryIds[item.category.id]) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByAccount.type) {
if (utilities.isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.account.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.account.name,
type: 'account',
id: item.account.id,
icon: item.account.icon || iconConstants.defaultAccountIcon.icon,
color: item.account.color || colorConstants.defaultAccountColor,
hidden: item.primaryAccount.hidden || item.account.hidden,
displayOrders: [item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.account.id] = data;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByPrimaryCategory.type) {
if (utilities.isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.primaryCategory.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.primaryCategory.name,
type: 'category',
id: item.primaryCategory.id,
icon: item.primaryCategory.icon || iconConstants.defaultCategoryIcon.icon,
color: item.primaryCategory.color || colorConstants.defaultCategoryColor,
hidden: item.primaryCategory.hidden,
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.primaryCategory.id] = data;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeBySecondaryCategory.type) {
if (utilities.isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.category.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.category.name,
type: 'category',
id: item.category.id,
icon: item.category.icon || iconConstants.defaultCategoryIcon.icon,
color: item.category.color || colorConstants.defaultCategoryColor,
hidden: item.primaryCategory.hidden || item.category.hidden,
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder, item.category.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.category.id] = data;
}
}
}
return {
totalAmount: totalAmount,
totalNonNegativeAmount: totalNonNegativeAmount,
items: allDataItems
}
}
export function statisticsItemsByAccountsData(state, getters) {
if (!getters.allPlainAccounts) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < getters.allPlainAccounts.length; i++) {
const account = getters.allPlainAccounts[i];
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalAssets.type) {
if (!account.isAsset) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalLiabilities.type) {
if (!account.isLiability) {
continue;
}
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[account.id]) {
continue;
}
let primaryAccount = state.allAccountsMap[account.parentId];
if (!primaryAccount) {
primaryAccount = account;
}
let amount = account.balance;
if (account.currency !== getters.currentUserDefaultCurrency) {
amount = Math.floor(getExchangedAmount(state)(amount, account.currency, getters.currentUserDefaultCurrency));
if (!utilities.isNumber(amount)) {
continue;
}
}
if (account.isLiability) {
amount = -amount;
}
const data = {
name: account.name,
type: 'account',
id: account.id,
icon: account.icon || iconConstants.defaultAccountIcon.icon,
color: account.color || colorConstants.defaultAccountColor,
hidden: primaryAccount.hidden || account.hidden,
displayOrders: [primaryAccount.category, primaryAccount.displayOrder, account.displayOrder],
totalAmount: amount
};
totalAmount += amount;
if (amount > 0) {
totalNonNegativeAmount += amount;
}
allDataItems[account.id] = data;
}
return {
totalAmount: totalAmount,
totalNonNegativeAmount: totalNonNegativeAmount,
items: allDataItems
}
}
-111
View File
@@ -1,111 +0,0 @@
import userState from '../lib/userstate.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import {
STORE_USER_INFO
} from './mutations.js';
export function getAllTokens() {
return new Promise((resolve, reject) => {
services.getTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get session list' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load token list', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get session list' });
} else {
reject(error);
}
});
});
}
export function refreshTokenAndRevokeOldToken(context) {
return new Promise((resolve) => {
services.refreshToken().then(response => {
const data = response.data;
if (data && data.success && data.result && data.result.newToken) {
userState.updateToken(data.result.newToken);
if (data.result.user && utilities.isObject(data.result.user)) {
context.commit(STORE_USER_INFO, data.result.user);
}
if (data.result.oldTokenId) {
revokeToken(context, {
tokenId: data.result.oldTokenId,
ignoreError: true
});
}
}
resolve(data.result);
});
});
}
export function revokeToken(context, { tokenId, ignoreError }) {
return new Promise((resolve, reject) => {
services.revokeToken({
tokenId: tokenId,
ignoreError: !!ignoreError
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout from this session' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke token', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout from this session' });
} else {
reject(error);
}
});
});
}
export function revokeAllTokens() {
return new Promise((resolve, reject) => {
services.revokeAllTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout all other sessions' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke all tokens', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout all other sessions' });
} else {
reject(error);
}
});
});
}
-374
View File
@@ -1,374 +0,0 @@
import transactionConstants from '../consts/transaction.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import { getExchangedAmount } from './exchangeRates.js';
import {
LOAD_TRANSACTION_LIST,
INIT_TRANSACTION_LIST_FILTER,
UPDATE_TRANSACTION_LIST_FILTER,
COLLAPSE_MONTH_IN_TRANSACTION_LIST,
SAVE_TRANSACTION_IN_TRANSACTION_LIST,
REMOVE_TRANSACTION_FROM_TRANSACTION_LIST,
UPDATE_TRANSACTION_LIST_INVALID_STATE,
UPDATE_ACCOUNT_LIST_INVALID_STATE,
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE,
} from './mutations.js';
const emptyTransactionResult = {
items: [],
transactionsNextTimeId: 0
};
export function initTransactionListFilter(context, filter) {
context.commit(INIT_TRANSACTION_LIST_FILTER, filter);
}
export function updateTransactionListFilter(context, filter) {
context.commit(UPDATE_TRANSACTION_LIST_FILTER, filter);
}
export function loadTransactions(context, { reload, autoExpand, defaultCurrency }) {
let actualMaxTime = context.state.transactionsNextTimeId;
if (reload && context.state.transactionsFilter.maxTime > 0) {
actualMaxTime = context.state.transactionsFilter.maxTime * 1000 + 999;
} else if (reload && context.state.transactionsFilter.maxTime <= 0) {
actualMaxTime = 0;
}
return new Promise((resolve, reject) => {
services.getTransactions({
maxTime: actualMaxTime,
minTime: context.state.transactionsFilter.minTime * 1000,
type: context.state.transactionsFilter.type,
categoryId: context.state.transactionsFilter.categoryId,
accountId: context.state.transactionsFilter.accountId,
keyword: context.state.transactionsFilter.keyword
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (reload) {
context.commit(LOAD_TRANSACTION_LIST, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!context.state.transactionListStateInvalid) {
context.commit(UPDATE_TRANSACTION_LIST_INVALID_STATE, true);
}
}
reject({ message: 'Unable to get transaction list' });
return;
}
context.commit(LOAD_TRANSACTION_LIST, {
transactions: data.result,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (reload) {
if (context.state.transactionListStateInvalid) {
context.commit(UPDATE_TRANSACTION_LIST_INVALID_STATE, false);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction list', error);
if (reload) {
context.commit(LOAD_TRANSACTION_LIST, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!context.state.transactionListStateInvalid) {
context.commit(UPDATE_TRANSACTION_LIST_INVALID_STATE, true);
}
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction list' });
} else {
reject(error);
}
});
});
}
export function getTransaction(context, { transactionId }) {
return new Promise((resolve, reject) => {
services.getTransaction({
id: transactionId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction' });
} else {
reject(error);
}
});
});
}
export function saveTransaction(context, { transaction, defaultCurrency }) {
return new Promise((resolve, reject) => {
let promise = null;
if (!transaction.id) {
promise = services.addTransaction(transaction);
} else {
promise = services.modifyTransaction(transaction);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
return;
}
if (!transaction.id) {
if (!context.state.transactionListStateInvalid) {
context.commit(UPDATE_TRANSACTION_LIST_INVALID_STATE, true);
}
} else {
context.commit(SAVE_TRANSACTION_IN_TRANSACTION_LIST, {
transaction: data.result,
defaultCurrency: defaultCurrency
});
}
if (!context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
if (!context.state.transactionOverviewStateInvalid) {
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
}
if (!context.state.transactionStatisticsStateInvalid) {
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save transaction', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
} else {
reject(error);
}
});
});
}
export function deleteTransaction(context, { transaction, defaultCurrency, beforeResolve }) {
return new Promise((resolve, reject) => {
services.deleteTransaction({
id: transaction.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this transaction' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
context.commit(REMOVE_TRANSACTION_FROM_TRANSACTION_LIST, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
});
} else {
context.commit(REMOVE_TRANSACTION_FROM_TRANSACTION_LIST, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
}
if (!context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
if (!context.state.transactionOverviewStateInvalid) {
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
}
if (!context.state.transactionStatisticsStateInvalid) {
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete transaction', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this transaction' });
} else {
reject(error);
}
});
});
}
export function collapseMonthInTransactionList(context, { month, collapse }) {
context.commit(COLLAPSE_MONTH_IN_TRANSACTION_LIST, {
month: month,
collapse: collapse
});
}
export function noTransaction(state) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j]) {
return false;
}
}
}
return true;
}
export function hasMoreTransaction(state) {
return state.transactionsNextTimeId > 0;
}
export function fillTransactionObject(state, transaction, currentUtcOffset) {
if (!transaction) {
return;
}
const transactionTime = utilities.parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
transaction.day = utilities.getDay(transactionTime);
transaction.dayOfWeek = utilities.getDayOfWeekName(transactionTime);
if (transaction.sourceAccountId) {
transaction.sourceAccount = state.allAccountsMap[transaction.sourceAccountId];
}
if (transaction.destinationAccountId) {
transaction.destinationAccount = state.allAccountsMap[transaction.destinationAccountId];
}
if (transaction.categoryId) {
transaction.category = state.allTransactionCategoriesMap[transaction.categoryId];
}
return transaction;
}
export function calculateMonthTotalAmount(state, transactionMonthList, defaultCurrency, accountId, incomplete) {
if (!transactionMonthList) {
return;
}
let totalExpense = 0;
let totalIncome = 0;
let hasUnCalculatedTotalExpense = false;
let hasUnCalculatedTotalIncome = false;
for (let i = 0; i < transactionMonthList.items.length; i++) {
const transaction = transactionMonthList.items[i];
let amount = transaction.sourceAmount;
let account = transaction.sourceAccount;
if (accountId && transaction.destinationAccount && (transaction.destinationAccount.id === accountId || transaction.destinationAccount.parentId === accountId)) {
amount = transaction.destinationAmount;
account = transaction.destinationAccount;
}
if (!account) {
continue;
}
if (account.currency !== defaultCurrency) {
const balance = getExchangedAmount(state)(amount, account.currency, defaultCurrency);
if (!utilities.isNumber(balance)) {
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
hasUnCalculatedTotalExpense = true;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
hasUnCalculatedTotalIncome = true;
}
continue;
}
amount = Math.floor(balance);
}
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
totalExpense += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
totalIncome += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Transfer && accountId) {
if (accountId === transaction.sourceAccountId) {
totalExpense += amount;
} else if (accountId === transaction.destinationAccountId) {
totalIncome += amount;
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId &&
transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
// Do Nothing
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId) {
totalExpense += amount;
} else if (transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
totalIncome += amount;
}
}
}
transactionMonthList.totalAmount = {
expense: totalExpense,
incompleteExpense: incomplete || hasUnCalculatedTotalExpense,
income: totalIncome,
incompleteIncome: incomplete || hasUnCalculatedTotalIncome
};
}
-355
View File
@@ -1,355 +0,0 @@
import categoryContants from '../consts/category.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import {
LOAD_TRANSACTION_CATEGORY_LIST,
ADD_CATEGORY_TO_TRANSACTION_CATEGORY_LIST,
SAVE_CATEGORY_IN_TRANSACTION_CATEGORY_LIST,
CHANGE_CATEGORY_DISPLAY_ORDER_IN_CATEGORY_LIST,
UPDATE_CATEGORY_VISIBILITY_IN_TRANSACTION_CATEGORY_LIST,
REMOVE_CATEGORY_FROM_TRANSACTION_CATEGORYLIST,
UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE,
} from './mutations.js';
export function loadAllCategories(context, { force }) {
if (!force && !context.state.transactionCategoryListStateInvalid) {
return new Promise((resolve) => {
resolve(context.state.allTransactionCategories);
});
}
return new Promise((resolve, reject) => {
services.getAllTransactionCategories().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get category list' });
return;
}
if (!data.result[categoryContants.allCategoryTypes.Income]) {
data.result[categoryContants.allCategoryTypes.Income] = [];
}
if (!data.result[categoryContants.allCategoryTypes.Expense]) {
data.result[categoryContants.allCategoryTypes.Expense] = [];
}
if (!data.result[categoryContants.allCategoryTypes.Transfer]) {
data.result[categoryContants.allCategoryTypes.Transfer] = [];
}
for (let categoryType in data.result) {
if (!Object.prototype.hasOwnProperty.call(data.result, categoryType)) {
continue;
}
const categories = data.result[categoryType];
for (let i = 0; i < categories.length; i++) {
const category = categories[i];
if (!category.subCategories) {
category.subCategories = [];
}
}
}
context.commit(LOAD_TRANSACTION_CATEGORY_LIST, data.result);
if (context.state.transactionCategoryListStateInvalid) {
context.commit(UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load category list', error);
} else {
logger.error('failed to load category list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get category list' });
} else {
reject(error);
}
});
});
}
export function getCategory(context, { categoryId }) {
return new Promise((resolve, reject) => {
services.getTransactionCategory({
id: categoryId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get category' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load category info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get category' });
} else {
reject(error);
}
});
});
}
export function saveCategory(context, { category }) {
return new Promise((resolve, reject) => {
let promise = null;
if (!category.id) {
promise = services.addTransactionCategory(category);
} else {
promise = services.modifyTransactionCategory(category);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!category.id) {
reject({ message: 'Unable to add category' });
} else {
reject({ message: 'Unable to save category' });
}
return;
}
if (!data.result.subCategories) {
data.result.subCategories = [];
}
if (!category.id) {
context.commit(ADD_CATEGORY_TO_TRANSACTION_CATEGORY_LIST, data.result);
} else {
context.commit(SAVE_CATEGORY_IN_TRANSACTION_CATEGORY_LIST, data.result);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save category', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!category.id) {
reject({ message: 'Unable to add category' });
} else {
reject({ message: 'Unable to save category' });
}
} else {
reject(error);
}
});
});
}
export function addCategories(context, { categories }) {
return new Promise((resolve, reject) => {
services.addTransactionCategoryBatch({
categories: categories
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to add preset categories' });
return;
}
if (!context.state.transactionCategoryListStateInvalid) {
context.commit(UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE, true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to add preset categories', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to add preset categories' });
} else {
reject(error);
}
});
});
}
export function changeCategoryDisplayOrder(context, { categoryId, from, to }) {
const category = context.state.allTransactionCategoriesMap[categoryId];
return new Promise((resolve, reject) => {
if (!category) {
reject({ message: 'Unable to move category' });
return;
}
if (!category.parentId || category.parentId === '0') {
if (!context.state.allTransactionCategories[category.type] ||
!context.state.allTransactionCategories[category.type][to]) {
reject({ message: 'Unable to move category' });
return;
}
} else {
if (!context.state.allTransactionCategoriesMap[category.parentId].subCategories ||
!context.state.allTransactionCategoriesMap[category.parentId].subCategories[to]) {
reject({ message: 'Unable to move category' });
return;
}
}
if (!context.state.transactionCategoryListStateInvalid) {
context.commit(UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE, true);
}
context.commit(CHANGE_CATEGORY_DISPLAY_ORDER_IN_CATEGORY_LIST, {
category: category,
from: from,
to: to
});
resolve();
});
}
export function updateCategoryDisplayOrders(context, { type, parentId }) {
const newDisplayOrders = [];
let categoryList = null;
if (!parentId || parentId === '0') {
categoryList = context.state.allTransactionCategories[type];
} else if (context.state.allTransactionCategoriesMap[parentId]) {
categoryList = context.state.allTransactionCategoriesMap[parentId].subCategories;
}
if (categoryList) {
for (let i = 0; i < categoryList.length; i++) {
newDisplayOrders.push({
id: categoryList[i].id,
displayOrder: i + 1
});
}
}
return new Promise((resolve, reject) => {
services.moveTransactionCategory({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move category' });
return;
}
if (context.state.transactionCategoryListStateInvalid) {
context.commit(UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save categories display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move category' });
} else {
reject(error);
}
});
});
}
export function hideCategory(context, { category, hidden }) {
return new Promise((resolve, reject) => {
services.hideTransactionCategory({
id: category.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this category' });
} else {
reject({ message: 'Unable to unhide this category' });
}
return;
}
context.commit(UPDATE_CATEGORY_VISIBILITY_IN_TRANSACTION_CATEGORY_LIST, {
category: category,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change category visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this category' });
} else {
reject({ message: 'Unable to unhide this category' });
}
} else {
reject(error);
}
});
});
}
export function deleteCategory(context, { category, beforeResolve }) {
return new Promise((resolve, reject) => {
services.deleteTransactionCategory({
id: category.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this category' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
context.commit(REMOVE_CATEGORY_FROM_TRANSACTION_CATEGORYLIST, category);
});
} else {
context.commit(REMOVE_CATEGORY_FROM_TRANSACTION_CATEGORYLIST, category);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete category', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this category' });
} else {
reject(error);
}
});
});
}
-247
View File
@@ -1,247 +0,0 @@
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import {
LOAD_TRANSACTION_TAG_LIST,
ADD_TAG_TO_TRANSACTION_TAG_LIST,
SAVE_TAG_IN_TRANSACTION_TAG_LIST,
CHANGE_TAG_DISPLAY_ORDER_IN_TRANSACTION_TAG_LIST,
UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST,
REMOVE_TAG_FROM_TRANSACTION_TAG_LIST,
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
} from './mutations.js';
export function loadAllTags(context, { force }) {
if (!force && !context.state.transactionTagListStateInvalid) {
return new Promise((resolve) => {
resolve(context.state.allTransactionTags);
});
}
return new Promise((resolve, reject) => {
services.getAllTransactionTags().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get tag list' });
return;
}
context.commit(LOAD_TRANSACTION_TAG_LIST, data.result);
if (context.state.transactionTagListStateInvalid) {
context.commit(UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load tag list', error);
} else {
logger.error('failed to load tag list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get tag list' });
} else {
reject(error);
}
});
});
}
export function saveTag(context, { tag }) {
return new Promise((resolve, reject) => {
let promise = null;
if (!tag.id) {
promise = services.addTransactionTag(tag);
} else {
promise = services.modifyTransactionTag(tag);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
return;
}
if (!tag.id) {
context.commit(ADD_TAG_TO_TRANSACTION_TAG_LIST, data.result);
} else {
context.commit(SAVE_TAG_IN_TRANSACTION_TAG_LIST, data.result);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tag', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
} else {
reject(error);
}
});
});
}
export function changeTagDisplayOrder(context, { tagId, from, to }) {
return new Promise((resolve, reject) => {
let tag = null;
for (let i = 0; i < context.state.allTransactionTags.length; i++) {
if (context.state.allTransactionTags[i].id === tagId) {
tag = context.state.allTransactionTags[i];
break;
}
}
if (!tag || !context.state.allTransactionTags[to]) {
reject({ message: 'Unable to move tag' });
return;
}
if (!context.state.transactionTagListStateInvalid) {
context.commit(UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE, true);
}
context.commit(CHANGE_TAG_DISPLAY_ORDER_IN_TRANSACTION_TAG_LIST, {
tag: tag,
from: from,
to: to
});
resolve();
});
}
export function updateTagDisplayOrders(context) {
const newDisplayOrders = [];
for (let i = 0; i < context.state.allTransactionTags.length; i++) {
newDisplayOrders.push({
id: context.state.allTransactionTags[i].id,
displayOrder: i + 1
});
}
return new Promise((resolve, reject) => {
services.moveTransactionTag({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move tag' });
return;
}
if (context.state.transactionTagListStateInvalid) {
context.commit(UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE, false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tags display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move tag' });
} else {
reject(error);
}
});
});
}
export function hideTag(context, { tag, hidden }) {
return new Promise((resolve, reject) => {
services.hideTransactionTag({
id: tag.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
return;
}
context.commit(UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST, {
tag: tag,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change tag visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
} else {
reject(error);
}
});
});
}
export function deleteTag(context, { tag, beforeResolve }) {
return new Promise((resolve, reject) => {
services.deleteTransactionTag({
id: tag.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this tag' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
context.commit(REMOVE_TAG_FROM_TRANSACTION_TAG_LIST, tag);
});
} else {
context.commit(REMOVE_TAG_FROM_TRANSACTION_TAG_LIST, tag);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete tag', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this tag' });
} else {
reject(error);
}
});
});
}
-140
View File
@@ -1,140 +0,0 @@
import userState from '../lib/userstate.js';
import services from '../lib/services.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
export function get2FAStatus() {
return new Promise((resolve, reject) => {
services.get2FAStatus().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !utilities.isBoolean(data.result.enable)) {
reject({ message: 'Unable to get current two factor authentication status' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get 2fa status', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get current two factor authentication status' });
} else {
reject(error);
}
});
});
}
export function enable2FA() {
return new Promise((resolve, reject) => {
services.enable2FA().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.qrcode || !data.result.secret) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to request to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
}
export function confirmEnable2FA(context, { secret, passcode }) {
return new Promise((resolve, reject) => {
services.confirmEnable2FA({
secret: secret,
passcode: passcode
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
if (data.result.token) {
userState.updateToken(data.result.token);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to confirm to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
}
export function disable2FA(context, { password }) {
return new Promise((resolve, reject) => {
services.disable2FA({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to disable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to disable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to disable two factor authentication' });
} else {
reject(error);
}
});
});
}
export function regenerate2FARecoveryCode(context, { password }) {
return new Promise((resolve, reject) => {
services.regenerate2FARecoveryCode({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.recoveryCodes || !data.result.recoveryCodes.length) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to regenerate 2fa recovery code', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
} else {
reject(error);
}
});
});
}
-438
View File
@@ -1,438 +0,0 @@
import userState from '../lib/userstate.js';
import services from '../lib/services.js';
import settings from '../lib/settings.js';
import logger from '../lib/logger.js';
import utilities from '../lib/utilities/index.js';
import {
RESET_STATE,
STORE_USER_INFO,
CLEAR_USER_INFO,
UPDATE_ACCOUNT_LIST_INVALID_STATE,
UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE,
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE
} from './mutations.js';
export function authorize(context, { loginName, password }) {
return new Promise((resolve, reject) => {
services.authorize({
loginName: loginName,
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to login' });
return;
}
if (data.result.need2FA) {
resolve(data.result);
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && utilities.isObject(data.result.user)) {
context.commit(STORE_USER_INFO, data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to login', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to login' });
}
});
});
}
export function authorize2FA(context, { token, passcode, recoveryCode }) {
return new Promise((resolve, reject) => {
let promise = null;
if (passcode) {
promise = services.authorize2FA({
passcode: passcode,
token: token
});
} else if (recoveryCode) {
promise = services.authorize2FAByBackupCode({
recoveryCode: recoveryCode,
token: token
});
} else {
reject({ message: 'An error has occurred' });
return;
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to verify' });
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && utilities.isObject(data.result.user)) {
context.commit(STORE_USER_INFO, data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to verify 2fa', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to verify' });
}
});
});
}
export function register(context, { user }) {
return new Promise((resolve, reject) => {
services.register({
username: user.username,
password: user.password,
email: user.email,
nickname: user.nickname,
language: user.language,
defaultCurrency: user.defaultCurrency,
firstDayOfWeek: user.firstDayOfWeek
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to sign up' });
return;
}
if (settings.isEnableApplicationLock()) {
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
if (data.result.token && utilities.isString(data.result.token)) {
userState.updateToken(data.result.token);
}
if (data.result.user && utilities.isObject(data.result.user)) {
context.commit(STORE_USER_INFO, data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to sign up', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to sign up' });
} else {
reject(error);
}
});
});
}
export function logout(context) {
return new Promise((resolve, reject) => {
services.logout().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout' });
return;
}
context.commit(CLEAR_USER_INFO);
userState.clearTokenAndUserInfo(true);
userState.clearWebAuthnConfig();
context.commit(RESET_STATE);
resolve(data.result);
}).catch(error => {
logger.error('failed to log out', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to logout' });
}
});
});
}
export function getCurrentUserProfile() {
return new Promise((resolve, reject) => {
services.getProfile().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user profile' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user profile' });
} else {
reject(error);
}
});
});
}
export function updateUserProfile(context, { profile, currentPassword }) {
return new Promise((resolve, reject) => {
services.updateProfile({
password: profile.password,
oldPassword: currentPassword,
email: profile.email,
nickname: profile.nickname,
defaultAccountId: profile.defaultAccountId,
transactionEditScope: profile.transactionEditScope,
language: profile.language,
defaultCurrency: profile.defaultCurrency,
firstDayOfWeek: profile.firstDayOfWeek,
longDateFormat: profile.longDateFormat,
shortDateFormat: profile.shortDateFormat,
longTimeFormat: profile.longTimeFormat,
shortTimeFormat: profile.shortTimeFormat
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to update user profile' });
return;
}
if (data.result.newToken && utilities.isString(data.result.newToken)) {
userState.updateToken(data.result.newToken);
}
if (data.result.user && utilities.isObject(data.result.user)) {
context.commit(STORE_USER_INFO, data.result.user);
}
if (!context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
if (!context.state.transactionOverviewStateInvalid) {
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
}
if (!context.state.transactionStatisticsStateInvalid) {
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to update user profile' });
} else {
reject(error);
}
});
});
}
export function getUserDataStatistics() {
return new Promise((resolve, reject) => {
services.getUserDataStatistics().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user statistics data' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user statistics data' });
} else {
reject(error);
}
});
});
}
export function getExportedUserData() {
return new Promise((resolve, reject) => {
services.getExportedUserData().then(response => {
if (response && response.headers && response.headers['content-type'] !== 'text/csv') {
reject({ message: 'Unable to get exported user data' });
return;
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
resolve(blob);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.headers['content-type'] === 'text/text' && error.response && error.response.data) {
reject({ message: 'error.' + error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get exported user data' });
} else {
reject(error);
}
});
});
}
export function clearUserData(context, { password }) {
return new Promise((resolve, reject) => {
services.clearData({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to clear user data' });
return;
}
if (!context.state.accountListStateInvalid) {
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
}
if (!context.state.transactionCategoryListStateInvalid) {
context.commit(UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE, true);
}
if (!context.state.transactionTagListStateInvalid) {
context.commit(UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE, true);
}
if (!context.state.transactionOverviewStateInvalid) {
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
}
if (!context.state.transactionStatisticsStateInvalid) {
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to clear user data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to clear user data' });
}
});
});
}
export function clearUserInfoState(context) {
context.commit(CLEAR_USER_INFO);
}
export function resetState(context) {
context.commit(RESET_STATE);
}
export function currentUserNickname(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.nickname || userInfo.username || null;
}
export function currentUserDefaultAccountId(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.defaultAccountId || '';
}
export function currentUserLanguage(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.language || state.defaultSetting.language;
}
export function currentUserDefaultCurrency(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.defaultCurrency || state.defaultSetting.currency;
}
export function currentUserFirstDayOfWeek(state) {
const userInfo = state.currentUserInfo || {};
return utilities.isNumber(userInfo.firstDayOfWeek) ? userInfo.firstDayOfWeek : state.defaultSetting.firstDayOfWeek;
}
export function currentUserLongDateFormat(state) {
const userInfo = state.currentUserInfo || {};
return utilities.isNumber(userInfo.longDateFormat) ? userInfo.longDateFormat : state.defaultSetting.longDateFormat;
}
export function currentUserShortDateFormat(state) {
const userInfo = state.currentUserInfo || {};
return utilities.isNumber(userInfo.shortDateFormat) ? userInfo.shortDateFormat : state.defaultSetting.shortDateFormat;
}
export function currentUserLongTimeFormat(state) {
const userInfo = state.currentUserInfo || {};
return utilities.isNumber(userInfo.longTimeFormat) ? userInfo.longTimeFormat : state.defaultSetting.longTimeFormat;
}
export function currentUserShortTimeFormat(state) {
const userInfo = state.currentUserInfo || {};
return utilities.isNumber(userInfo.shortTimeFormat) ? userInfo.shortTimeFormat : state.defaultSetting.shortTimeFormat;
}
+797
View File
@@ -0,0 +1,797 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import accountConstants from '@/consts/account.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
import { getCategorizedAccounts, getAllFilteredAccountsBalance } from '@/lib/account.js';
function loadAccountList(state, accounts) {
state.allAccounts = accounts;
state.allAccountsMap = {};
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
}
state.allCategorizedAccounts = getCategorizedAccounts(accounts);
}
function addAccountToAccountList(state, account) {
let insertIndexToAllList = 0;
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].category > account.category) {
insertIndexToAllList = i;
break;
}
}
state.allAccounts.splice(insertIndexToAllList, 0, account);
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
accountList.push(account);
} else {
state.allCategorizedAccounts = getCategorizedAccounts(state.allAccounts);
}
}
function updateAccountToAccountList(state, account) {
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === account.id) {
state.allAccounts.splice(i, 1, account);
break;
}
}
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (accountList[i].id === account.id) {
accountList.splice(i, 1, account);
break;
}
}
}
}
function updateAccountDisplayOrderInAccountList(state, { account, from, to }) {
let fromAccount = null;
let toAccount = null;
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
fromAccount = accountList[from];
toAccount = accountList[to];
accountList.splice(to, 0, accountList.splice(from, 1)[0]);
}
if (fromAccount && toAccount) {
let globalFromIndex = -1;
let globalToIndex = -1;
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === fromAccount.id) {
globalFromIndex = i;
} else if (state.allAccounts[i].id === toAccount.id) {
globalToIndex = i;
}
}
if (globalFromIndex >= 0 && globalToIndex >= 0) {
state.allAccounts.splice(globalToIndex, 0, state.allAccounts.splice(globalFromIndex, 1)[0]);
}
}
}
function updateAccountVisibilityInAccountList(state, { account, hidden }) {
if (state.allAccountsMap[account.id]) {
state.allAccountsMap[account.id].hidden = hidden;
}
}
function removeAccountFromAccountList(state, account) {
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === account.id) {
state.allAccounts.splice(i, 1);
break;
}
}
if (state.allAccountsMap[account.id] && state.allAccountsMap[account.id].subAccounts) {
const subAccounts = state.allAccountsMap[account.id].subAccounts;
for (let i = 0; i < subAccounts.length; i++) {
const subAccount = subAccounts[i];
if (state.allAccountsMap[subAccount.id]) {
delete state.allAccountsMap[subAccount.id];
}
}
}
if (state.allAccountsMap[account.id]) {
delete state.allAccountsMap[account.id];
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (accountList[i].id === account.id) {
accountList.splice(i, 1);
break;
}
}
}
}
export const useAccountsStore = defineStore('accounts', {
state: () => ({
allAccounts: [],
allAccountsMap: {},
allCategorizedAccounts: {},
accountListStateInvalid: true,
}),
getters: {
allPlainAccounts(state) {
const allAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allAccounts.push(subAccount);
}
}
}
return allAccounts;
},
allVisiblePlainAccounts(state) {
const allVisibleAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.hidden) {
continue;
}
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allVisibleAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allVisibleAccounts.push(subAccount);
}
}
}
return allVisibleAccounts;
},
allAvailableAccountsCount(state) {
let allAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
allAccountCount += state.allCategorizedAccounts[category].accounts.length;
}
return allAccountCount;
},
allVisibleAccountsCount(state) {
let shownAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
const accountList = state.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (!accountList[i].hidden) {
shownAccountCount++;
}
}
}
return shownAccountCount;
}
},
actions: {
updateAccountListInvalidState(invalidState) {
this.accountListStateInvalid = invalidState;
},
resetAccounts() {
this.allAccounts = [];
this.allAccountsMap = {};
this.allCategorizedAccounts = {};
this.accountListStateInvalid = true;
},
getFirstShowingIds(showHidden) {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccounts, category)) {
continue;
}
if (!this.allCategorizedAccounts[category] || !this.allCategorizedAccounts[category].accounts) {
continue;
}
const accounts = this.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
if (account.type === accountConstants.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
if (showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
},
getLastShowingIds(showHidden) {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccounts, category)) {
continue;
}
if (!this.allCategorizedAccounts[category] || !this.allCategorizedAccounts[category].accounts) {
continue;
}
const accounts = this.allCategorizedAccounts[category].accounts;
for (let i = accounts.length - 1; i >= 0; i--) {
const account = accounts[i];
if (account.type === accountConstants.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = account.subAccounts.length - 1; j >= 0; j--) {
const subAccount = account.subAccounts[j];
if (showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
},
getNetAssets(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, () => true);
let netAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
netAssets += accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
netAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return netAssets + '+';
} else {
return netAssets;
}
},
getTotalAssets(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.isAsset);
let totalAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
totalAssets += accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalAssets + '+';
} else {
return totalAssets;
}
},
getTotalLiabilities(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.isLiability);
let totalLiabilities = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
totalLiabilities -= accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalLiabilities -= Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalLiabilities + '+';
} else {
return totalLiabilities;
}
},
getAccountCategoryTotalBalance(showAccountBalance, accountCategory) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.category === accountCategory.id);
let totalBalance = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
if (accountsBalance[i].isAsset) {
totalBalance += accountsBalance[i].balance;
} else if (accountsBalance[i].isLiability) {
totalBalance -= accountsBalance[i].balance;
} else {
totalBalance += accountsBalance[i].balance;
}
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
if (accountsBalance[i].isAsset) {
totalBalance += Math.floor(balance);
} else if (accountsBalance[i].isLiability) {
totalBalance -= Math.floor(balance);
} else {
totalBalance += Math.floor(balance);
}
}
}
if (hasUnCalculatedAmount) {
return totalBalance + '+';
} else {
return totalBalance;
}
},
getAccountBalance(showAccountBalance, account) {
if (account.type !== accountConstants.allAccountTypes.SingleAccount) {
return null;
}
if (showAccountBalance) {
if (account.isAsset) {
return account.balance;
} else if (account.isLiability) {
return -account.balance;
} else {
return account.balance;
}
} else {
return '***';
}
},
hasAccount(accountCategory, visibleOnly) {
if (!this.allCategorizedAccounts[accountCategory.id] ||
!this.allCategorizedAccounts[accountCategory.id].accounts ||
!this.allCategorizedAccounts[accountCategory.id].accounts.length) {
return false;
}
let shownCount = 0;
for (let i = 0; i < this.allCategorizedAccounts[accountCategory.id].accounts.length; i++) {
const account = this.allCategorizedAccounts[accountCategory.id].accounts[i];
if (!visibleOnly || !account.hidden) {
shownCount++;
}
}
return shownCount > 0;
},
hasVisibleSubAccount(showHidden, account) {
if (!account || account.type !== accountConstants.allAccountTypes.MultiSubAccounts || !account.subAccounts) {
return false;
}
for (let i = 0; i < account.subAccounts.length; i++) {
if (showHidden || !account.subAccounts[i].hidden) {
return true;
}
}
return false;
},
loadAllAccounts({ force }) {
const self = this;
if (!force && !self.accountListStateInvalid) {
return new Promise((resolve) => {
resolve(self.allAccounts);
});
}
return new Promise((resolve, reject) => {
services.getAllAccounts({
visibleOnly: false
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account list' });
return;
}
loadAccountList(self, data.result);
if (self.accountListStateInvalid) {
self.updateAccountListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load account list', error);
} else {
logger.error('failed to load account list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get account list' });
} else {
reject(error);
}
});
});
},
getAccount({ accountId }) {
return new Promise((resolve, reject) => {
services.getAccount({
id: accountId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load account info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get account' });
} else {
reject(error);
}
});
});
},
saveAccount({ account }) {
const self = this;
const oldAccount = account.id ? self.allAccountsMap[account.id] : null;
return new Promise((resolve, reject) => {
let promise = null;
if (!account.id) {
promise = services.addAccount(account);
} else {
promise = services.modifyAccount(account);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
return;
}
if (!account.id) {
addAccountToAccountList(self, data.result);
} else {
if (oldAccount && oldAccount.category === data.result.category) {
updateAccountToAccountList(self, data.result);
} else {
self.updateAccountListInvalidState(true);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save account', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
} else {
reject(error);
}
});
});
},
changeAccountDisplayOrder({ accountId, from, to }) {
const self = this;
const account = self.allAccountsMap[accountId];
return new Promise((resolve, reject) => {
if (!account ||
!self.allCategorizedAccounts[account.category] ||
!self.allCategorizedAccounts[account.category].accounts ||
!self.allCategorizedAccounts[account.category].accounts[to]) {
reject({ message: 'Unable to move account' });
return;
}
if (!self.accountListStateInvalid) {
self.updateAccountListInvalidState(true);
}
updateAccountDisplayOrderInAccountList(self, {
account: account,
from: from,
to: to
});
resolve();
});
},
updateAccountDisplayOrders() {
const self = this;
const newDisplayOrders = [];
for (let category in self.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(self.allCategorizedAccounts, category)) {
continue;
}
const accountList = self.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
newDisplayOrders.push({
id: accountList[i].id,
displayOrder: i + 1
});
}
}
return new Promise((resolve, reject) => {
services.moveAccount({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move account' });
return;
}
if (self.accountListStateInvalid) {
self.updateAccountListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save accounts display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move account' });
} else {
reject(error);
}
});
});
},
hideAccount({ account, hidden }) {
const self = this;
return new Promise((resolve, reject) => {
services.hideAccount({
id: account.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
return;
}
updateAccountVisibilityInAccountList(self, {
account: account,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change account visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
} else {
reject(error);
}
});
});
},
deleteAccount({ account, beforeResolve }) {
const self = this;
return new Promise((resolve, reject) => {
services.deleteAccount({
id: account.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this account' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeAccountFromAccountList(self, account);
});
} else {
removeAccountFromAccountList(self, account);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete account', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this account' });
} else {
reject(error);
}
});
});
}
}
});
+132
View File
@@ -0,0 +1,132 @@
import { defineStore } from 'pinia';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isEquals } from '@/lib/common.js';
import { getCurrentUnixTime, formatUnixTime } from '@/lib/datetime.js';
import { getExchangedAmount } from '@/lib/currency.js';
const exchangeRatesLocalStorageKey = 'ebk_app_exchange_rates';
function getExchangeRatesFromLocalStorage() {
const storageData = localStorage.getItem(exchangeRatesLocalStorageKey) || '{}';
return JSON.parse(storageData);
}
function setExchangeRatesToLocalStorage(value) {
const storageData = JSON.stringify(value);
localStorage.setItem(exchangeRatesLocalStorageKey, storageData);
}
function clearExchangeRatesFromLocalStorage() {
localStorage.removeItem(exchangeRatesLocalStorageKey);
}
export const useExchangeRatesStore = defineStore('exchangeRates', {
state: () => ({
latestExchangeRates: getExchangeRatesFromLocalStorage()
}),
getters: {
exchangeRatesLastUpdateTime(state) {
const exchangeRates = state.latestExchangeRates || {};
return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null;
},
latestExchangeRateMap(state) {
const exchangeRateMap = {};
if (!state.latestExchangeRates || !state.latestExchangeRates.data || !state.latestExchangeRates.data.exchangeRates) {
return exchangeRateMap;
}
for (let i = 0; i < state.latestExchangeRates.data.exchangeRates.length; i++) {
const exchangeRate = state.latestExchangeRates.data.exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
return exchangeRateMap;
}
},
actions: {
resetLatestExchangeRates() {
this.latestExchangeRates = {};
clearExchangeRatesFromLocalStorage();
},
getLatestExchangeRates({ silent, force }) {
const self = this;
const currentExchangeRateData = self.latestExchangeRates;
const now = getCurrentUnixTime();
if (!force) {
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === formatUnixTime(now, 'YYYY-MM-DD')) {
return currentExchangeRateData.data;
}
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === formatUnixTime(now, 'YYYY-MM-DD HH')) {
return currentExchangeRateData.data;
}
}
return new Promise((resolve, reject) => {
services.getLatestExchangeRates({
ignoreError: silent
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get exchange rates data' });
return;
}
const currentData = getExchangeRatesFromLocalStorage();
if (currentData && currentData.data && isEquals(currentData.data, data.result)) {
reject({ message: 'Exchange rates data is up to date' });
return;
}
this.latestExchangeRates = {
time: now,
data: data.result
};
setExchangeRatesToLocalStorage(this.latestExchangeRates);
resolve(data.result);
}).catch(error => {
logger.error('failed to get latest exchange rates data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to get exchange rates data' });
}
});
});
},
getExchangedAmount(amount, fromCurrency, toCurrency) {
if (!this.latestExchangeRates || !this.latestExchangeRates.data || !this.latestExchangeRates.data.exchangeRates) {
return null;
}
const exchangeRates = this.latestExchangeRates.data.exchangeRates;
const exchangeRateMap = {};
for (let i = 0; i < exchangeRates.length; i++) {
const exchangeRate = exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
const fromCurrencyExchangeRate = exchangeRateMap[fromCurrency];
const toCurrencyExchangeRate = exchangeRateMap[toCurrency];
if (!fromCurrencyExchangeRate || !toCurrencyExchangeRate) {
return null;
}
return getExchangedAmount(amount, fromCurrencyExchangeRate.rate, toCurrencyExchangeRate.rate);
}
}
});
+350
View File
@@ -0,0 +1,350 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useTransactionTagsStore } from './transactionTag.js';
import { useTransactionsStore } from './transaction.js';
import { useOverviewStore } from './overview.js';
import { useStatisticsStore } from './statistics.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import settings from '@/lib/settings.js';
import logger from '@/lib/logger.js';
import { isObject, isString } from '@/lib/common.js';
export const useRootStore = defineStore('root', {
actions: {
resetAllStates() {
const exchangeRatesStore = useExchangeRatesStore();
exchangeRatesStore.resetLatestExchangeRates();
const statisticsStore = useStatisticsStore();
statisticsStore.resetTransactionStatistics();
const overviewStore = useOverviewStore();
overviewStore.resetTransactionOverview();
const transactionsStore = useTransactionsStore();
transactionsStore.resetTransactions();
const transactionTagsStore = useTransactionTagsStore();
transactionTagsStore.resetTransactionTags();
const transactionCategoriesStore = useTransactionCategoriesStore();
transactionCategoriesStore.resetTransactionCategories();
const accountsStore = useAccountsStore();
accountsStore.resetAccounts();
const userStore = useUserStore();
userStore.resetUserInfo();
},
authorize({ loginName, password }) {
return new Promise((resolve, reject) => {
services.authorize({
loginName: loginName,
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to login' });
return;
}
if (data.result.need2FA) {
resolve(data.result);
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to login', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to login' });
}
});
});
},
authorize2FA({ token, passcode, recoveryCode }) {
return new Promise((resolve, reject) => {
let promise = null;
if (passcode) {
promise = services.authorize2FA({
passcode: passcode,
token: token
});
} else if (recoveryCode) {
promise = services.authorize2FAByBackupCode({
recoveryCode: recoveryCode,
token: token
});
} else {
reject({ message: 'An error has occurred' });
return;
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to verify' });
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to verify 2fa', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to verify' });
}
});
});
},
register({ user }) {
return new Promise((resolve, reject) => {
services.register({
username: user.username,
password: user.password,
email: user.email,
nickname: user.nickname,
language: user.language,
defaultCurrency: user.defaultCurrency,
firstDayOfWeek: user.firstDayOfWeek
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to sign up' });
return;
}
if (settings.isEnableApplicationLock()) {
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
if (data.result.token && isString(data.result.token)) {
userState.updateToken(data.result.token);
}
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to sign up', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to sign up' });
} else {
reject(error);
}
});
});
},
logout() {
const self = this;
return new Promise((resolve, reject) => {
services.logout().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout' });
return;
}
userState.clearTokenAndUserInfo(true);
userState.clearWebAuthnConfig();
self.resetAllStates();
resolve(data.result);
}).catch(error => {
logger.error('failed to log out', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to logout' });
}
});
});
},
forceLogout() {
userState.clearTokenAndUserInfo(true);
userState.clearWebAuthnConfig();
this.resetAllStates();
},
updateUserProfile({ profile, currentPassword }) {
return new Promise((resolve, reject) => {
services.updateProfile({
password: profile.password,
oldPassword: currentPassword,
email: profile.email,
nickname: profile.nickname,
defaultAccountId: profile.defaultAccountId,
transactionEditScope: profile.transactionEditScope,
language: profile.language,
defaultCurrency: profile.defaultCurrency,
firstDayOfWeek: profile.firstDayOfWeek,
longDateFormat: profile.longDateFormat,
shortDateFormat: profile.shortDateFormat,
longTimeFormat: profile.longTimeFormat,
shortTimeFormat: profile.shortTimeFormat
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to update user profile' });
return;
}
if (data.result.newToken && isString(data.result.newToken)) {
userState.updateToken(data.result.newToken);
}
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to update user profile' });
} else {
reject(error);
}
});
});
},
clearUserData({ password }) {
return new Promise((resolve, reject) => {
services.clearData({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to clear user data' });
return;
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const transactionCategoriesStore = useTransactionCategoriesStore();
if (!transactionCategoriesStore.transactionCategoryListStateInvalid) {
transactionCategoriesStore.updateTransactionCategoryListInvalidState(true);
}
const transactionTagsStore = useTransactionTagsStore();
if (!transactionTagsStore.transactionTagListStateInvalid) {
transactionTagsStore.updateTransactionTagListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to clear user data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to clear user data' });
}
});
});
}
}
});
+119
View File
@@ -0,0 +1,119 @@
import { defineStore } from 'pinia';
import { useExchangeRatesStore } from './exchangeRates.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
export const useOverviewStore = defineStore('overview', {
state: () => ({
transactionOverview: {},
transactionOverviewStateInvalid: true
}),
actions: {
updateTransactionOverviewInvalidState(invalidState) {
this.transactionOverviewStateInvalid = invalidState;
},
resetTransactionOverview() {
this.transactionOverview = {};
this.transactionOverviewStateInvalid = true;
},
loadTransactionOverview({ defaultCurrency, dateRange, force }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
if (!force && !self.transactionOverviewStateInvalid) {
return new Promise((resolve) => {
resolve(self.transactionOverview);
});
}
return new Promise((resolve, reject) => {
services.getTransactionAmounts({
today: dateRange.today,
thisWeek: dateRange.thisWeek,
thisMonth: dateRange.thisMonth,
thisYear: dateRange.thisYear
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction overview' });
return;
}
const overview = data.result;
for (let field in overview) {
if (!Object.prototype.hasOwnProperty.call(overview, field)) {
continue;
}
const item = overview[field];
if (!item.amounts || !item.amounts.length) {
item.amounts = [];
}
let totalIncomeAmount = 0;
let totalExpenseAmount = 0;
let hasUnCalculatedTotalIncome = false;
let hasUnCalculatedTotalExpense = false;
for (let i = 0; i < item.amounts.length; i++) {
const amount = item.amounts[i];
if (amount.currency !== defaultCurrency) {
const incomeAmount = exchangeRatesStore.getExchangedAmount(amount.incomeAmount, amount.currency, defaultCurrency);
const expenseAmount = exchangeRatesStore.getExchangedAmount(amount.expenseAmount, amount.currency, defaultCurrency);
if (isNumber(incomeAmount)) {
totalIncomeAmount += Math.floor(incomeAmount);
} else {
hasUnCalculatedTotalIncome = true;
}
if (isNumber(expenseAmount)) {
totalExpenseAmount += Math.floor(expenseAmount);
} else {
hasUnCalculatedTotalExpense = true;
}
} else {
totalIncomeAmount += amount.incomeAmount;
totalExpenseAmount += amount.expenseAmount;
}
}
item.incomeAmount = totalIncomeAmount;
item.expenseAmount = totalExpenseAmount;
item.incompleteIncomeAmount = hasUnCalculatedTotalIncome;
item.incompleteExpenseAmount = hasUnCalculatedTotalExpense;
}
self.transactionOverview = overview;
if (self.transactionOverviewStateInvalid) {
self.updateTransactionOverviewInvalidState(false);
}
resolve(overview);
}).catch(error => {
if (force) {
logger.error('failed to force load transaction overview', error);
} else {
logger.error('failed to load transaction overview', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction overview' });
} else {
reject(error);
}
});
});
}
}
});
+24
View File
@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import currencyConstants from '@/consts/currency.js';
import datetimeConstants from '@/consts/datetime.js';
export const useSettingsStore = defineStore('settings', {
state: () => ({
defaultSetting: {
language: '',
currency: currencyConstants.defaultCurrency,
firstDayOfWeek: datetimeConstants.defaultFirstDayOfWeek,
longDateFormat: 0,
shortDateFormat: 0,
longTimeFormat: 0,
shortTimeFormat: 0
}
}),
actions: {
updateLocalizedDefaultSettings({ defaultCurrency, defaultFirstDayOfWeek }) {
this.defaultSetting.currency = defaultCurrency;
this.defaultSetting.firstDayOfWeek = defaultFirstDayOfWeek;
}
}
});
+423
View File
@@ -0,0 +1,423 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import statisticsConstants from '@/consts/statistics.js';
import categoryConstants from '@/consts/category.js';
import iconConstants from '@/consts/icon.js';
import colorConstants from '@/consts/color.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber, isObject } from '@/lib/common.js';
function loadTransactionStatistics(state, { statistics, defaultCurrency }) {
if (statistics && statistics.items && statistics.items.length) {
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const exchangeRatesStore = useExchangeRatesStore();
for (let i = 0; i < statistics.items.length; i++) {
const item = statistics.items[i];
if (item.accountId) {
item.account = accountsStore.allAccountsMap[item.accountId];
}
if (item.account && item.account.parentId !== '0') {
item.primaryAccount = accountsStore.allAccountsMap[item.account.parentId];
} else {
item.primaryAccount = item.account;
}
if (item.categoryId) {
item.category = transactionCategoriesStore.allTransactionCategoriesMap[item.categoryId];
}
if (item.category && item.category.parentId !== '0') {
item.primaryCategory = transactionCategoriesStore.allTransactionCategoriesMap[item.category.parentId];
} else {
item.primaryCategory = item.category;
}
if (item.account && item.account.currency !== defaultCurrency) {
const amount = exchangeRatesStore.getExchangedAmount(item.amount, item.account.currency, defaultCurrency);
if (isNumber(amount)) {
item.amountInDefaultCurrency = Math.floor(amount);
}
} else if (item.account && item.account.currency === defaultCurrency) {
item.amountInDefaultCurrency = item.amount;
} else {
item.amountInDefaultCurrency = null;
}
}
}
state.transactionStatistics = statistics;
}
export const useStatisticsStore = defineStore('statistics', {
state: () => ({
transactionStatisticsFilter: {
dateType: statisticsConstants.defaultDataRangeType,
startTime: 0,
endTime: 0,
chartType: statisticsConstants.defaultChartType,
chartDataType: statisticsConstants.defaultChartDataType,
filterAccountIds: {},
filterCategoryIds: {}
},
transactionStatistics: [],
transactionStatisticsStateInvalid: true
}),
getters: {
statisticsItemsByTransactionStatisticsData(state) {
if (!state.transactionStatistics || !state.transactionStatistics.items) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < state.transactionStatistics.items.length; i++) {
const item = state.transactionStatistics.items[i];
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Expense) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Income) {
continue;
}
} else {
continue;
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[item.account.id]) {
continue;
}
if (state.transactionStatisticsFilter.filterCategoryIds && state.transactionStatisticsFilter.filterCategoryIds[item.category.id]) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByAccount.type) {
if (isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.account.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.account.name,
type: 'account',
id: item.account.id,
icon: item.account.icon || iconConstants.defaultAccountIcon.icon,
color: item.account.color || colorConstants.defaultAccountColor,
hidden: item.primaryAccount.hidden || item.account.hidden,
displayOrders: [item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.account.id] = data;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByPrimaryCategory.type) {
if (isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.primaryCategory.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.primaryCategory.name,
type: 'category',
id: item.primaryCategory.id,
icon: item.primaryCategory.icon || iconConstants.defaultCategoryIcon.icon,
color: item.primaryCategory.color || colorConstants.defaultCategoryColor,
hidden: item.primaryCategory.hidden,
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.primaryCategory.id] = data;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeBySecondaryCategory.type) {
if (isNumber(item.amountInDefaultCurrency)) {
let data = allDataItems[item.category.id];
if (data) {
data.totalAmount += item.amountInDefaultCurrency;
} else {
data = {
name: item.category.name,
type: 'category',
id: item.category.id,
icon: item.category.icon || iconConstants.defaultCategoryIcon.icon,
color: item.category.color || colorConstants.defaultCategoryColor,
hidden: item.primaryCategory.hidden || item.category.hidden,
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder, item.category.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.category.id] = data;
}
}
}
return {
totalAmount: totalAmount,
totalNonNegativeAmount: totalNonNegativeAmount,
items: allDataItems
}
},
statisticsItemsByAccountsData(state) {
const userStore = useUserStore();
const accountsStore = useAccountsStore();
const exchangeRatesStore = useExchangeRatesStore();
if (!accountsStore.allPlainAccounts) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < accountsStore.allPlainAccounts.length; i++) {
const account = accountsStore.allPlainAccounts[i];
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalAssets.type) {
if (!account.isAsset) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalLiabilities.type) {
if (!account.isLiability) {
continue;
}
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[account.id]) {
continue;
}
let primaryAccount = accountsStore.allAccountsMap[account.parentId];
if (!primaryAccount) {
primaryAccount = account;
}
let amount = account.balance;
if (account.currency !== userStore.currentUserDefaultCurrency) {
amount = Math.floor(exchangeRatesStore.getExchangedAmount(amount, account.currency, userStore.currentUserDefaultCurrency));
if (!isNumber(amount)) {
continue;
}
}
if (account.isLiability) {
amount = -amount;
}
const data = {
name: account.name,
type: 'account',
id: account.id,
icon: account.icon || iconConstants.defaultAccountIcon.icon,
color: account.color || colorConstants.defaultAccountColor,
hidden: primaryAccount.hidden || account.hidden,
displayOrders: [primaryAccount.category, primaryAccount.displayOrder, account.displayOrder],
totalAmount: amount
};
totalAmount += amount;
if (amount > 0) {
totalNonNegativeAmount += amount;
}
allDataItems[account.id] = data;
}
return {
totalAmount: totalAmount,
totalNonNegativeAmount: totalNonNegativeAmount,
items: allDataItems
}
}
},
actions: {
updateTransactionStatisticsInvalidState(invalidState) {
this.transactionStatisticsStateInvalid = invalidState;
},
resetTransactionStatistics() {
this.transactionStatisticsFilter.dateType = statisticsConstants.defaultDataRangeType;
this.transactionStatisticsFilter.startTime = 0;
this.transactionStatisticsFilter.endTime = 0;
this.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
this.transactionStatisticsFilter.chartDataType = statisticsConstants.defaultChartDataType;
this.transactionStatisticsFilter.filterAccountIds = {};
this.transactionStatisticsFilter.filterCategoryIds = {};
this.transactionStatistics = {};
this.transactionStatisticsStateInvalid = true;
},
initTransactionStatisticsFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionStatisticsFilter.dateType = filter.dateType;
} else {
this.transactionStatisticsFilter.dateType = statisticsConstants.defaultDataRangeType;
}
if (filter && isNumber(filter.startTime)) {
this.transactionStatisticsFilter.startTime = filter.startTime;
} else {
this.transactionStatisticsFilter.startTime = 0;
}
if (filter && isNumber(filter.endTime)) {
this.transactionStatisticsFilter.endTime = filter.endTime;
} else {
this.transactionStatisticsFilter.endTime = 0;
}
if (filter && isNumber(filter.chartType)) {
this.transactionStatisticsFilter.chartType = filter.chartType;
} else {
this.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
}
if (filter && isNumber(filter.chartDataType)) {
this.transactionStatisticsFilter.chartDataType = filter.chartDataType;
} else {
this.transactionStatisticsFilter.chartDataType = statisticsConstants.defaultChartDataType;
}
if (filter && isObject(filter.filterAccountIds)) {
this.transactionStatisticsFilter.filterAccountIds = filter.filterAccountIds;
} else {
this.transactionStatisticsFilter.filterAccountIds = {};
}
if (filter && isObject(filter.filterCategoryIds)) {
this.transactionStatisticsFilter.filterCategoryIds = filter.filterCategoryIds;
} else {
this.transactionStatisticsFilter.filterCategoryIds = {};
}
if (filter && isNumber(filter.sortingType)) {
this.transactionStatisticsFilter.sortingType = filter.sortingType;
} else {
this.transactionStatisticsFilter.sortingType = statisticsConstants.defaultSortingType;
}
},
updateTransactionStatisticsFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionStatisticsFilter.dateType = filter.dateType;
}
if (filter && isNumber(filter.startTime)) {
this.transactionStatisticsFilter.startTime = filter.startTime;
}
if (filter && isNumber(filter.endTime)) {
this.transactionStatisticsFilter.endTime = filter.endTime;
}
if (filter && isNumber(filter.chartType)) {
this.transactionStatisticsFilter.chartType = filter.chartType;
}
if (filter && isNumber(filter.chartDataType)) {
this.transactionStatisticsFilter.chartDataType = filter.chartDataType;
}
if (filter && isObject(filter.filterAccountIds)) {
this.transactionStatisticsFilter.filterAccountIds = filter.filterAccountIds;
}
if (filter && isObject(filter.filterCategoryIds)) {
this.transactionStatisticsFilter.filterCategoryIds = filter.filterCategoryIds;
}
if (filter && isNumber(filter.sortingType)) {
this.transactionStatisticsFilter.sortingType = filter.sortingType;
}
},
loadTransactionStatistics({ defaultCurrency }) {
const self = this;
return new Promise((resolve, reject) => {
services.getTransactionStatistics({
startTime: self.transactionStatisticsFilter.startTime,
endTime: self.transactionStatisticsFilter.endTime
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction statistics' });
return;
}
loadTransactionStatistics(self, {
statistics: data.result,
defaultCurrency: defaultCurrency
});
if (self.transactionStatisticsStateInvalid) {
self.updateTransactionStatisticsInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get transaction statistics', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction statistics' });
} else {
reject(error);
}
});
});
},
}
});
+115
View File
@@ -0,0 +1,115 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isObject } from '@/lib/common.js';
export const useTokensStore = defineStore('tokens', {
actions: {
getAllTokens() {
return new Promise((resolve, reject) => {
services.getTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get session list' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load token list', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get session list' });
} else {
reject(error);
}
});
});
},
refreshTokenAndRevokeOldToken() {
const self = this;
return new Promise((resolve) => {
services.refreshToken().then(response => {
const data = response.data;
if (data && data.success && data.result && data.result.newToken) {
userState.updateToken(data.result.newToken);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
if (data.result.oldTokenId) {
self.revokeToken({
tokenId: data.result.oldTokenId,
ignoreError: true
});
}
}
resolve(data.result);
});
});
},
revokeToken({ tokenId, ignoreError }) {
return new Promise((resolve, reject) => {
services.revokeToken({
tokenId: tokenId,
ignoreError: !!ignoreError
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout from this session' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke token', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout from this session' });
} else {
reject(error);
}
});
});
},
revokeAllTokens() {
return new Promise((resolve, reject) => {
services.revokeAllTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout all other sessions' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke all tokens', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout all other sessions' });
} else {
reject(error);
}
});
});
}
}
});
+618
View File
@@ -0,0 +1,618 @@
import { defineStore } from 'pinia';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useOverviewStore } from './overview.js';
import { useStatisticsStore } from './statistics.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import datetimeConstants from '@/consts/datetime.js';
import transactionConstants from '@/consts/transaction.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber, isString } from '@/lib/common.js';
import {
getTimezoneOffsetMinutes,
parseDateFromUnixTime,
getYear,
getMonth,
getYearAndMonth,
getDay,
getDayOfWeekName
} from '@/lib/datetime.js';
const emptyTransactionResult = {
items: [],
transactionsNextTimeId: 0
};
function loadTransactionList(state, exchangeRatesStore, { transactions, reload, autoExpand, defaultCurrency }) {
if (reload) {
state.transactions = [];
}
if (transactions.items && transactions.items.length) {
const currentUtcOffset = getTimezoneOffsetMinutes();
let currentMonthListIndex = -1;
let currentMonthList = null;
for (let i = 0; i < transactions.items.length; i++) {
const item = transactions.items[i];
fillTransactionObject(state, item, currentUtcOffset);
const transactionTime = parseDateFromUnixTime(item.time, item.utcOffset, currentUtcOffset);
const transactionYear = getYear(transactionTime);
const transactionMonth = getMonth(transactionTime);
const transactionYearMonth = getYearAndMonth(transactionTime);
if (currentMonthList && currentMonthList.year === transactionYear && currentMonthList.month === transactionMonth) {
currentMonthList.items.push(Object.freeze(item));
continue;
}
for (let j = currentMonthListIndex + 1; j < state.transactions.length; j++) {
if (state.transactions[j].year === transactionYear && state.transactions[j].month === transactionMonth) {
currentMonthListIndex = j;
currentMonthList = state.transactions[j];
break;
}
}
if (!currentMonthList || currentMonthList.year !== transactionYear || currentMonthList.month !== transactionMonth) {
calculateMonthTotalAmount(state, exchangeRatesStore, currentMonthList, defaultCurrency, state.transactionsFilter.accountId, false);
state.transactions.push({
year: transactionYear,
month: transactionMonth,
yearMonth: transactionYearMonth,
opened: autoExpand,
items: []
});
currentMonthListIndex = state.transactions.length - 1;
currentMonthList = state.transactions[state.transactions.length - 1];
}
currentMonthList.items.push(Object.freeze(item));
calculateMonthTotalAmount(state, exchangeRatesStore, currentMonthList, defaultCurrency, state.transactionsFilter.accountId, true);
}
}
if (transactions.nextTimeSequenceId) {
state.transactionsNextTimeId = transactions.nextTimeSequenceId;
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, state.transactions[state.transactions.length - 1], defaultCurrency, state.transactionsFilter.accountId, false);
state.transactionsNextTimeId = -1;
}
}
function updateTransactionInTransactionList(state, exchangeRatesStore, { transaction, defaultCurrency }) {
const currentUtcOffset = getTimezoneOffsetMinutes();
const transactionTime = parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
const transactionYear = getYear(transactionTime);
const transactionMonth = getMonth(transactionTime);
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
if (!transactionMonthList.items) {
continue;
}
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j].id === transaction.id) {
fillTransactionObject(state, transaction, currentUtcOffset);
if (transactionYear !== transactionMonthList.year ||
transactionMonth !== transactionMonthList.month ||
transaction.day !== transactionMonthList.items[j].day) {
state.transactionListStateInvalid = true;
return;
}
if ((state.transactionsFilter.categoryId && state.transactionsFilter.categoryId !== '0' && state.transactionsFilter.categoryId !== transaction.categoryId) ||
(state.transactionsFilter.accountId && state.transactionsFilter.accountId !== '0' &&
state.transactionsFilter.accountId !== transaction.sourceAccountId &&
state.transactionsFilter.accountId !== transaction.destinationAccountId &&
(!transaction.sourceAccount || state.transactionsFilter.accountId !== transaction.sourceAccount.parentId) &&
(!transaction.destinationAccount || state.transactionsFilter.accountId !== transaction.destinationAccount.parentId)
)
) {
transactionMonthList.items.splice(j, 1);
} else {
transactionMonthList.items.splice(j, 1, transaction);
}
if (transactionMonthList.items.length < 1) {
state.transactions.splice(i, 1);
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, state.transactionsFilter.accountId, i >= state.transactions.length - 1 && state.transactionsNextTimeId > 0);
}
return;
}
}
}
}
function removeTransactionFromTransactionList(state, exchangeRatesStore, { transaction, defaultCurrency }) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
if (!transactionMonthList.items ||
transactionMonthList.items[0].time < transaction.time ||
transactionMonthList.items[transactionMonthList.items.length - 1].time > transaction.time) {
continue;
}
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j].id === transaction.id) {
transactionMonthList.items.splice(j, 1);
}
}
if (transactionMonthList.items.length < 1) {
state.transactions.splice(i, 1);
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, state.transactionsFilter.accountId, i >= state.transactions.length - 1 && state.transactionsNextTimeId > 0);
}
}
}
function calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, accountId, incomplete) {
if (!transactionMonthList) {
return;
}
let totalExpense = 0;
let totalIncome = 0;
let hasUnCalculatedTotalExpense = false;
let hasUnCalculatedTotalIncome = false;
for (let i = 0; i < transactionMonthList.items.length; i++) {
const transaction = transactionMonthList.items[i];
let amount = transaction.sourceAmount;
let account = transaction.sourceAccount;
if (accountId && transaction.destinationAccount && (transaction.destinationAccount.id === accountId || transaction.destinationAccount.parentId === accountId)) {
amount = transaction.destinationAmount;
account = transaction.destinationAccount;
}
if (!account) {
continue;
}
if (account.currency !== defaultCurrency) {
const balance = exchangeRatesStore.getExchangedAmount(amount, account.currency, defaultCurrency);
if (!isNumber(balance)) {
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
hasUnCalculatedTotalExpense = true;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
hasUnCalculatedTotalIncome = true;
}
continue;
}
amount = Math.floor(balance);
}
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
totalExpense += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
totalIncome += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Transfer && accountId) {
if (accountId === transaction.sourceAccountId) {
totalExpense += amount;
} else if (accountId === transaction.destinationAccountId) {
totalIncome += amount;
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId &&
transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
// Do Nothing
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId) {
totalExpense += amount;
} else if (transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
totalIncome += amount;
}
}
}
transactionMonthList.totalAmount = {
expense: totalExpense,
incompleteExpense: incomplete || hasUnCalculatedTotalExpense,
income: totalIncome,
incompleteIncome: incomplete || hasUnCalculatedTotalIncome
};
}
function fillTransactionObject(state, transaction, currentUtcOffset) {
if (!transaction) {
return;
}
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTime = parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
transaction.day = getDay(transactionTime);
transaction.dayOfWeek = getDayOfWeekName(transactionTime);
if (transaction.sourceAccountId) {
transaction.sourceAccount = accountsStore.allAccountsMap[transaction.sourceAccountId];
}
if (transaction.destinationAccountId) {
transaction.destinationAccount = accountsStore.allAccountsMap[transaction.destinationAccountId];
}
if (transaction.categoryId) {
transaction.category = transactionCategoriesStore.allTransactionCategoriesMap[transaction.categoryId];
}
return transaction;
}
export const useTransactionsStore = defineStore('transactions', {
state: () => ({
transactionsFilter: {
dateType: datetimeConstants.allDateRanges.All.type,
maxTime: 0,
minTime: 0,
type: 0,
categoryId: '0',
accountId: '0',
keyword: ''
},
transactions: [],
transactionsNextTimeId: 0,
transactionListStateInvalid: true,
}),
getters: {
noTransaction(state) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j]) {
return false;
}
}
}
return true;
},
hasMoreTransaction(state) {
return state.transactionsNextTimeId > 0;
}
},
actions: {
updateTransactionListInvalidState(invalidState) {
this.transactionListStateInvalid = invalidState;
},
resetTransactions() {
this.transactionsFilter.dateType = datetimeConstants.allDateRanges.All.type;
this.transactionsFilter.maxTime = 0;
this.transactionsFilter.minTime = 0;
this.transactionsFilter.type = 0;
this.transactionsFilter.categoryId = '0';
this.transactionsFilter.accountId = '0';
this.transactionsFilter.keyword = '';
this.transactions = [];
this.transactionsNextTimeId = 0;
this.transactionListStateInvalid = true;
},
initTransactionListFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionsFilter.dateType = filter.dateType;
} else {
this.transactionsFilter.dateType = datetimeConstants.allDateRanges.All.type;
}
if (filter && isNumber(filter.maxTime)) {
this.transactionsFilter.maxTime = filter.maxTime;
} else {
this.transactionsFilter.maxTime = 0;
}
if (filter && isNumber(filter.minTime)) {
this.transactionsFilter.minTime = filter.minTime;
} else {
this.transactionsFilter.minTime = 0;
}
if (filter && isNumber(filter.type)) {
this.transactionsFilter.type = filter.type;
} else {
this.transactionsFilter.type = 0;
}
if (filter && isString(filter.categoryId)) {
this.transactionsFilter.categoryId = filter.categoryId;
} else {
this.transactionsFilter.categoryId = '0';
}
if (filter && isString(filter.accountId)) {
this.transactionsFilter.accountId = filter.accountId;
} else {
this.transactionsFilter.accountId = '0';
}
if (filter && isString(filter.keyword)) {
this.transactionsFilter.keyword = filter.keyword;
} else {
this.transactionsFilter.keyword = '';
}
},
updateTransactionListFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionsFilter.dateType = filter.dateType;
}
if (filter && isNumber(filter.maxTime)) {
this.transactionsFilter.maxTime = filter.maxTime;
}
if (filter && isNumber(filter.minTime)) {
this.transactionsFilter.minTime = filter.minTime;
}
if (filter && isNumber(filter.type)) {
this.transactionsFilter.type = filter.type;
}
if (filter && isString(filter.categoryId)) {
this.transactionsFilter.categoryId = filter.categoryId;
}
if (filter && isString(filter.accountId)) {
this.transactionsFilter.accountId = filter.accountId;
}
if (filter && isString(filter.keyword)) {
this.transactionsFilter.keyword = filter.keyword;
}
},
loadTransactions({ reload, autoExpand, defaultCurrency }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
let actualMaxTime = self.transactionsNextTimeId;
if (reload && self.transactionsFilter.maxTime > 0) {
actualMaxTime = self.transactionsFilter.maxTime * 1000 + 999;
} else if (reload && self.transactionsFilter.maxTime <= 0) {
actualMaxTime = 0;
}
return new Promise((resolve, reject) => {
services.getTransactions({
maxTime: actualMaxTime,
minTime: self.transactionsFilter.minTime * 1000,
type: self.transactionsFilter.type,
categoryId: self.transactionsFilter.categoryId,
accountId: self.transactionsFilter.accountId,
keyword: self.transactionsFilter.keyword
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (reload) {
loadTransactionList(self, exchangeRatesStore, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
}
reject({ message: 'Unable to get transaction list' });
return;
}
loadTransactionList(self, exchangeRatesStore, {
transactions: data.result,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (reload) {
if (self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(false);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction list', error);
if (reload) {
loadTransactionList(self, exchangeRatesStore, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction list' });
} else {
reject(error);
}
});
});
},
getTransaction({ transactionId }) {
return new Promise((resolve, reject) => {
services.getTransaction({
id: transactionId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction' });
} else {
reject(error);
}
});
});
},
saveTransaction({ transaction, defaultCurrency }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
return new Promise((resolve, reject) => {
let promise = null;
if (!transaction.id) {
promise = services.addTransaction(transaction);
} else {
promise = services.modifyTransaction(transaction);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
return;
}
if (!transaction.id) {
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
} else {
updateTransactionInTransactionList(self, exchangeRatesStore, {
transaction: data.result,
defaultCurrency: defaultCurrency
});
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save transaction', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
} else {
reject(error);
}
});
});
},
deleteTransaction({ transaction, defaultCurrency, beforeResolve }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
return new Promise((resolve, reject) => {
services.deleteTransaction({
id: transaction.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this transaction' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeTransactionFromTransactionList(self, exchangeRatesStore, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
});
} else {
removeTransactionFromTransactionList(self, exchangeRatesStore, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete transaction', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this transaction' });
} else {
reject(error);
}
});
});
},
collapseMonthInTransactionList({ month, collapse }) {
if (month) {
month.opened = !collapse;
}
}
}
});
+483
View File
@@ -0,0 +1,483 @@
import { defineStore } from 'pinia';
import categoryConstants from '@/consts/category.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
function loadTransactionCategoryList(state, allCategories) {
state.allTransactionCategories = allCategories;
state.allTransactionCategoriesMap = {};
for (let categoryType in allCategories) {
if (!Object.prototype.hasOwnProperty.call(allCategories, categoryType)) {
continue;
}
const categories = allCategories[categoryType];
for (let i = 0; i < categories.length; i++) {
const category = categories[i];
state.allTransactionCategoriesMap[category.id] = category;
for (let j = 0; j < category.subCategories.length; j++) {
const subCategory = category.subCategories[j];
state.allTransactionCategoriesMap[subCategory.id] = subCategory;
}
}
}
}
function addCategoryToTransactionCategoryList(state, category) {
let categoryList = null;
if (!category.parentId || category.parentId === '0') {
categoryList = state.allTransactionCategories[category.type];
} else if (state.allTransactionCategoriesMap[category.parentId]) {
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
}
if (categoryList) {
categoryList.push(category);
}
state.allTransactionCategoriesMap[category.id] = category;
}
function updateCategoryInTransactionCategoryList(state, category) {
let categoryList = null;
if (!category.parentId || category.parentId === '0') {
categoryList = state.allTransactionCategories[category.type];
} else if (state.allTransactionCategoriesMap[category.parentId]) {
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
}
if (categoryList) {
for (let i = 0; i < categoryList.length; i++) {
if (categoryList[i].id === category.id) {
categoryList.splice(i, 1, category);
break;
}
}
}
state.allTransactionCategoriesMap[category.id] = category;
}
function updateCategoryDisplayOrderInCategoryList(state, { category, from, to }) {
let categoryList = null;
if (!category.parentId || category.parentId === '0') {
categoryList = state.allTransactionCategories[category.type];
} else if (state.allTransactionCategoriesMap[category.parentId]) {
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
}
if (categoryList) {
categoryList.splice(to, 0, categoryList.splice(from, 1)[0]);
}
}
function updateCategoryVisibilityInTransactionCategoryList(state, { category, hidden }) {
if (state.allTransactionCategoriesMap[category.id]) {
state.allTransactionCategoriesMap[category.id].hidden = hidden;
}
}
function removeCategoryFromTransactionCategoryList(state, category) {
let categoryList = null;
if (!category.parentId || category.parentId === '0') {
categoryList = state.allTransactionCategories[category.type];
} else if (state.allTransactionCategoriesMap[category.parentId]) {
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
}
if (categoryList) {
for (let i = 0; i < categoryList.length; i++) {
if (categoryList[i].id === category.id) {
categoryList.splice(i, 1);
break;
}
}
}
if (state.allTransactionCategoriesMap[category.id] && state.allTransactionCategoriesMap[category.id].subCategories) {
const subCategories = state.allTransactionCategoriesMap[category.id].subCategories;
for (let i = 0; i < subCategories.length; i++) {
const subCategory = subCategories[i];
if (state.allTransactionCategoriesMap[subCategory.id]) {
delete state.allTransactionCategoriesMap[subCategory.id];
}
}
}
if (state.allTransactionCategoriesMap[category.id]) {
delete state.allTransactionCategoriesMap[category.id];
}
}
export const useTransactionCategoriesStore = defineStore('transactionCategories', {
state: () => ({
allTransactionCategories: {},
allTransactionCategoriesMap: {},
transactionCategoryListStateInvalid: true,
}),
actions: {
updateTransactionCategoryListInvalidState(invalidState) {
this.transactionCategoryListStateInvalid = invalidState;
},
resetTransactionCategories() {
this.allTransactionCategories = {};
this.allTransactionCategoriesMap = {};
this.transactionCategoryListStateInvalid = true;
},
loadAllCategories({ force }) {
const self = this;
if (!force && !self.transactionCategoryListStateInvalid) {
return new Promise((resolve) => {
resolve(self.allTransactionCategories);
});
}
return new Promise((resolve, reject) => {
services.getAllTransactionCategories().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get category list' });
return;
}
if (!data.result[categoryConstants.allCategoryTypes.Income]) {
data.result[categoryConstants.allCategoryTypes.Income] = [];
}
if (!data.result[categoryConstants.allCategoryTypes.Expense]) {
data.result[categoryConstants.allCategoryTypes.Expense] = [];
}
if (!data.result[categoryConstants.allCategoryTypes.Transfer]) {
data.result[categoryConstants.allCategoryTypes.Transfer] = [];
}
for (let categoryType in data.result) {
if (!Object.prototype.hasOwnProperty.call(data.result, categoryType)) {
continue;
}
const categories = data.result[categoryType];
for (let i = 0; i < categories.length; i++) {
const category = categories[i];
if (!category.subCategories) {
category.subCategories = [];
}
}
}
loadTransactionCategoryList(self, data.result);
if (self.transactionCategoryListStateInvalid) {
self.updateTransactionCategoryListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load category list', error);
} else {
logger.error('failed to load category list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get category list' });
} else {
reject(error);
}
});
});
},
getCategory({ categoryId }) {
return new Promise((resolve, reject) => {
services.getTransactionCategory({
id: categoryId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get category' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load category info', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get category' });
} else {
reject(error);
}
});
});
},
saveCategory({ category }) {
const self = this;
return new Promise((resolve, reject) => {
let promise = null;
if (!category.id) {
promise = services.addTransactionCategory(category);
} else {
promise = services.modifyTransactionCategory(category);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!category.id) {
reject({ message: 'Unable to add category' });
} else {
reject({ message: 'Unable to save category' });
}
return;
}
if (!data.result.subCategories) {
data.result.subCategories = [];
}
if (!category.id) {
addCategoryToTransactionCategoryList(self, data.result);
} else {
updateCategoryInTransactionCategoryList(self, data.result);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save category', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!category.id) {
reject({ message: 'Unable to add category' });
} else {
reject({ message: 'Unable to save category' });
}
} else {
reject(error);
}
});
});
},
addCategories({ categories }) {
const self = this;
return new Promise((resolve, reject) => {
services.addTransactionCategoryBatch({
categories: categories
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to add preset categories' });
return;
}
if (!self.transactionCategoryListStateInvalid) {
self.updateTransactionCategoryListInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to add preset categories', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to add preset categories' });
} else {
reject(error);
}
});
});
},
changeCategoryDisplayOrder({ categoryId, from, to }) {
const self = this;
const category = self.allTransactionCategoriesMap[categoryId];
return new Promise((resolve, reject) => {
if (!category) {
reject({ message: 'Unable to move category' });
return;
}
if (!category.parentId || category.parentId === '0') {
if (!self.allTransactionCategories[category.type] ||
!self.allTransactionCategories[category.type][to]) {
reject({ message: 'Unable to move category' });
return;
}
} else {
if (!self.allTransactionCategoriesMap[category.parentId].subCategories ||
!self.allTransactionCategoriesMap[category.parentId].subCategories[to]) {
reject({ message: 'Unable to move category' });
return;
}
}
if (!self.transactionCategoryListStateInvalid) {
self.updateTransactionCategoryListInvalidState(true);
}
updateCategoryDisplayOrderInCategoryList(self, {
category: category,
from: from,
to: to
});
resolve();
});
},
updateCategoryDisplayOrders({ type, parentId }) {
const self = this;
const newDisplayOrders = [];
let categoryList = null;
if (!parentId || parentId === '0') {
categoryList = self.allTransactionCategories[type];
} else if (self.allTransactionCategoriesMap[parentId]) {
categoryList = self.allTransactionCategoriesMap[parentId].subCategories;
}
if (categoryList) {
for (let i = 0; i < categoryList.length; i++) {
newDisplayOrders.push({
id: categoryList[i].id,
displayOrder: i + 1
});
}
}
return new Promise((resolve, reject) => {
services.moveTransactionCategory({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move category' });
return;
}
if (self.transactionCategoryListStateInvalid) {
self.updateTransactionCategoryListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save categories display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move category' });
} else {
reject(error);
}
});
});
},
hideCategory({ category, hidden }) {
const self = this;
return new Promise((resolve, reject) => {
services.hideTransactionCategory({
id: category.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this category' });
} else {
reject({ message: 'Unable to unhide this category' });
}
return;
}
updateCategoryVisibilityInTransactionCategoryList(self, {
category: category,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change category visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this category' });
} else {
reject({ message: 'Unable to unhide this category' });
}
} else {
reject(error);
}
});
});
},
deleteCategory({ category, beforeResolve }) {
const self = this;
return new Promise((resolve, reject) => {
services.deleteTransactionCategory({
id: category.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this category' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeCategoryFromTransactionCategoryList(self, category);
});
} else {
removeCategoryFromTransactionCategoryList(self, category);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete category', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this category' });
} else {
reject(error);
}
});
});
}
}
});
+311
View File
@@ -0,0 +1,311 @@
import { defineStore } from 'pinia';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
function loadTransactionTagList(state, tags) {
state.allTransactionTags = tags;
state.allTransactionTagsMap = {};
for (let i = 0; i < tags.length; i++) {
const tag = tags[i];
state.allTransactionTagsMap[tag.id] = tag;
}
}
function addTagToTransactionTagList(state, tag) {
state.allTransactionTags.push(tag);
state.allTransactionTagsMap[tag.id] = tag;
}
function updateTagInTransactionTagList(state, tag) {
for (let i = 0; i < state.allTransactionTags.length; i++) {
if (state.allTransactionTags[i].id === tag.id) {
state.allTransactionTags.splice(i, 1, tag);
break;
}
}
state.allTransactionTagsMap[tag.id] = tag;
}
function updateTagDisplayOrderInTransactionTagList(state, { from, to }) {
state.allTransactionTags.splice(to, 0, state.allTransactionTags.splice(from, 1)[0]);
}
function updateTagVisibilityInTransactionTagList(state, { tag, hidden }) {
if (state.allTransactionTagsMap[tag.id]) {
state.allTransactionTagsMap[tag.id].hidden = hidden;
}
}
function removeTagFromTransactionTagList(state, tag) {
for (let i = 0; i < state.allTransactionTags.length; i++) {
if (state.allTransactionTags[i].id === tag.id) {
state.allTransactionTags.splice(i, 1);
break;
}
}
if (state.allTransactionTagsMap[tag.id]) {
delete state.allTransactionTagsMap[tag.id];
}
}
export const useTransactionTagsStore = defineStore('transactionTags', {
state: () => ({
allTransactionTags: [],
allTransactionTagsMap: {},
transactionTagListStateInvalid: true,
}),
actions: {
updateTransactionTagListInvalidState(invalidState) {
this.transactionTagListStateInvalid = invalidState;
},
resetTransactionTags() {
this.allTransactionTags = [];
this.allTransactionTagsMap = {};
this.transactionTagListStateInvalid = true;
},
loadAllTags({ force }) {
const self = this;
if (!force && !self.transactionTagListStateInvalid) {
return new Promise((resolve) => {
resolve(self.allTransactionTags);
});
}
return new Promise((resolve, reject) => {
services.getAllTransactionTags().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get tag list' });
return;
}
loadTransactionTagList(self, data.result);
if (self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load tag list', error);
} else {
logger.error('failed to load tag list', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get tag list' });
} else {
reject(error);
}
});
});
},
saveTag({ tag }) {
const self = this;
return new Promise((resolve, reject) => {
let promise = null;
if (!tag.id) {
promise = services.addTransactionTag(tag);
} else {
promise = services.modifyTransactionTag(tag);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
return;
}
if (!tag.id) {
addTagToTransactionTagList(self, data.result);
} else {
updateTagInTransactionTagList(self, data.result);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tag', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
} else {
reject(error);
}
});
});
},
changeTagDisplayOrder({ tagId, from, to }) {
const self = this;
return new Promise((resolve, reject) => {
let tag = null;
for (let i = 0; i < self.allTransactionTags.length; i++) {
if (self.allTransactionTags[i].id === tagId) {
tag = self.allTransactionTags[i];
break;
}
}
if (!tag || !self.allTransactionTags[to]) {
reject({ message: 'Unable to move tag' });
return;
}
if (!self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(true);
}
updateTagDisplayOrderInTransactionTagList(self, {
tag: tag,
from: from,
to: to
});
resolve();
});
},
updateTagDisplayOrders() {
const self = this;
const newDisplayOrders = [];
for (let i = 0; i < self.allTransactionTags.length; i++) {
newDisplayOrders.push({
id: self.allTransactionTags[i].id,
displayOrder: i + 1
});
}
return new Promise((resolve, reject) => {
services.moveTransactionTag({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move tag' });
return;
}
if (self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tags display order', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to move tag' });
} else {
reject(error);
}
});
});
},
hideTag({ tag, hidden }) {
const self = this;
return new Promise((resolve, reject) => {
services.hideTransactionTag({
id: tag.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
return;
}
updateTagVisibilityInTransactionTagList(self, {
tag: tag,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change tag visibility', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (hidden) {
reject({ message: 'Unable to hide this tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
} else {
reject(error);
}
});
});
},
deleteTag({ tag, beforeResolve }) {
const self = this;
return new Promise((resolve, reject) => {
services.deleteTransactionTag({
id: tag.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this tag' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeTagFromTransactionTagList(self, tag);
});
} else {
removeTagFromTransactionTagList(self, tag);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete tag', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to delete this tag' });
} else {
reject(error);
}
});
});
}
}
});
+142
View File
@@ -0,0 +1,142 @@
import { defineStore } from 'pinia';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isBoolean } from '@/lib/common.js';
export const useTwoFactorAuthStore = defineStore('twoFactorAuth', {
actions: {
get2FAStatus() {
return new Promise((resolve, reject) => {
services.get2FAStatus().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !isBoolean(data.result.enable)) {
reject({ message: 'Unable to get current two factor authentication status' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get 2fa status', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get current two factor authentication status' });
} else {
reject(error);
}
});
});
},
enable2FA() {
return new Promise((resolve, reject) => {
services.enable2FA().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.qrcode || !data.result.secret) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to request to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
},
confirmEnable2FA({ secret, passcode }) {
return new Promise((resolve, reject) => {
services.confirmEnable2FA({
secret: secret,
passcode: passcode
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
if (data.result.token) {
userState.updateToken(data.result.token);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to confirm to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
},
disable2FA({ password }) {
return new Promise((resolve, reject) => {
services.disable2FA({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to disable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to disable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to disable two factor authentication' });
} else {
reject(error);
}
});
});
},
regenerate2FARecoveryCode({ password }) {
return new Promise((resolve, reject) => {
services.regenerate2FARecoveryCode({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.recoveryCodes || !data.result.recoveryCodes.length) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to regenerate 2fa recovery code', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
} else {
reject(error);
}
});
});
}
}
});
+140
View File
@@ -0,0 +1,140 @@
import { defineStore } from 'pinia';
import { useSettingsStore } from './setting.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
export const useUserStore = defineStore('user', {
state: () => ({
currentUserInfo: userState.getUserInfo()
}),
getters: {
currentUserNickname(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.nickname || userInfo.username || null;
},
currentUserDefaultAccountId(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.defaultAccountId || '';
},
currentUserLanguage(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return userInfo.language || settingsStore.language;
},
currentUserDefaultCurrency(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return userInfo.defaultCurrency || settingsStore.currency;
},
currentUserFirstDayOfWeek(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.firstDayOfWeek) ? userInfo.firstDayOfWeek : settingsStore.firstDayOfWeek;
},
currentUserLongDateFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.longDateFormat) ? userInfo.longDateFormat : settingsStore.longDateFormat;
},
currentUserShortDateFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.shortDateFormat) ? userInfo.shortDateFormat : settingsStore.shortDateFormat;
},
currentUserLongTimeFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.longTimeFormat) ? userInfo.longTimeFormat : settingsStore.longTimeFormat;
},
currentUserShortTimeFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.shortTimeFormat) ? userInfo.shortTimeFormat : settingsStore.shortTimeFormat;
}
},
actions: {
storeUserInfo(userInfo) {
this.currentUserInfo = userInfo;
userState.updateUserInfo(userInfo);
},
resetUserInfo() {
this.currentUserInfo = null;
userState.clearUserInfo();
},
getCurrentUserProfile() {
return new Promise((resolve, reject) => {
services.getProfile().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user profile' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user profile' });
} else {
reject(error);
}
});
});
},
getUserDataStatistics() {
return new Promise((resolve, reject) => {
services.getUserDataStatistics().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user statistics data' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user statistics data' });
} else {
reject(error);
}
});
});
},
getExportedUserData() {
return new Promise((resolve, reject) => {
services.getExportedUserData().then(response => {
if (response && response.headers && response.headers['content-type'] !== 'text/csv') {
reject({ message: 'Unable to get exported user data' });
return;
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
resolve(blob);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.headers['content-type'] === 'text/text' && error.response && error.response.data) {
reject({ message: 'error.' + error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get exported user data' });
} else {
reject(error);
}
});
});
},
}
});
+9 -3
View File
@@ -41,8 +41,14 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import licenses from '@/lib/licenses.js';
export default {
computed: {
...mapStores(useUserStore),
version() {
return 'v' + this.$version;
},
@@ -51,13 +57,13 @@ export default {
return this.$buildTime;
}
return this.$utilities.formatUnixTime(this.$buildTime, this.$locale.getLongDateTimeFormat());
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.$buildTime);
},
licenseLines() {
return this.$licenses.license.replaceAll(/\r/g, '').split('\n');
return licenses.getLicense().replaceAll(/\r/g, '').split('\n');
},
thirdPartyLicenses() {
return this.$licenses.thirdPartyLicenses;
return licenses.getThirdPartyLicenses();
}
}
}
+14 -5
View File
@@ -36,6 +36,12 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import logger from '@/lib/logger.js';
import webauthn from '@/lib/webauthn.js';
export default {
data() {
return {
@@ -48,6 +54,9 @@ export default {
showInputPinCodeSheetForDisable: false
};
},
computed: {
...mapStores(useUserStore)
},
watch: {
isEnableApplicationLockWebAuthn: function (newValue) {
const self = this;
@@ -55,9 +64,9 @@ export default {
if (newValue) {
self.$showLoading();
self.$webauthn.registerCredential(
webauthn.registerCredential(
self.$user.getUserAppLockState(),
self.$store.state.currentUserInfo,
self.userStore.currentUserInfo,
).then(({ id }) => {
self.$hideLoading();
@@ -65,7 +74,7 @@ export default {
self.$settings.setEnableApplicationLockWebAuthn(true);
self.$toast('You have enabled WebAuthn successfully');
}).catch(error => {
self.$logger.error('failed to enable WebAuthn', error);
logger.error('failed to enable WebAuthn', error);
self.$hideLoading();
@@ -91,7 +100,7 @@ export default {
},
created() {
const self = this;
self.$webauthn.isCompletelySupported().then(result => {
webauthn.isCompletelySupported().then(result => {
self.isSupportedWebAuthn = result;
});
},
@@ -112,7 +121,7 @@ export default {
return;
}
const user = this.$store.state.currentUserInfo;
const user = this.userStore.currentUserInfo;
if (!user || !user.username) {
this.$alert('An error has occurred');
+29 -31
View File
@@ -34,8 +34,8 @@
:title="displayBaseAmount"
@click="showBaseAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
<number-pad-sheet :min-value="allowedMinAmount"
:max-value="allowedMaxAmount"
v-model:show="showBaseAmountSheet"
v-model="baseAmount"
></number-pad-sheet>
@@ -90,12 +90,20 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import transactionConstants from '@/consts/transaction.js';
import { isNumber, appendThousandsSeparator } from '@/lib/common.js';
import { stringCurrencyToNumeric, getExchangedAmount } from '@/lib/currency.js';
export default {
data() {
const self = this;
const userStore = useUserStore();
return {
baseCurrency: self.$store.getters.currentUserDefaultCurrency,
baseCurrency: userStore.currentUserDefaultCurrency,
baseAmount: 100,
updating: false,
showMoreActionSheet: false,
@@ -103,29 +111,13 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useExchangeRatesStore),
exchangeRatesData() {
return this.$store.state.latestExchangeRates.data;
return this.exchangeRatesStore.latestExchangeRates.data;
},
exchangeRatesDataUpdateTime() {
if (!this.exchangeRatesData) {
return '';
}
return this.$utilities.formatUnixTime(this.exchangeRatesData.updateTime, this.$locale.getLongDateFormat());
},
exchangeRateMap() {
const exchangeRateMap = {};
if (!this.exchangeRatesData.exchangeRates) {
return exchangeRateMap;
}
for (let i = 0; i < this.exchangeRatesData.exchangeRates.length; i++) {
const exchangeRate = this.exchangeRatesData.exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
return exchangeRateMap;
const exchangeRatesLastUpdateTime = this.exchangeRatesStore.exchangeRatesLastUpdateTime;
return exchangeRatesLastUpdateTime ? this.$locale.formatUnixTimeToLongDate(this.userStore, exchangeRatesLastUpdateTime) : '';
},
availableExchangeRates() {
if (!this.exchangeRatesData || !this.exchangeRatesData.exchangeRates) {
@@ -155,6 +147,12 @@ export default {
},
baseAmountFontSize() {
return this.getFontSizeByAmount(this.baseAmount);
},
allowedMinAmount() {
return transactionConstants.minAmount;
},
allowedMaxAmount() {
return transactionConstants.maxAmount;
}
},
created() {
@@ -189,7 +187,7 @@ export default {
self.$showLoading();
}
self.$store.dispatch('getLatestExchangeRates', {
self.exchangeRatesStore.getLatestExchangeRates({
silent: false,
force: true
}).then(() => {
@@ -215,19 +213,19 @@ export default {
});
},
getConvertedAmount(toExchangeRate) {
const fromExchangeRate = this.exchangeRateMap[this.baseCurrency];
const fromExchangeRate = this.exchangeRatesStore.latestExchangeRateMap[this.baseCurrency];
if (!fromExchangeRate) {
return '';
}
return this.$utilities.getExchangedAmount(this.baseAmount / 100, fromExchangeRate.rate, toExchangeRate.rate);
return getExchangedAmount(this.baseAmount / 100, fromExchangeRate.rate, toExchangeRate.rate);
},
getDisplayConvertedAmount(toExchangeRate) {
const rateStr = this.getConvertedAmount(toExchangeRate).toString();
if (rateStr.indexOf('.') < 0) {
return this.$utilities.appendThousandsSeparator(rateStr);
return appendThousandsSeparator(rateStr);
} else {
let firstNonZeroPos = 0;
@@ -239,16 +237,16 @@ export default {
}
const trimmedRateStr = rateStr.substring(0, Math.max(6, Math.max(firstNonZeroPos, rateStr.indexOf('.') + 2)));
return this.$utilities.appendThousandsSeparator(trimmedRateStr);
return appendThousandsSeparator(trimmedRateStr);
}
},
setAsBaseline(currency, amount) {
if (!this.$utilities.isNumber(amount)) {
if (!isNumber(amount)) {
amount = '';
}
this.baseCurrency = currency;
this.baseAmount = this.$utilities.stringCurrencyToNumeric(amount.toString());
this.baseAmount = stringCurrencyToNumeric(amount.toString());
},
getFontSizeByAmount(amount) {
if (amount >= 100000000 || amount <= -100000000) {
+50 -29
View File
@@ -36,7 +36,7 @@
</f7-card>
<f7-list strong inset dividers class="margin-top overview-transaction-list" :class="{ 'skeleton-text': loading }">
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.Today.type" chevron-center>
<f7-list-item :link="'/transaction/list?dateType=' + allDateRanges.Today.type" chevron-center>
<template #media>
<f7-icon f7="calendar_today"></f7-icon>
</template>
@@ -66,7 +66,7 @@
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisWeek.type" chevron-center>
<f7-list-item :link="'/transaction/list?dateType=' + allDateRanges.ThisWeek.type" chevron-center>
<template #media>
<f7-icon f7="calendar"></f7-icon>
</template>
@@ -99,7 +99,7 @@
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisMonth.type" chevron-center>
<f7-list-item :link="'/transaction/list?dateType=' + allDateRanges.ThisMonth.type" chevron-center>
<template #media>
<f7-icon f7="calendar"></f7-icon>
</template>
@@ -132,7 +132,7 @@
</template>
</f7-list-item>
<f7-list-item :link="'/transaction/list?dateType=' + $constants.datetime.allDateRanges.ThisYear.type" chevron-center>
<f7-list-item :link="'/transaction/list?dateType=' + allDateRanges.ThisYear.type" chevron-center>
<template #media>
<f7-icon f7="square_stack_3d_up"></f7-icon>
</template>
@@ -188,25 +188,46 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useOverviewStore } from '@/stores/overview.js';
import datetimeConstants from '@/consts/datetime.js';
import {
formatUnixTime,
getTodayFirstUnixTime,
getTodayLastUnixTime,
getThisWeekFirstUnixTime,
getThisWeekLastUnixTime,
getThisMonthFirstUnixTime,
getThisMonthLastUnixTime,
getThisYearFirstUnixTime,
getThisYearLastUnixTime
} from '@/lib/datetime.js';
export default {
data() {
const self = this;
return {
loading: true,
todayFirstUnixTime: self.$utilities.getTodayFirstUnixTime(),
todayLastUnixTime: self.$utilities.getTodayLastUnixTime(),
todayFirstUnixTime: getTodayFirstUnixTime(),
todayLastUnixTime: getTodayLastUnixTime(),
showAmountInHomePage: self.$settings.isShowAmountInHomePage(),
isEnableThousandsSeparator: self.$settings.isEnableThousandsSeparator(),
currencyDisplayMode: self.$settings.getCurrencyDisplayMode()
};
},
computed: {
...mapStores(useUserStore, useOverviewStore),
defaultCurrency() {
return this.$store.getters.currentUserDefaultCurrency;
return this.userStore.currentUserDefaultCurrency;
},
firstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
return this.userStore.currentUserFirstDayOfWeek;
},
allDateRanges() {
return datetimeConstants.allDateRanges;
},
dateRange() {
const self = this;
@@ -217,16 +238,16 @@ export default {
endTime: self.todayLastUnixTime
},
thisWeek: {
startTime: self.$utilities.getThisWeekFirstUnixTime(self.firstDayOfWeek),
endTime: self.$utilities.getThisWeekLastUnixTime(self.firstDayOfWeek)
startTime: getThisWeekFirstUnixTime(self.firstDayOfWeek),
endTime: getThisWeekLastUnixTime(self.firstDayOfWeek)
},
thisMonth: {
startTime: self.$utilities.getThisMonthFirstUnixTime(),
endTime: self.$utilities.getThisMonthLastUnixTime()
startTime: getThisMonthFirstUnixTime(),
endTime: getThisMonthLastUnixTime()
},
thisYear: {
startTime: self.$utilities.getThisYearFirstUnixTime(),
endTime: self.$utilities.getThisYearLastUnixTime()
startTime: getThisYearFirstUnixTime(),
endTime: getThisYearLastUnixTime()
}
};
},
@@ -235,19 +256,19 @@ export default {
return {
today: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.today.startTime, self.$locale.getLongDateFormat()),
displayTime: self.$locale.formatUnixTimeToLongDate(self.userStore, self.dateRange.today.startTime),
},
thisWeek: {
startTime: self.$utilities.formatUnixTime(self.dateRange.thisWeek.startTime, self.$locale.getLongMonthDayFormat()),
endTime: self.$utilities.formatUnixTime(self.dateRange.thisWeek.endTime, self.$locale.getLongMonthDayFormat())
startTime: self.$locale.formatUnixTimeToLongMonthDay(self.userStore, self.dateRange.thisWeek.startTime),
endTime: self.$locale.formatUnixTimeToLongMonthDay(self.userStore, self.dateRange.thisWeek.endTime)
},
thisMonth: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.startTime, 'MMMM'),
startTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.startTime, self.$locale.getLongMonthDayFormat()),
endTime: self.$utilities.formatUnixTime(self.dateRange.thisMonth.endTime, self.$locale.getLongMonthDayFormat())
displayTime: formatUnixTime(self.dateRange.thisMonth.startTime, 'MMMM'),
startTime: self.$locale.formatUnixTimeToLongMonthDay(self.userStore, self.dateRange.thisMonth.startTime),
endTime: self.$locale.formatUnixTimeToLongMonthDay(self.userStore, self.dateRange.thisMonth.endTime)
},
thisYear: {
displayTime: self.$utilities.formatUnixTime(self.dateRange.thisYear.startTime, self.$locale.getLongYearFormat())
displayTime: self.$locale.formatUnixTimeToLongYear(self.userStore, self.dateRange.thisYear.startTime)
}
};
},
@@ -256,7 +277,7 @@ export default {
const isEnableThousandsSeparator = this.isEnableThousandsSeparator; // eslint-disable-line
const currencyDisplayMode = this.currencyDisplayMode; // eslint-disable-line
if (!this.$store.state.transactionOverview || !this.$store.state.transactionOverview.thisMonth) {
if (!this.overviewStore.transactionOverview || !this.overviewStore.transactionOverview.thisMonth) {
return {
thisMonth: {
valid: false,
@@ -266,7 +287,7 @@ export default {
};
}
const originalOverview = this.$store.state.transactionOverview;
const originalOverview = this.overviewStore.transactionOverview;
const displayOverview = {};
[ 'today', 'thisWeek', 'thisMonth', 'thisYear' ].forEach(key => {
@@ -292,7 +313,7 @@ export default {
if (self.$user.isUserLogined() && self.$user.isUserUnlocked()) {
self.loading = true;
self.$store.dispatch('loadTransactionOverview', {
self.overviewStore.loadTransactionOverview({
defaultCurrency: self.defaultCurrency,
dateRange: self.dateRange,
force: false
@@ -319,21 +340,21 @@ export default {
let dateChanged = false;
if (this.todayFirstUnixTime !== this.$utilities.getTodayFirstUnixTime()) {
if (this.todayFirstUnixTime !== getTodayFirstUnixTime()) {
dateChanged = true;
this.todayFirstUnixTime = this.$utilities.getTodayFirstUnixTime();
this.todayLastUnixTime = this.$utilities.getTodayLastUnixTime();
this.todayFirstUnixTime = getTodayFirstUnixTime();
this.todayLastUnixTime = getTodayLastUnixTime();
}
if ((dateChanged || this.$store.state.transactionOverviewStateInvalid) && !this.loading) {
if ((dateChanged || this.overviewStore.transactionOverviewStateInvalid) && !this.loading) {
this.reload(null);
}
},
reload(done) {
const self = this;
self.$store.dispatch('loadTransactionOverview', {
self.overviewStore.loadTransactionOverview({
defaultCurrency: self.defaultCurrency,
dateRange: self.dateRange,
force: true
+12 -5
View File
@@ -109,6 +109,12 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import { isModalShowing } from '@/lib/ui.mobile.js';
export default {
props: [
'f7router'
@@ -127,6 +133,7 @@ export default {
};
},
computed: {
...mapStores(useRootStore, useExchangeRatesStore),
version() {
return 'v' + this.$version;
},
@@ -187,7 +194,7 @@ export default {
self.logining = true;
self.$showLoading(() => self.logining);
self.$store.dispatch('authorize', {
self.rootStore.authorize({
loginName: self.username,
password: self.password
}).then(authResponse => {
@@ -205,7 +212,7 @@ export default {
}
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
router.refreshPage();
@@ -219,7 +226,7 @@ export default {
});
},
loginByPressEnter() {
if (this.$ui.isModalShowing()) {
if (isModalShowing()) {
return;
}
@@ -244,7 +251,7 @@ export default {
self.verifying = true;
self.$showLoading(() => self.verifying);
self.$store.dispatch('authorize2FA', {
self.rootStore.authorize2FA({
token: self.tempToken,
passcode: self.twoFAVerifyType === 'passcode' ? self.passcode : null,
recoveryCode: self.twoFAVerifyType === 'backupcode' ? self.backupCode : null
@@ -257,7 +264,7 @@ export default {
}
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
self.show2faSheet = false;
+19 -8
View File
@@ -61,10 +61,10 @@
:title="$t('Currency Display Mode')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Currency Display Mode'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="currencyDisplayMode">
<option :value="$constants.currency.allCurrencyDisplayModes.None">{{ $t('None') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Symbol">{{ $t('Currency Symbol') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Code">{{ $t('Currency Code') }}</option>
<option :value="$constants.currency.allCurrencyDisplayModes.Name">{{ $t('Currency Name') }}</option>
<option :value="allCurrencyDisplayModes.None">{{ $t('None') }}</option>
<option :value="allCurrencyDisplayModes.Symbol">{{ $t('Currency Symbol') }}</option>
<option :value="allCurrencyDisplayModes.Code">{{ $t('Currency Code') }}</option>
<option :value="allCurrencyDisplayModes.Name">{{ $t('Currency Name') }}</option>
</select>
</f7-list-item>
@@ -96,6 +96,13 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useUserStore } from '@/stores/user.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import currencyConstants from '@/consts/currency.js';
export default {
props: [
'f7router'
@@ -110,6 +117,7 @@ export default {
};
},
computed: {
...mapStores(useRootStore, useUserStore, useExchangeRatesStore),
version() {
return 'v' + this.$version;
},
@@ -136,11 +144,14 @@ export default {
}
},
currentNickName() {
return this.$store.getters.currentUserNickname || this.$t('User');
return this.userStore.currentUserNickname || this.$t('User');
},
exchangeRatesLastUpdateDate() {
const exchangeRatesLastUpdateTime = this.$store.getters.exchangeRatesLastUpdateTime;
return exchangeRatesLastUpdateTime ? this.$utilities.formatUnixTime(exchangeRatesLastUpdateTime, this.$locale.getLongDateFormat()) : '';
const exchangeRatesLastUpdateTime = this.exchangeRatesStore.exchangeRatesLastUpdateTime;
return exchangeRatesLastUpdateTime ? this.$locale.formatUnixTimeToLongDate(this.userStore, exchangeRatesLastUpdateTime) : '';
},
allCurrencyDisplayModes() {
return currencyConstants.allCurrencyDisplayModes;
},
isAutoUpdateExchangeRatesData: {
get: function () {
@@ -223,7 +234,7 @@ export default {
self.logouting = true;
self.$showLoading(() => self.logouting);
self.$store.dispatch('logout').then(() => {
self.rootStore.logout().then(() => {
self.logouting = false;
self.$hideLoading();
+29 -17
View File
@@ -174,12 +174,23 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useSettingsStore } from '@/stores/setting.js';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import datetimeConstants from '@/consts/datetime.js';
import categoryConstants from '@/consts/category.js';
import { getNameByKeyValue, copyArrayTo } from '@/lib/common.js';
export default {
props: [
'f7router'
],
data() {
const self = this;
const settingsStore = useSettingsStore();
return {
user: {
@@ -189,14 +200,14 @@ export default {
email: '',
nickname: '',
language: self.$i18n.locale,
defaultCurrency: self.$store.state.defaultSetting.currency,
firstDayOfWeek: self.$constants.datetime.allWeekDays[self.$t('default.firstDayOfWeek')] ? self.$constants.datetime.allWeekDays[self.$t('default.firstDayOfWeek')].type : 0
defaultCurrency: settingsStore.defaultSetting.currency,
firstDayOfWeek: datetimeConstants.allWeekDays[self.$t('default.firstDayOfWeek')] ? datetimeConstants.allWeekDays[self.$t('default.firstDayOfWeek')].type : 0
},
submitting: false,
presetCategories: {
[self.$constants.category.allCategoryTypes.Income]: self.$utilities.copyArrayTo(self.$constants.category.defaultIncomeCategories, []),
[self.$constants.category.allCategoryTypes.Expense]: self.$utilities.copyArrayTo(self.$constants.category.defaultExpenseCategories, []),
[self.$constants.category.allCategoryTypes.Transfer]: self.$utilities.copyArrayTo(self.$constants.category.defaultTransferCategories, [])
[categoryConstants.allCategoryTypes.Income]: copyArrayTo(categoryConstants.defaultIncomeCategories, []),
[categoryConstants.allCategoryTypes.Expense]: copyArrayTo(categoryConstants.defaultExpenseCategories, []),
[categoryConstants.allCategoryTypes.Transfer]: copyArrayTo(categoryConstants.defaultTransferCategories, [])
},
usePresetCategories: false,
showPresetCategories: false,
@@ -205,6 +216,7 @@ export default {
};
},
computed: {
...mapStores(useRootStore, useSettingsStore, useTransactionCategoriesStore, useExchangeRatesStore),
allLanguages() {
return this.$locale.getAllLanguageInfos();
},
@@ -212,25 +224,25 @@ export default {
return this.$locale.getAllCurrencies();
},
allWeekDays() {
return this.$constants.datetime.allWeekDays;
return datetimeConstants.allWeekDays;
},
currentLocale: {
get: function () {
return this.$i18n.locale;
},
set: function (value) {
const isCurrencyDefault = this.user.defaultCurrency === this.$store.state.defaultSetting.currency;
const isFirstWeekDayDefault = this.user.firstDayOfWeek === (this.$constants.datetime.allWeekDays[this.$t('default.firstDayOfWeek')] ? this.$constants.datetime.allWeekDays[this.$t('default.firstDayOfWeek')].type : 0);
const isCurrencyDefault = this.user.defaultCurrency === this.settingsStore.defaultSetting.currency;
const isFirstWeekDayDefault = this.user.firstDayOfWeek === (datetimeConstants.allWeekDays[this.$t('default.firstDayOfWeek')] ? datetimeConstants.allWeekDays[this.$t('default.firstDayOfWeek')].type : 0);
this.user.language = value;
this.$locale.setLanguage(value);
if (isCurrencyDefault) {
this.user.defaultCurrency = this.$store.state.defaultSetting.currency;
this.user.defaultCurrency = this.settingsStore.defaultSetting.currency;
}
if (isFirstWeekDayDefault) {
this.user.firstDayOfWeek = this.$constants.datetime.allWeekDays[this.$t('default.firstDayOfWeek')] ? this.$constants.datetime.allWeekDays[this.$t('default.firstDayOfWeek')].type : 0;
this.user.firstDayOfWeek = datetimeConstants.allWeekDays[this.$t('default.firstDayOfWeek')] ? datetimeConstants.allWeekDays[this.$t('default.firstDayOfWeek')].type : 0;
}
}
},
@@ -324,7 +336,7 @@ export default {
}
}
self.$store.dispatch('register', {
self.rootStore.register({
user: self.user
}).then(response => {
if (!self.$user.isUserLogined()) {
@@ -346,7 +358,7 @@ export default {
}
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
if (!self.usePresetCategories) {
@@ -358,7 +370,7 @@ export default {
return;
}
self.$store.dispatch('addCategories', {
self.transactionCategoriesStore.addCategories({
categories: allCategories
}).then(() => {
self.submitting = false;
@@ -383,17 +395,17 @@ export default {
});
},
getDayOfWeekName(dayOfWeek) {
const weekName = this.$utilities.getNameByKeyValue(this.$constants.datetime.allWeekDays, dayOfWeek, 'type', 'name');
const weekName = getNameByKeyValue(datetimeConstants.allWeekDays, dayOfWeek, 'type', 'name');
const i18nWeekNameKey = `datetime.${weekName}.long`;
return this.$t(i18nWeekNameKey);
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case this.$constants.category.allCategoryTypes.Income.toString():
case categoryConstants.allCategoryTypes.Income.toString():
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense.toString():
case categoryConstants.allCategoryTypes.Expense.toString():
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer.toString():
case categoryConstants.allCategoryTypes.Transfer.toString():
return this.$t('Transfer Categories');
default:
return this.$t('Transaction Categories');
+24 -16
View File
@@ -57,6 +57,16 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useUserStore } from '@/stores/user.js';
import { useTokensStore } from '@/stores/token.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import logger from '@/lib/logger.js';
import webauthn from '@/lib/webauthn.js';
import { isModalShowing } from '@/lib/ui.mobile.js';
export default {
props: [
'f7router'
@@ -67,6 +77,7 @@ export default {
}
},
computed: {
...mapStores(useRootStore, useUserStore, useTokensStore, useExchangeRatesStore),
version() {
return 'v' + this.$version;
},
@@ -76,7 +87,7 @@ export default {
isWebAuthnAvailable() {
return this.$settings.isEnableApplicationLockWebAuthn()
&& this.$user.getWebAuthnCredentialId()
&& this.$webauthn.isSupported();
&& webauthn.isSupported();
},
currentLanguageName() {
const currentLocale = this.$i18n.locale;
@@ -99,34 +110,34 @@ export default {
return;
}
if (!self.$webauthn.isSupported()) {
if (!webauthn.isSupported()) {
self.$toast('This device does not support WebAuthn');
return;
}
self.$showLoading();
self.$webauthn.verifyCredential(
self.$store.state.currentUserInfo,
webauthn.verifyCredential(
self.userStore.currentUserInfo,
self.$user.getWebAuthnCredentialId()
).then(({ id, userName, userSecret }) => {
self.$hideLoading();
self.$user.unlockTokenByWebAuthn(id, userName, userSecret);
self.$store.dispatch('refreshTokenAndRevokeOldToken').then(response => {
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
if (response.user && response.user.language) {
self.$locale.setLanguage(response.user.language);
}
});
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
router.refreshPage();
}).catch(error => {
self.$hideLoading();
self.$logger.error('failed to use webauthn to verify', error);
logger.error('failed to use webauthn to verify', error);
if (error.notSupported) {
self.$toast('This device does not support WebAuthn');
@@ -146,12 +157,12 @@ export default {
return;
}
if (self.$ui.isModalShowing()) {
if (isModalShowing()) {
return;
}
const router = self.f7router;
const user = self.$store.state.currentUserInfo;
const user = self.userStore.currentUserInfo;
if (!user || !user.username) {
self.$alert('An error has occurred');
@@ -160,19 +171,19 @@ export default {
try {
self.$user.unlockTokenByPinCode(user.username, pinCode);
self.$store.dispatch('refreshTokenAndRevokeOldToken').then(response => {
self.tokensStore.refreshTokenAndRevokeOldToken().then(response => {
if (response.user && response.user.language) {
self.$locale.setLanguage(response.user.language);
}
});
if (self.$settings.isAutoUpdateExchangeRatesData()) {
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false });
}
router.refreshPage();
} catch (ex) {
self.$logger.error('failed to unlock by pin code', ex);
logger.error('failed to unlock by pin code', ex);
self.$toast('PIN code is wrong');
}
},
@@ -181,10 +192,7 @@ export default {
const router = self.f7router;
self.$confirm('Are you sure you want to re-login?', () => {
self.$user.clearTokenAndUserInfo(true);
self.$user.clearWebAuthnConfig();
self.$store.dispatch('clearUserInfoState');
self.$store.dispatch('resetState');
self.rootStore.forceLogout();
self.$settings.clearSettings();
self.$locale.initLocale();
+66 -50
View File
@@ -4,7 +4,7 @@
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t(title)"></f7-nav-title>
<f7-nav-right>
<f7-link icon-f7="ellipsis" v-if="!editAccountId && account.type === $constants.account.allAccountTypes.MultiSubAccounts" @click="showMoreActionSheet = true"></f7-link>
<f7-link icon-f7="ellipsis" v-if="!editAccountId && account.type === allAccountTypes.MultiSubAccounts" @click="showMoreActionSheet = true"></f7-link>
<f7-link :class="{ 'disabled': isInputEmpty() || submitting }" :text="$t(saveButtonTitle)" @click="save"></f7-link>
</f7-nav-right>
</f7-navbar>
@@ -42,7 +42,7 @@
>
<list-item-selection-sheet value-type="item"
key-field="id" value-field="id" title-field="name"
:items="allAccountTypes"
:items="allAccountTypesArray"
:title-i18n="true"
v-model:show="showAccountTypeSheet"
v-model="account.type">
@@ -96,7 +96,7 @@
<f7-list-input label="Description" type="textarea" placeholder="Your account description (optional)"></f7-list-input>
</f7-list>
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === $constants.account.allAccountTypes.SingleAccount">
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === allAccountTypes.SingleAccount">
<f7-list-input
type="text"
clear-button
@@ -180,11 +180,11 @@
class="list-item-with-header-and-title"
:class="{ 'disabled': editAccountId }"
:header="$t('Account Balance')"
:title="$locale.getDisplayCurrency(account.balance, account.currency)"
:title="getAccountBalance(account)"
@click="account.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
<number-pad-sheet :min-value="allowedMinAmount"
:max-value="allowedMaxAmount"
v-model:show="account.showBalanceSheet"
v-model="account.balance"
></number-pad-sheet>
@@ -204,7 +204,7 @@
></f7-list-input>
</f7-list>
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<f7-list form strong inset dividers class="margin-vertical" v-else-if="!loading && account.type === allAccountTypes.MultiSubAccounts">
<f7-list-input
type="text"
clear-button
@@ -277,7 +277,7 @@
></f7-list-input>
</f7-list>
<f7-block class="no-padding no-margin" v-if="!loading && account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<f7-block class="no-padding no-margin" v-if="!loading && account.type === allAccountTypes.MultiSubAccounts">
<f7-list strong inset dividers class="subaccount-edit-list margin-vertical"
:key="idx"
v-for="(subAccount, idx) in subAccounts">
@@ -373,11 +373,11 @@
class="list-item-with-header-and-title"
:class="{ 'disabled': editAccountId }"
:header="$t('Sub Account Balance')"
:title="$locale.getDisplayCurrency(subAccount.balance, subAccount.currency)"
:title="getAccountBalance(subAccount)"
@click="subAccount.showBalanceSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
<number-pad-sheet :min-value="allowedMinAmount"
:max-value="allowedMaxAmount"
v-model:show="subAccount.showBalanceSheet"
v-model="subAccount.balance"
></number-pad-sheet>
@@ -420,13 +420,24 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import accountConstants from '@/consts/account.js';
import iconConstants from '@/consts/icon.js';
import colorConstants from '@/consts/color.js';
import currencyConstants from '@/consts/currency.js';
import transactionConstants from '@/consts/transaction.js';
import { getNameByKeyValue } from '@/lib/common.js';
export default {
props: [
'f7route',
'f7router'
],
data() {
const self = this;
const userStore = useUserStore();
return {
editAccountId: null,
@@ -434,11 +445,11 @@ export default {
loadingError: null,
account: {
category: 1,
type: self.$constants.account.allAccountTypes.SingleAccount,
type: accountConstants.allAccountTypes.SingleAccount,
name: '',
icon: self.$constants.icons.defaultAccountIconId,
color: self.$constants.colors.defaultAccountColor,
currency: self.$store.getters.currentUserDefaultCurrency,
icon: iconConstants.defaultAccountIconId,
color: colorConstants.defaultAccountColor,
currency: userStore.currentUserDefaultCurrency,
balance: 0,
comment: '',
visible: true,
@@ -456,6 +467,7 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useAccountsStore),
title() {
if (!this.editAccountId) {
return 'Add Account';
@@ -470,26 +482,29 @@ export default {
return 'Save';
}
},
allAccountCategories() {
return this.$constants.account.allCategories;
},
allAccountTypes() {
return [{
id: 1,
name: 'Single Account'
}, {
id: 2,
name: 'Multi Sub Accounts'
}];
return accountConstants.allAccountTypes;
},
allAccountCategories() {
return accountConstants.allCategories;
},
allAccountTypesArray() {
return accountConstants.allAccountTypesArray;
},
allAccountIcons() {
return this.$constants.icons.allAccountIcons;
return iconConstants.allAccountIcons;
},
allAccountColors() {
return this.$constants.colors.allAccountColors;
return colorConstants.allAccountColors;
},
allCurrencies() {
return this.$locale.getAllCurrencies();
},
allowedMinAmount() {
return transactionConstants.minAmount;
},
allowedMaxAmount() {
return transactionConstants.maxAmount;
}
},
watch: {
@@ -511,7 +526,7 @@ export default {
self.editAccountId = query.id;
self.$store.dispatch('getAccount', {
self.accountsStore.getAccount({
accountId: self.editAccountId
}).then(account => {
self.account.id = account.id;
@@ -568,7 +583,7 @@ export default {
addSubAccount() {
const self = this;
if (self.account.type !== self.$constants.account.allAccountTypes.MultiSubAccounts) {
if (self.account.type !== this.allAccountTypes.MultiSubAccounts) {
return;
}
@@ -578,7 +593,7 @@ export default {
name: '',
icon: self.account.icon,
color: self.account.color,
currency: self.$store.getters.currentUserDefaultCurrency,
currency: self.userStore.currentUserDefaultCurrency,
balance: 0,
comment: '',
visible: true,
@@ -614,7 +629,7 @@ export default {
let problemMessage = self.getInputEmptyProblemMessage(self.account, false);
if (!problemMessage && self.account.type === self.$constants.account.allAccountTypes.MultiSubAccounts) {
if (!problemMessage && self.account.type === self.allAccountTypes.MultiSubAccounts) {
for (let i = 0; i < self.subAccounts.length; i++) {
problemMessage = self.getInputEmptyProblemMessage(self.subAccounts[i], true);
@@ -634,12 +649,12 @@ export default {
const subAccounts = [];
if (self.account.type === self.$constants.account.allAccountTypes.MultiSubAccounts) {
if (self.account.type === self.allAccountTypes.MultiSubAccounts) {
for (let i = 0; i < self.subAccounts.length; i++) {
const subAccount = self.subAccounts[i];
const submitAccount = {
category: self.account.category,
type: self.$constants.account.allAccountTypes.SingleAccount,
type: self.allAccountTypes.SingleAccount,
name: subAccount.name,
icon: subAccount.icon,
color: subAccount.color,
@@ -663,10 +678,10 @@ export default {
name: self.account.name,
icon: self.account.icon,
color: self.account.color,
currency: self.account.type === self.$constants.account.allAccountTypes.SingleAccount ? self.account.currency : self.$constants.currency.parentAccountCurrencyPlaceholder,
balance: self.account.type === self.$constants.account.allAccountTypes.SingleAccount ? self.account.balance : 0,
currency: self.account.type === self.allAccountTypes.SingleAccount ? self.account.currency : currencyConstants.parentAccountCurrencyPlaceholder,
balance: self.account.type === self.allAccountTypes.SingleAccount ? self.account.balance : 0,
comment: self.account.comment,
subAccounts: self.account.type === self.$constants.account.allAccountTypes.SingleAccount ? null : subAccounts,
subAccounts: self.account.type === self.allAccountTypes.SingleAccount ? null : subAccounts,
};
if (self.editAccountId) {
@@ -674,7 +689,7 @@ export default {
submitAccount.hidden = !self.account.visible;
}
self.$store.dispatch('saveAccount', {
self.accountsStore.saveAccount({
account: submitAccount
}).then(() => {
self.submitting = false;
@@ -697,19 +712,20 @@ export default {
});
},
getAccountTypeName(accountType) {
const typeName = this.$utilities.getNameByKeyValue(this.allAccountTypes, accountType, 'id', 'name');
const typeName = getNameByKeyValue(this.allAccountTypesArray, accountType, 'id', 'name');
return this.$t(typeName);
},
getAccountCategoryName(accountCategory) {
const categoryName = this.$utilities.getNameByKeyValue(this.allAccountCategories, accountCategory, 'id', 'name');
const categoryName = getNameByKeyValue(this.allAccountCategories, accountCategory, 'id', 'name');
return this.$t(categoryName);
},
getAccountBalance(account) {
return this.$locale.getDisplayCurrency(account.balance, account.currency)
},
chooseSuitableIcon(oldCategory, newCategory) {
const allCategories = this.$constants.account.allCategories;
for (let i = 0; i < allCategories.length; i++) {
if (allCategories[i].id === oldCategory) {
if (this.account.icon !== allCategories[i].defaultAccountIconId) {
for (let i = 0; i < this.allAccountCategories.length; i++) {
if (this.allAccountCategories[i].id === oldCategory) {
if (this.account.icon !== this.allAccountCategories[i].defaultAccountIconId) {
return;
} else {
break;
@@ -717,9 +733,9 @@ export default {
}
}
for (let i = 0; i < allCategories.length; i++) {
if (allCategories[i].id === newCategory) {
this.account.icon = allCategories[i].defaultAccountIconId;
for (let i = 0; i < this.allAccountCategories.length; i++) {
if (this.allAccountCategories[i].id === newCategory) {
this.account.icon = this.allAccountCategories[i].defaultAccountIconId;
}
}
},
@@ -730,7 +746,7 @@ export default {
return true;
}
if (this.account.type === this.$constants.account.allAccountTypes.MultiSubAccounts) {
if (this.account.type === this.allAccountTypes.MultiSubAccounts) {
for (let i = 0; i < this.subAccounts.length; i++) {
const isSubAccountEmpty = !!this.getInputEmptyProblemMessage(this.subAccounts[i], true);
@@ -749,7 +765,7 @@ export default {
return 'Account type cannot be empty';
} else if (!account.name) {
return 'Account name cannot be empty';
} else if (account.type === this.$constants.account.allAccountTypes.SingleAccount && !account.currency) {
} else if (account.type === this.allAccountTypes.SingleAccount && !account.currency) {
return 'Account currency cannot be empty';
} else {
return null;
+50 -267
View File
@@ -18,7 +18,7 @@
</p>
<p class="no-margin">
<span class="net-assets" v-if="loading">0.00 USD</span>
<span class="net-assets" v-else-if="!loading">{{ $locale.getDisplayCurrency(netAssets, defaultCurrency) }}</span>
<span class="net-assets" v-else-if="!loading">{{ netAssets }}</span>
<f7-link class="margin-left-half" @click="toggleShowAccountBalance()">
<f7-icon :f7="showAccountBalance ? 'eye_slash_fill' : 'eye_fill'" size="18px"></f7-icon>
</f7-link>
@@ -29,10 +29,10 @@
</small>
<small class="account-overview-info" v-else-if="!loading">
<span>{{ $t('Total assets') }}</span>
<span>{{ $locale.getDisplayCurrency(totalAssets, defaultCurrency) }}</span>
<span>{{ totalAssets }}</span>
<span>|</span>
<span>{{ $t('Total liabilities') }}</span>
<span>{{ $locale.getDisplayCurrency(totalLiabilities, defaultCurrency) }}</span>
<span>{{ totalLiabilities }}</span>
</small>
</p>
</f7-card-header>
@@ -68,21 +68,21 @@
<f7-list-item group-title :sortable="false">
<small>
<span>{{ $t(accountCategory.name) }}</span>
<span style="margin-left: 10px">{{ $locale.getDisplayCurrency(accountCategoryTotalBalance(accountCategory), defaultCurrency) }}</span>
<span style="margin-left: 10px">{{ accountCategoryTotalBalance(accountCategory) }}</span>
</small>
</f7-list-item>
<f7-list-item swipeout
class="nested-list-item"
:id="getAccountDomId(account)"
:class="{ 'has-child-list-item': account.type === $constants.account.allAccountTypes.MultiSubAccounts && hasVisibleSubAccount(account), 'actual-first-child': account.id === firstShowingIds.accounts[accountCategory.id], 'actual-last-child': account.id === lastShowingIds.accounts[accountCategory.id] }"
:after="$locale.getDisplayCurrency(accountBalance(account), account.currency)"
:class="{ 'has-child-list-item': account.type === allAccountTypes.MultiSubAccounts && hasVisibleSubAccount(account), 'actual-first-child': account.id === firstShowingIds.accounts[accountCategory.id], 'actual-last-child': account.id === lastShowingIds.accounts[accountCategory.id] }"
:after="accountBalance(account)"
:link="!sortable ? '/transaction/list?accountId=' + account.id : null"
:key="account.id"
v-for="account in categorizedAccounts[accountCategory.id].accounts"
v-show="showHidden || !account.hidden"
@taphold="setSortable()"
>
<template #media v-if="account.type !== $constants.account.allAccountTypes.MultiSubAccounts || !hasVisibleSubAccount(account)">
<template #media v-if="account.type !== allAccountTypes.MultiSubAccounts || !hasVisibleSubAccount(account)">
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color">
<f7-badge color="gray" class="right-bottom-icon" v-if="account.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
@@ -93,7 +93,7 @@
<template #title>
<div class="display-flex padding-top-half padding-bottom-half">
<ItemIcon icon-type="account" :icon-id="account.icon" :color="account.color"
v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts && hasVisibleSubAccount(account)">
v-if="account.type === allAccountTypes.MultiSubAccounts && hasVisibleSubAccount(account)">
<f7-badge color="gray" class="right-bottom-icon" v-if="account.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
@@ -103,12 +103,12 @@
<div class="item-footer" v-if="account.comment">{{ account.comment }}</div>
</div>
</div>
<li v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts">
<li v-if="account.type === allAccountTypes.MultiSubAccounts">
<ul class="no-padding">
<f7-list-item class="no-sortable nested-list-item-child"
:class="{ 'actual-first-child': subAccount.id === firstShowingIds.subAccounts[account.id], 'actual-last-child': subAccount.id === lastShowingIds.subAccounts[account.id] }"
:id="getAccountDomId(subAccount)"
:title="subAccount.name" :footer="subAccount.comment" :after="$locale.getDisplayCurrency(accountBalance(subAccount), subAccount.currency)"
:title="subAccount.name" :footer="subAccount.comment" :after="accountBalance(subAccount)"
:link="!sortable ? '/transaction/list?accountId=' + subAccount.id : null"
:key="subAccount.id"
v-for="subAccount in account.subAccounts"
@@ -165,6 +165,14 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import accountConstants from '@/consts/account.js';
import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7router'
@@ -184,194 +192,46 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useAccountsStore, useExchangeRatesStore),
defaultCurrency() {
return this.$store.getters.currentUserDefaultCurrency;
return this.userStore.currentUserDefaultCurrency;
},
allAccountTypes() {
return accountConstants.allAccountTypes;
},
allAccountCategories() {
return this.$constants.account.allCategories;
return accountConstants.allCategories;
},
categorizedAccounts() {
return this.$store.state.allCategorizedAccounts;
return this.accountsStore.allCategorizedAccounts;
},
allAccountCount() {
return this.$store.getters.allAvailableAccountsCount;
return this.accountsStore.allAvailableAccountsCount;
},
firstShowingIds() {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.categorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.categorizedAccounts, category)) {
continue;
}
if (!this.categorizedAccounts[category] || !this.categorizedAccounts[category].accounts) {
continue;
}
const accounts = this.categorizedAccounts[category].accounts;
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
if (account.type === this.$constants.account.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
if (this.showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (this.showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
return this.accountsStore.getFirstShowingIds(this.showHidden);
},
lastShowingIds() {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.categorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.categorizedAccounts, category)) {
continue;
}
if (!this.categorizedAccounts[category] || !this.categorizedAccounts[category].accounts) {
continue;
}
const accounts = this.categorizedAccounts[category].accounts;
for (let i = accounts.length - 1; i >= 0; i--) {
const account = accounts[i];
if (account.type === this.$constants.account.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = account.subAccounts.length - 1; j >= 0; j--) {
const subAccount = account.subAccounts[j];
if (this.showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (this.showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
return this.accountsStore.getLastShowingIds(this.showHidden);
},
noAvailableAccount() {
if (this.showHidden) {
return this.$store.getters.allAvailableAccountsCount < 1;
return this.accountsStore.allAvailableAccountsCount < 1;
} else {
return this.$store.getters.allVisibleAccountsCount < 1;
return this.accountsStore.allVisibleAccountsCount < 1;
}
},
netAssets() {
if (!this.showAccountBalance) {
return '***';
}
const accountsBalance = this.$utilities.getAllFilteredAccountsBalance(this.categorizedAccounts, () => true);
let netAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === this.defaultCurrency) {
netAssets += accountsBalance[i].balance;
} else {
const balance = this.$store.getters.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
if (!this.$utilities.isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
netAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return netAssets + '+';
} else {
return netAssets;
}
const netAssets = this.accountsStore.getNetAssets(this.showAccountBalance);
return this.$locale.getDisplayCurrency(netAssets, this.defaultCurrency);
},
totalAssets() {
if (!this.showAccountBalance) {
return '***';
}
const accountsBalance = this.$utilities.getAllFilteredAccountsBalance(this.categorizedAccounts, account => account.isAsset);
let totalAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === this.defaultCurrency) {
totalAssets += accountsBalance[i].balance;
} else {
const balance = this.$store.getters.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
if (!this.$utilities.isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalAssets + '+';
} else {
return totalAssets;
}
const totalAssets = this.accountsStore.getTotalAssets(this.showAccountBalance);
return this.$locale.getDisplayCurrency(totalAssets, this.defaultCurrency);
},
totalLiabilities() {
if (!this.showAccountBalance) {
return '***';
}
const accountsBalance = this.$utilities.getAllFilteredAccountsBalance(this.categorizedAccounts, account => account.isLiability);
let totalLiabilities = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === this.defaultCurrency) {
totalLiabilities -= accountsBalance[i].balance;
} else {
const balance = this.$store.getters.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
if (!this.$utilities.isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalLiabilities -= Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalLiabilities + '+';
} else {
return totalLiabilities;
}
const totalLiabilities = this.accountsStore.getTotalLiabilities(this.showAccountBalance);
return this.$locale.getDisplayCurrency(totalLiabilities, this.defaultCurrency);
}
},
created() {
@@ -379,7 +239,7 @@ export default {
self.loading = true;
self.$store.dispatch('loadAllAccounts', {
self.accountsStore.loadAllAccounts({
force: false
}).then(() => {
self.loading = false;
@@ -394,7 +254,7 @@ export default {
},
methods: {
onPageAfterIn() {
if (this.$store.state.accountListStateInvalid && !this.loading) {
if (this.accountsStore.accountListStateInvalid && !this.loading) {
this.reload(null);
}
@@ -408,7 +268,7 @@ export default {
const self = this;
self.$store.dispatch('loadAllAccounts', {
self.accountsStore.loadAllAccounts({
force: true
}).then(() => {
if (done) {
@@ -425,99 +285,22 @@ export default {
});
},
hasAccount(accountCategory, visibleOnly) {
if (!this.categorizedAccounts[accountCategory.id] ||
!this.categorizedAccounts[accountCategory.id].accounts ||
!this.categorizedAccounts[accountCategory.id].accounts.length) {
return false;
}
let shownCount = 0;
for (let i = 0; i < this.categorizedAccounts[accountCategory.id].accounts.length; i++) {
const account = this.categorizedAccounts[accountCategory.id].accounts[i];
if (!visibleOnly || !account.hidden) {
shownCount++;
}
}
return shownCount > 0;
return this.accountsStore.hasAccount(accountCategory, visibleOnly);
},
hasVisibleSubAccount(account) {
if (!account || account.type !== this.$constants.account.allAccountTypes.MultiSubAccounts || !account.subAccounts) {
return false;
}
for (let i = 0; i < account.subAccounts.length; i++) {
if (this.showHidden || !account.subAccounts[i].hidden) {
return true;
}
}
return false;
return this.accountsStore.hasVisibleSubAccount(this.showHidden, account);
},
toggleShowAccountBalance() {
this.showAccountBalance = !this.showAccountBalance;
this.$settings.setShowAccountBalance(this.showAccountBalance);
},
accountBalance(account) {
if (account.type !== this.$constants.account.allAccountTypes.SingleAccount) {
return null;
}
if (this.showAccountBalance) {
if (account.isAsset) {
return account.balance;
} else if (account.isLiability) {
return -account.balance;
} else {
return account.balance;
}
} else {
return '***';
}
const balance = this.accountsStore.getAccountBalance(this.showAccountBalance, account);
return this.$locale.getDisplayCurrency(balance, account.currency);
},
accountCategoryTotalBalance(accountCategory) {
if (!this.showAccountBalance) {
return '***';
}
const accountsBalance = this.$utilities.getAllFilteredAccountsBalance(this.categorizedAccounts, account => account.category === accountCategory.id);
let totalBalance = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === this.defaultCurrency) {
if (accountsBalance[i].isAsset) {
totalBalance += accountsBalance[i].balance;
} else if (accountsBalance[i].isLiability) {
totalBalance -= accountsBalance[i].balance;
} else {
totalBalance += accountsBalance[i].balance;
}
} else {
const balance = this.$store.getters.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
if (!this.$utilities.isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
if (accountsBalance[i].isAsset) {
totalBalance += Math.floor(balance);
} else if (accountsBalance[i].isLiability) {
totalBalance -= Math.floor(balance);
} else {
totalBalance += Math.floor(balance);
}
}
}
if (hasUnCalculatedAmount) {
return totalBalance + '+';
} else {
return totalBalance;
}
const totalBalance = this.accountsStore.getAccountCategoryTotalBalance(this.showAccountBalance, accountCategory);
return this.$locale.getDisplayCurrency(totalBalance, this.defaultCurrency);
},
setSortable() {
if (this.sortable) {
@@ -543,7 +326,7 @@ export default {
return;
}
self.$store.dispatch('changeAccountDisplayOrder', {
self.accountsStore.changeAccountDisplayOrder({
accountId: id,
from: event.from - 1, // first item in the list is title, so the index need minus one
to: event.to - 1
@@ -565,7 +348,7 @@ export default {
self.displayOrderSaving = true;
self.$showLoading();
self.$store.dispatch('updateAccountDisplayOrders').then(() => {
self.accountsStore.updateAccountDisplayOrders().then(() => {
self.displayOrderSaving = false;
self.$hideLoading();
@@ -589,7 +372,7 @@ export default {
self.$showLoading();
self.$store.dispatch('hideAccount', {
self.accountsStore.hideAccount({
account: account,
hidden: hidden
}).then(() => {
@@ -620,10 +403,10 @@ export default {
self.accountToDelete = null;
self.$showLoading();
self.$store.dispatch('deleteAccount', {
self.accountsStore.deleteAccount({
account: account,
beforeResolve: (done) => {
self.$ui.onSwipeoutDeleted(self.getAccountDomId(account), done);
onSwipeoutDeleted(self.getAccountDomId(account), done);
}
}).then(() => {
self.$hideLoading();
+8 -2
View File
@@ -18,6 +18,9 @@
<script>
import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
export default {
props: [
'f7router'
@@ -28,12 +31,15 @@ export default {
loadingError: null
};
},
computed: {
...mapStores(useTransactionCategoriesStore)
},
created() {
const self = this;
self.loading = true;
self.$store.dispatch('loadAllCategories', {
self.transactionCategoriesStore.loadAllCategories({
force: false
}).then(() => {
self.loading = false;
@@ -53,7 +59,7 @@ export default {
reload(done) {
const self = this;
self.$store.dispatch('loadAllCategories', {
self.transactionCategoriesStore.loadAllCategories({
force: true
}).then(() => {
if (done) {
+17 -9
View File
@@ -128,6 +128,13 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import categoryConstants from '@/consts/category.js';
import iconConstants from '@/consts/icon.js';
import colorConstants from '@/consts/color.js';
export default {
props: [
'f7route',
@@ -145,8 +152,8 @@ export default {
type: parseInt(query.type),
name: '',
parentId: query.parentId,
icon: self.$constants.icons.defaultCategoryIconId,
color: self.$constants.colors.defaultCategoryColor,
icon: iconConstants.defaultCategoryIconId,
color: colorConstants.defaultCategoryColor,
comment: '',
visible: true,
showIconSelectionSheet: false,
@@ -156,6 +163,7 @@ export default {
};
},
computed: {
...mapStores(useTransactionCategoriesStore),
title() {
if (!this.editCategoryId) {
if (this.category.parentId === '0') {
@@ -175,10 +183,10 @@ export default {
}
},
allCategoryIcons() {
return this.$constants.icons.allCategoryIcons;
return iconConstants.allCategoryIcons;
},
allCategoryColors() {
return this.$constants.colors.allCategoryColors;
return colorConstants.allCategoryColors;
},
inputIsEmpty() {
return !!this.inputEmptyProblemMessage;
@@ -205,7 +213,7 @@ export default {
self.loading = true;
self.editCategoryId = query.id;
self.$store.dispatch('getCategory', {
self.transactionCategoriesStore.getCategory({
categoryId: self.editCategoryId
}).then(category => {
self.category.id = category.id;
@@ -229,9 +237,9 @@ export default {
} else if (query.parentId) {
const categoryType = parseInt(query.type);
if (categoryType !== this.$constants.category.allCategoryTypes.Income &&
categoryType !== this.$constants.category.allCategoryTypes.Expense &&
categoryType !== this.$constants.category.allCategoryTypes.Transfer) {
if (categoryType !== categoryConstants.allCategoryTypes.Income &&
categoryType !== categoryConstants.allCategoryTypes.Expense &&
categoryType !== categoryConstants.allCategoryTypes.Transfer) {
self.$toast('Parameter Invalid');
self.loadingError = 'Parameter Invalid';
return;
@@ -272,7 +280,7 @@ export default {
submitCategory.hidden = !self.category.visible;
}
self.$store.dispatch('saveCategory', {
self.transactionCategoriesStore.saveCategory({
category: submitCategory
}).then(() => {
self.submitting = false;
+25 -18
View File
@@ -87,6 +87,12 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import categoryConstants from '@/consts/category.js';
import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7route',
@@ -109,19 +115,20 @@ export default {
};
},
computed: {
...mapStores(useTransactionCategoriesStore),
categories() {
if (!this.categoryId || this.categoryId === '' || this.categoryId === '0') {
if (!this.$store.state.allTransactionCategories || !this.$store.state.allTransactionCategories[this.categoryType]) {
if (!this.transactionCategoriesStore.allTransactionCategories || !this.transactionCategoriesStore.allTransactionCategories[this.categoryType]) {
return [];
}
return this.$store.state.allTransactionCategories[this.categoryType];
return this.transactionCategoriesStore.allTransactionCategories[this.categoryType];
} else if (this.categoryId && this.categoryId !== '' && this.categoryId !== '0') {
if (!this.$store.state.allTransactionCategoriesMap || !this.$store.state.allTransactionCategoriesMap[this.categoryId]) {
if (!this.transactionCategoriesStore.allTransactionCategoriesMap || !this.transactionCategoriesStore.allTransactionCategoriesMap[this.categoryId]) {
return [];
}
return this.$store.state.allTransactionCategoriesMap[this.categoryId].subCategories;
return this.transactionCategoriesStore.allTransactionCategoriesMap[this.categoryId].subCategories;
} else {
return [];
}
@@ -130,13 +137,13 @@ export default {
let title = '';
switch (this.categoryType) {
case this.$constants.category.allCategoryTypes.Income:
case categoryConstants.allCategoryTypes.Income:
title = 'Income';
break;
case this.$constants.category.allCategoryTypes.Expense:
case categoryConstants.allCategoryTypes.Expense:
title = 'Expense';
break;
case this.$constants.category.allCategoryTypes.Transfer:
case categoryConstants.allCategoryTypes.Transfer:
title = 'Transfer';
break;
default:
@@ -189,9 +196,9 @@ export default {
self.categoryType = parseInt(query.type);
if (self.categoryType !== this.$constants.category.allCategoryTypes.Income &&
self.categoryType !== this.$constants.category.allCategoryTypes.Expense &&
self.categoryType !== this.$constants.category.allCategoryTypes.Transfer) {
if (self.categoryType !== categoryConstants.allCategoryTypes.Income &&
self.categoryType !== categoryConstants.allCategoryTypes.Expense &&
self.categoryType !== categoryConstants.allCategoryTypes.Transfer) {
self.$toast('Parameter Invalid');
self.loadingError = 'Parameter Invalid';
return;
@@ -207,7 +214,7 @@ export default {
self.loading = true;
self.$store.dispatch('loadAllCategories', {
self.transactionCategoriesStore.loadAllCategories({
force: false
}).then(() => {
self.loading = false;
@@ -222,7 +229,7 @@ export default {
},
methods: {
onPageAfterIn() {
if (this.$store.state.transactionCategoryListStateInvalid && !this.loading) {
if (this.transactionCategoriesStore.transactionCategoryListStateInvalid && !this.loading) {
this.reload(null);
}
@@ -236,7 +243,7 @@ export default {
const self = this;
self.$store.dispatch('loadAllCategories', {
self.transactionCategoriesStore.loadAllCategories({
force: true
}).then(() => {
if (done) {
@@ -276,7 +283,7 @@ export default {
return;
}
self.$store.dispatch('changeCategoryDisplayOrder', {
self.transactionCategoriesStore.changeCategoryDisplayOrder({
categoryId: id,
from: event.from,
to: event.to
@@ -298,7 +305,7 @@ export default {
self.displayOrderSaving = true;
self.$showLoading();
self.$store.dispatch('updateCategoryDisplayOrders', {
self.transactionCategoriesStore.updateCategoryDisplayOrders({
type: self.categoryType,
parentId: self.categoryId,
}).then(() => {
@@ -325,7 +332,7 @@ export default {
self.$showLoading();
self.$store.dispatch('hideCategory', {
self.transactionCategoriesStore.hideCategory({
category: category,
hidden: hidden
}).then(() => {
@@ -356,10 +363,10 @@ export default {
self.categoryToDelete = null;
self.$showLoading();
self.$store.dispatch('deleteCategory', {
self.transactionCategoriesStore.deleteCategory({
category: category,
beforeResolve: (done) => {
self.$ui.onSwipeoutDeleted(self.getCategoryDomId(category), done);
onSwipeoutDeleted(self.getCategoryDomId(category), done);
}
}).then(() => {
self.$hideLoading();
+22 -15
View File
@@ -55,6 +55,12 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import categoryConstants from '@/consts/category.js';
import { copyArrayTo } from '@/lib/common.js';
export default {
props: [
'f7route',
@@ -74,6 +80,7 @@ export default {
};
},
computed: {
...mapStores(useTransactionCategoriesStore),
allLanguages() {
return this.$locale.getAllLanguageInfos();
}
@@ -85,9 +92,9 @@ export default {
self.categoryType = parseInt(query.type);
if (self.categoryType !== 0 &&
self.categoryType !== this.$constants.category.allCategoryTypes.Income &&
self.categoryType !== this.$constants.category.allCategoryTypes.Expense &&
self.categoryType !== this.$constants.category.allCategoryTypes.Transfer) {
self.categoryType !== categoryConstants.allCategoryTypes.Income &&
self.categoryType !== categoryConstants.allCategoryTypes.Expense &&
self.categoryType !== categoryConstants.allCategoryTypes.Transfer) {
self.$toast('Parameter Invalid');
self.loadingError = 'Parameter Invalid';
return;
@@ -97,13 +104,13 @@ export default {
for (let i = 1; i <= 3; i++) {
self.allCategories.push({
type: i,
categories: self.$utilities.copyArrayTo(self.getDefaultCategories(i), [])
categories: copyArrayTo(self.getDefaultCategories(i), [])
});
}
} else {
self.allCategories.push({
type: self.categoryType,
categories: self.$utilities.copyArrayTo(self.getDefaultCategories(self.categoryType), [])
categories: copyArrayTo(self.getDefaultCategories(self.categoryType), [])
});
}
},
@@ -113,12 +120,12 @@ export default {
},
getDefaultCategories(categoryType) {
switch (categoryType) {
case this.$constants.category.allCategoryTypes.Income:
return this.$constants.category.defaultIncomeCategories;
case this.$constants.category.allCategoryTypes.Expense:
return this.$constants.category.defaultExpenseCategories;
case this.$constants.category.allCategoryTypes.Transfer:
return this.$constants.category.defaultTransferCategories;
case categoryConstants.allCategoryTypes.Income:
return categoryConstants.defaultIncomeCategories;
case categoryConstants.allCategoryTypes.Expense:
return categoryConstants.defaultExpenseCategories;
case categoryConstants.allCategoryTypes.Transfer:
return categoryConstants.defaultTransferCategories;
default:
return [];
}
@@ -159,7 +166,7 @@ export default {
}
}
self.$store.dispatch('addCategories', {
self.transactionCategoriesStore.addCategories({
categories: categories
}).then(() => {
self.submitting = false;
@@ -178,11 +185,11 @@ export default {
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case this.$constants.category.allCategoryTypes.Income:
case categoryConstants.allCategoryTypes.Income:
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense:
case categoryConstants.allCategoryTypes.Expense:
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer:
case categoryConstants.allCategoryTypes.Transfer:
return this.$t('Transfer Categories');
default:
return this.$t('Transaction Categories');
@@ -66,7 +66,7 @@
<f7-accordion-content :style="{ height: collapseStates[accountCategory.category].opened ? 'auto' : '' }">
<f7-list strong inset dividers accordion-list class="combination-list-content">
<f7-list-item checkbox
:class="{ 'has-child-list-item': account.type === $constants.account.allAccountTypes.MultiSubAccounts && accountCategory.visibleSubAccounts[account.id] }"
:class="{ 'has-child-list-item': account.type === allAccountTypes.MultiSubAccounts && accountCategory.visibleSubAccounts[account.id] }"
:title="account.name"
:value="account.id"
:checked="isAccountOrSubAccountsAllChecked(account, filterAccountIds)"
@@ -80,7 +80,7 @@
<template #root>
<ul class="padding-left"
v-if="account.type === $constants.account.allAccountTypes.MultiSubAccounts && accountCategory.visibleSubAccounts[account.id]">
v-if="account.type === allAccountTypes.MultiSubAccounts && accountCategory.visibleSubAccounts[account.id]">
<f7-list-item checkbox
:title="subAccount.name"
:value="subAccount.id"
@@ -114,6 +114,14 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useAccountsStore } from '@/stores/account.js';
import { useStatisticsStore } from '@/stores/statistics.js';
import accountConstants from '@/consts/account.js';
import { copyObjectTo } from '@/lib/common.js';
import { getVisibleCategorizedAccounts } from '@/lib/account.js';
export default {
props: [
'f7route',
@@ -132,6 +140,7 @@ export default {
}
},
computed: {
...mapStores(useAccountsStore, useStatisticsStore),
title() {
if (this.modifyDefault) {
return 'Default Account Filter';
@@ -146,11 +155,14 @@ export default {
return 'Apply';
}
},
allAccountTypes() {
return accountConstants.allAccountTypes;
},
allVisibleCategorizedAccounts() {
return this.$utilities.getVisibleCategorizedAccounts(this.$store.state.allCategorizedAccounts);
return getVisibleCategorizedAccounts(this.accountsStore.allCategorizedAccounts);
},
hasAnyAvailableAccount() {
return this.$store.getters.allVisibleAccountsCount > 0;
return this.accountsStore.allVisibleAccountsCount > 0;
}
},
created() {
@@ -159,26 +171,26 @@ export default {
self.modifyDefault = !!query.modifyDefault;
self.$store.dispatch('loadAllAccounts', {
self.accountsStore.loadAllAccounts({
force: false
}).then(() => {
self.loading = false;
const allAccountIds = {};
for (let accountId in self.$store.state.allAccountsMap) {
if (!Object.prototype.hasOwnProperty.call(self.$store.state.allAccountsMap, accountId)) {
for (let accountId in self.accountsStore.allAccountsMap) {
if (!Object.prototype.hasOwnProperty.call(self.accountsStore.allAccountsMap, accountId)) {
continue;
}
const account = self.$store.state.allAccountsMap[accountId];
const account = self.accountsStore.allAccountsMap[accountId];
allAccountIds[account.id] = false;
}
if (self.modifyDefault) {
self.filterAccountIds = self.$utilities.copyObjectTo(self.$settings.getStatisticsDefaultAccountFilter(), allAccountIds);
self.filterAccountIds = copyObjectTo(self.$settings.getStatisticsDefaultAccountFilter(), allAccountIds);
} else {
self.filterAccountIds = self.$utilities.copyObjectTo(self.$store.state.transactionStatisticsFilter.filterAccountIds, allAccountIds);
self.filterAccountIds = copyObjectTo(self.statisticsStore.transactionStatisticsFilter.filterAccountIds, allAccountIds);
}
}).catch(error => {
if (error.processed) {
@@ -212,7 +224,7 @@ export default {
if (self.modifyDefault) {
self.$settings.setStatisticsDefaultAccountFilter(filteredAccountIds);
} else {
self.$store.dispatch('updateTransactionStatisticsFilter', {
self.statisticsStore.updateTransactionStatisticsFilter({
filterAccountIds: filteredAccountIds
});
}
@@ -221,15 +233,15 @@ export default {
},
selectAccountOrSubAccounts(e) {
const accountId = e.target.value;
const account = this.$store.state.allAccountsMap[accountId];
const account = this.accountsStore.allAccountsMap[accountId];
if (!account) {
return;
}
if (account.type === this.$constants.account.allAccountTypes.SingleAccount) {
if (account.type === this.allAccountTypes.SingleAccount) {
this.filterAccountIds[account.id] = !e.target.checked;
} else if (account.type === this.$constants.account.allAccountTypes.MultiSubAccounts) {
} else if (account.type === this.allAccountTypes.MultiSubAccounts) {
if (!account.subAccounts || !account.subAccounts.length) {
return;
}
@@ -242,7 +254,7 @@ export default {
},
selectAccount(e) {
const accountId = e.target.value;
const account = this.$store.state.allAccountsMap[accountId];
const account = this.accountsStore.allAccountsMap[accountId];
if (!account) {
return;
@@ -256,9 +268,9 @@ export default {
continue;
}
const account = this.$store.state.allAccountsMap[accountId];
const account = this.accountsStore.allAccountsMap[accountId];
if (account && account.type === this.$constants.account.allAccountTypes.SingleAccount) {
if (account && account.type === this.allAccountTypes.SingleAccount) {
this.filterAccountIds[account.id] = false;
}
}
@@ -269,9 +281,9 @@ export default {
continue;
}
const account = this.$store.state.allAccountsMap[accountId];
const account = this.accountsStore.allAccountsMap[accountId];
if (account && account.type === this.$constants.account.allAccountTypes.SingleAccount) {
if (account && account.type === this.allAccountTypes.SingleAccount) {
this.filterAccountIds[account.id] = true;
}
}
@@ -282,9 +294,9 @@ export default {
continue;
}
const account = this.$store.state.allAccountsMap[accountId];
const account = this.accountsStore.allAccountsMap[accountId];
if (account && account.type === this.$constants.account.allAccountTypes.SingleAccount) {
if (account && account.type === this.allAccountTypes.SingleAccount) {
this.filterAccountIds[account.id] = !this.filterAccountIds[account.id];
}
}
@@ -325,12 +337,12 @@ export default {
getCollapseStates() {
const collapseStates = {};
for (let categoryType in this.$constants.account.allCategories) {
if (!Object.prototype.hasOwnProperty.call(this.$constants.account.allCategories, categoryType)) {
for (let categoryType in accountConstants.allCategories) {
if (!Object.prototype.hasOwnProperty.call(accountConstants.allCategories, categoryType)) {
continue;
}
const accountCategory = this.$constants.account.allCategories[categoryType];
const accountCategory = accountConstants.allCategories[categoryType];
collapseStates[accountCategory.id] = {
opened: true
@@ -123,6 +123,14 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useStatisticsStore } from '@/stores/statistics.js';
import categoryConstants from '@/consts/category.js';
import { copyObjectTo } from '@/lib/common.js';
import { allVisibleTransactionCategories } from '@/lib/category.js';
export default {
props: [
'f7route',
@@ -141,6 +149,8 @@ export default {
}
},
computed: {
...mapStores(useTransactionCategoriesStore, useStatisticsStore),
title() {
if (this.modifyDefault) {
return 'Default Transaction Category Filter';
@@ -156,7 +166,7 @@ export default {
}
},
allVisibleTransactionCategories() {
return this.$utilities.allVisibleTransactionCategories(this.$store.state.allTransactionCategories);
return allVisibleTransactionCategories(this.transactionCategoriesStore.allTransactionCategories);
},
hasAnyAvailableCategory() {
for (let type in this.allVisibleTransactionCategories) {
@@ -194,26 +204,26 @@ export default {
self.modifyDefault = !!query.modifyDefault;
self.$store.dispatch('loadAllCategories', {
self.transactionCategoriesStore.loadAllCategories({
force: false
}).then(() => {
self.loading = false;
const allCategoryIds = {};
for (let categoryId in self.$store.state.allTransactionCategoriesMap) {
if (!Object.prototype.hasOwnProperty.call(self.$store.state.allTransactionCategoriesMap, categoryId)) {
for (let categoryId in self.transactionCategoriesStore.allTransactionCategoriesMap) {
if (!Object.prototype.hasOwnProperty.call(self.transactionCategoriesStore.allTransactionCategoriesMap, categoryId)) {
continue;
}
const category = self.$store.state.allTransactionCategoriesMap[categoryId];
const category = self.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
allCategoryIds[category.id] = false;
}
if (self.modifyDefault) {
self.filterCategoryIds = self.$utilities.copyObjectTo(self.$settings.getStatisticsDefaultTransactionCategoryFilter(), allCategoryIds);
self.filterCategoryIds = copyObjectTo(self.$settings.getStatisticsDefaultTransactionCategoryFilter(), allCategoryIds);
} else {
self.filterCategoryIds = self.$utilities.copyObjectTo(self.$store.state.transactionStatisticsFilter.filterCategoryIds, allCategoryIds);
self.filterCategoryIds = copyObjectTo(self.statisticsStore.transactionStatisticsFilter.filterCategoryIds, allCategoryIds);
}
}).catch(error => {
if (error.processed) {
@@ -247,7 +257,7 @@ export default {
if (self.modifyDefault) {
self.$settings.setStatisticsDefaultTransactionCategoryFilter(filteredCategoryIds);
} else {
self.$store.dispatch('updateTransactionStatisticsFilter', {
self.statisticsStore.updateTransactionStatisticsFilter({
filterCategoryIds: filteredCategoryIds
});
}
@@ -256,7 +266,7 @@ export default {
},
selectCategory(e) {
const categoryId = e.target.value;
const category = this.$store.state.allTransactionCategoriesMap[categoryId];
const category = this.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
if (!category) {
return;
@@ -266,7 +276,7 @@ export default {
},
selectSubCategories(e) {
const categoryId = e.target.value;
const category = this.$store.state.allTransactionCategoriesMap[categoryId];
const category = this.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
if (!category || !category.subCategories || !category.subCategories.length) {
return;
@@ -283,7 +293,7 @@ export default {
continue;
}
const category = this.$store.state.allTransactionCategoriesMap[categoryId];
const category = this.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
if (category) {
this.filterCategoryIds[category.id] = false;
@@ -296,7 +306,7 @@ export default {
continue;
}
const category = this.$store.state.allTransactionCategoriesMap[categoryId];
const category = this.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
if (category) {
this.filterCategoryIds[category.id] = true;
@@ -309,7 +319,7 @@ export default {
continue;
}
const category = this.$store.state.allTransactionCategoriesMap[categoryId];
const category = this.transactionCategoriesStore.allTransactionCategoriesMap[categoryId];
if (category) {
this.filterCategoryIds[category.id] = !this.filterCategoryIds[category.id];
@@ -318,11 +328,11 @@ export default {
},
getCategoryTypeName(categoryType) {
switch (categoryType) {
case this.$constants.category.allCategoryTypes.Income.toString():
case categoryConstants.allCategoryTypes.Income.toString():
return this.$t('Income Categories');
case this.$constants.category.allCategoryTypes.Expense.toString():
case categoryConstants.allCategoryTypes.Expense.toString():
return this.$t('Expense Categories');
case this.$constants.category.allCategoryTypes.Transfer.toString():
case categoryConstants.allCategoryTypes.Transfer.toString():
return this.$t('Transfer Categories');
default:
return this.$t('Transaction Categories');
@@ -356,12 +366,12 @@ export default {
getCollapseStates() {
const collapseStates = {};
for (let categoryTypeField in this.$constants.category.allCategoryTypes) {
if (!Object.prototype.hasOwnProperty.call(this.$constants.category.allCategoryTypes, categoryTypeField)) {
for (let categoryTypeField in categoryConstants.allCategoryTypes) {
if (!Object.prototype.hasOwnProperty.call(categoryConstants.allCategoryTypes, categoryTypeField)) {
continue;
}
const categoryType = this.$constants.category.allCategoryTypes[categoryTypeField];
const categoryType = categoryConstants.allCategoryTypes[categoryTypeField];
collapseStates[categoryType] = {
opened: true
+14 -8
View File
@@ -7,8 +7,8 @@
:title="$t('Default Chart Type')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Chart Type'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), popupCloseLinkText: $t('Done') }">
<select v-model="defaultChartType">
<option :value="$constants.statistics.allChartTypes.Pie">{{ $t('Pie Chart') }}</option>
<option :value="$constants.statistics.allChartTypes.Bar">{{ $t('Bar Chart') }}</option>
<option :value="allChartTypes.Pie">{{ $t('Pie Chart') }}</option>
<option :value="allChartTypes.Bar">{{ $t('Bar Chart') }}</option>
</select>
</f7-list-item>
@@ -50,25 +50,31 @@
</template>
<script>
import datetimeConstants from '@/consts/datetime.js';
import statisticsConstants from '@/consts/statistics.js';
export default {
computed: {
allChartTypes() {
return statisticsConstants.allChartTypes;
},
allChartDataTypes() {
return this.$constants.statistics.allChartDataTypes;
return statisticsConstants.allChartDataTypes;
},
allSortingTypes() {
return this.$constants.statistics.allSortingTypes;
return statisticsConstants.allSortingTypes;
},
allDateRanges() {
const allDateRanges = [];
for (let dateRangeField in this.$constants.datetime.allDateRanges) {
if (!Object.prototype.hasOwnProperty.call(this.$constants.datetime.allDateRanges, dateRangeField)) {
for (let dateRangeField in datetimeConstants.allDateRanges) {
if (!Object.prototype.hasOwnProperty.call(datetimeConstants.allDateRanges, dateRangeField)) {
continue;
}
const dateRangeType = this.$constants.datetime.allDateRanges[dateRangeField];
const dateRangeType = datetimeConstants.allDateRanges[dateRangeField];
if (dateRangeType.type !== this.$constants.datetime.allDateRanges.Custom.type) {
if (dateRangeType.type !== datetimeConstants.allDateRanges.Custom.type) {
allDateRanges.push(dateRangeType);
}
}
+84 -53
View File
@@ -27,7 +27,7 @@
</f7-list>
</f7-popover>
<f7-card v-if="query.chartType === $constants.statistics.allChartTypes.Pie">
<f7-card v-if="query.chartType === allChartTypes.Pie">
<f7-card-header class="no-border display-block">
<div class="statistics-chart-header full-line text-align-right">
<span style="margin-right: 4px;">{{ $t('Sort By') }}</span>
@@ -76,7 +76,7 @@
</f7-card-content>
</f7-card>
<f7-card v-else-if="query.chartType === $constants.statistics.allChartTypes.Bar">
<f7-card v-else-if="query.chartType === allChartTypes.Bar">
<f7-card-header class="no-border display-block">
<div class="statistics-chart-header display-flex full-line justify-content-space-between">
<div>
@@ -150,7 +150,7 @@
<template #title>
<div class="statistics-list-item-text">
<span>{{ item.name }}</span>
<small class="statistics-percent" v-if="item.percent >= 0">{{ $utilities.formatPercent(item.percent, 2, '&lt;0.01') }}</small>
<small class="statistics-percent" v-if="item.percent >= 0">{{ getDisplayPercent(item.percent, 2, '&lt;0.01') }}</small>
</div>
</template>
@@ -195,11 +195,11 @@
<f7-link :class="{ 'disabled': query.dateType === allDateRanges.All.type || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type }" @click="shiftDateRange(query.startTime, query.endTime, 1)">
<f7-icon f7="arrow_right_square"></f7-icon>
</f7-link>
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType($constants.statistics.allChartTypes.Pie)">
<span :class="{ 'tabbar-item-changed': query.chartType === $constants.statistics.allChartTypes.Pie }">{{ $t('Pie Chart') }}</span>
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType(allChartTypes.Pie)">
<span :class="{ 'tabbar-item-changed': query.chartType === allChartTypes.Pie }">{{ $t('Pie Chart') }}</span>
</f7-link>
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType($constants.statistics.allChartTypes.Bar)">
<span :class="{ 'tabbar-item-changed': query.chartType === $constants.statistics.allChartTypes.Bar }">{{ $t('Bar Chart') }}</span>
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType(allChartTypes.Bar)">
<span :class="{ 'tabbar-item-changed': query.chartType === allChartTypes.Bar }">{{ $t('Bar Chart') }}</span>
</f7-link>
</f7-toolbar>
@@ -217,10 +217,10 @@
</template>
<template #footer>
<div v-if="dateRange.type === allDateRanges.Custom.type && query.dateType === allDateRanges.Custom.type && query.startTime && query.endTime">
<span>{{ $utilities.formatUnixTime(query.startTime, $locale.getLongDateTimeFormat()) }}</span>
<span>{{ queryStartTime }}</span>
<span>&nbsp;-&nbsp;</span>
<br/>
<span>{{ $utilities.formatUnixTime(query.endTime, $locale.getLongDateTimeFormat()) }}</span>
<span>{{ queryEndTime }}</span>
</div>
</template>
</f7-list-item>
@@ -250,6 +250,24 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useStatisticsStore } from '@/stores/statistics.js';
import datetimeConstants from '@/consts/datetime.js';
import statisticsConstants from '@/consts/statistics.js';
import { getNameByKeyValue, limitText, formatPercent } from '@/lib/common.js'
import {
parseDateFromUnixTime,
getYear,
getShiftedDateRange,
getDateRangeByDateType,
isDateRangeMatchFullYears,
isDateRangeMatchFullMonths
} from '@/lib/datetime.js';
export default {
props: [
'f7router'
@@ -269,31 +287,41 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useAccountsStore, useTransactionCategoriesStore, useStatisticsStore),
defaultCurrency() {
return this.$store.getters.currentUserDefaultCurrency;
return this.userStore.currentUserDefaultCurrency;
},
firstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
return this.userStore.currentUserFirstDayOfWeek;
},
query() {
return this.$store.state.transactionStatisticsFilter;
return this.statisticsStore.transactionStatisticsFilter;
},
queryChartDataTypeName() {
const queryChartDataTypeName = this.$utilities.getNameByKeyValue(this.allChartDataTypes, this.query.chartDataType, 'type', 'name', 'Statistics');
const queryChartDataTypeName = getNameByKeyValue(this.allChartDataTypes, this.query.chartDataType, 'type', 'name', 'Statistics');
return this.$t(queryChartDataTypeName);
},
querySortingTypeName() {
const querySortingTypeName = this.$utilities.getNameByKeyValue(this.allSortingTypes, this.query.sortingType, 'type', 'name', 'System Default');
const querySortingTypeName = getNameByKeyValue(this.allSortingTypes, this.query.sortingType, 'type', 'name', 'System Default');
return this.$t(querySortingTypeName);
},
queryStartTime() {
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.startTime);
},
queryEndTime() {
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.endTime);
},
allChartTypes() {
return statisticsConstants.allChartTypes;
},
allChartDataTypes() {
return this.$constants.statistics.allChartDataTypes;
return statisticsConstants.allChartDataTypes;
},
allSortingTypes() {
return this.$constants.statistics.allSortingTypes;
return statisticsConstants.allSortingTypes;
},
allDateRanges() {
return this.$constants.datetime.allDateRanges;
return datetimeConstants.allDateRanges;
},
totalAmountName() {
if (this.query.chartDataType === this.allChartDataTypes.IncomeByAccount.type
@@ -325,10 +353,10 @@ export default {
self.query.chartDataType === self.allChartDataTypes.IncomeByAccount.type ||
self.query.chartDataType === self.allChartDataTypes.IncomeByPrimaryCategory.type ||
self.query.chartDataType === self.allChartDataTypes.IncomeBySecondaryCategory.type) {
combinedData = this.$store.getters.statisticsItemsByTransactionStatisticsData;
combinedData = this.statisticsStore.statisticsItemsByTransactionStatisticsData;
} else if (self.query.chartDataType === self.allChartDataTypes.AccountTotalAssets.type ||
self.query.chartDataType === self.allChartDataTypes.AccountTotalLiabilities.type) {
combinedData = this.$store.getters.statisticsItemsByAccountsData;
combinedData = this.statisticsStore.statisticsItemsByAccountsData;
}
const allStatisticsItems = [];
@@ -405,31 +433,31 @@ export default {
let defaultChartType = self.$settings.getStatisticsDefaultChartType();
if (defaultChartType !== self.$constants.statistics.allChartTypes.Pie && defaultChartType !== self.$constants.statistics.allChartTypes.Bar) {
defaultChartType = self.$constants.statistics.defaultChartType;
if (defaultChartType !== self.allChartTypes.Pie && defaultChartType !== self.allChartTypes.Bar) {
defaultChartType = statisticsConstants.defaultChartType;
}
let defaultChartDataType = self.$settings.getStatisticsDefaultChartDataType();
if (defaultChartDataType < self.allChartDataTypes.ExpenseByAccount.type || defaultChartDataType > self.allChartDataTypes.AccountTotalLiabilities.type) {
defaultChartDataType = self.$constants.statistics.defaultChartDataType;
defaultChartDataType = statisticsConstants.defaultChartDataType;
}
let defaultDateRange = self.$settings.getStatisticsDefaultDateRange();
if (defaultDateRange < self.allDateRanges.All.type || defaultDateRange >= self.allDateRanges.Custom.type) {
defaultDateRange = self.$constants.statistics.defaultDataRangeType;
defaultDateRange = statisticsConstants.defaultDataRangeType;
}
let defaultSortType = self.$settings.getStatisticsSortingType();
if (defaultSortType < self.allSortingTypes.Amount.type || defaultSortType > self.allSortingTypes.Name.type) {
defaultSortType = self.$constants.statistics.defaultSortingType;
defaultSortType = statisticsConstants.defaultSortingType;
}
const dateRange = self.$utilities.getDateRangeByDateType(defaultDateRange, self.firstDayOfWeek);
const dateRange = getDateRangeByDateType(defaultDateRange, self.firstDayOfWeek);
self.$store.dispatch('initTransactionStatisticsFilter', {
self.statisticsStore.initTransactionStatisticsFilter({
dateType: dateRange ? dateRange.dateType : undefined,
startTime: dateRange ? dateRange.minTime : undefined,
endTime: dateRange ? dateRange.maxTime : undefined,
@@ -441,10 +469,10 @@ export default {
});
Promise.all([
self.$store.dispatch('loadAllAccounts', { force: false }),
self.$store.dispatch('loadAllCategories', { force: false })
self.accountsStore.loadAllAccounts({ force: false }),
self.transactionCategoriesStore.loadAllCategories({ force: false })
]).then(() => {
return self.$store.dispatch('loadTransactionStatistics', {
return self.statisticsStore.loadTransactionStatistics({
defaultCurrency: self.defaultCurrency
});
}).then(() => {
@@ -460,7 +488,7 @@ export default {
},
methods: {
onPageAfterIn() {
if (this.$store.state.transactionStatisticsStateInvalid && !this.loading) {
if (this.statisticsStore.transactionStatisticsStateInvalid && !this.loading) {
this.reload(null);
}
@@ -476,12 +504,12 @@ export default {
self.query.chartDataType === self.allChartDataTypes.IncomeByAccount.type ||
self.query.chartDataType === self.allChartDataTypes.IncomeByPrimaryCategory.type ||
self.query.chartDataType === self.allChartDataTypes.IncomeBySecondaryCategory.type) {
dispatchPromise = self.$store.dispatch('loadTransactionStatistics', {
dispatchPromise = self.statisticsStore.loadTransactionStatistics({
defaultCurrency: self.defaultCurrency
});
} else if (self.query.chartDataType === self.allChartDataTypes.AccountTotalAssets.type ||
self.query.chartDataType === self.allChartDataTypes.AccountTotalLiabilities.type) {
dispatchPromise = self.$store.dispatch('loadAllAccounts', {
dispatchPromise = self.accountsStore.loadAllAccounts({
force: true
});
}
@@ -503,12 +531,12 @@ export default {
}
},
setChartType(chartType) {
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
chartType: chartType
});
},
setChartDataType(chartDataType) {
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
chartDataType: chartDataType
});
this.showChartDataTypePopover = false;
@@ -519,7 +547,7 @@ export default {
return;
}
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
sortingType: sortingType
});
@@ -535,13 +563,13 @@ export default {
return;
}
const dateRange = this.$utilities.getDateRangeByDateType(dateType, this.firstDayOfWeek);
const dateRange = getDateRangeByDateType(dateType, this.firstDayOfWeek);
if (!dateRange) {
return;
}
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
dateType: dateRange.dateType,
startTime: dateRange.minTime,
endTime: dateRange.maxTime
@@ -555,7 +583,7 @@ export default {
return;
}
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
dateType: this.allDateRanges.Custom.type,
startTime: startTime,
endTime: endTime
@@ -570,7 +598,7 @@ export default {
return;
}
const newDateRange = this.$utilities.getShiftedDateRange(startTime, endTime, scale);
const newDateRange = getShiftedDateRange(startTime, endTime, scale);
let newDateType = this.allDateRanges.Custom.type;
for (let dateRangeField in this.allDateRanges) {
@@ -579,7 +607,7 @@ export default {
}
const dateRangeType = this.allDateRanges[dateRangeField];
const dateRange = this.$utilities.getDateRangeByDateType(dateRangeType.type, this.firstDayOfWeek);
const dateRange = getDateRangeByDateType(dateRangeType.type, this.firstDayOfWeek);
if (dateRange && dateRange.minTime === newDateRange.minTime && dateRange.maxTime === newDateRange.maxTime) {
newDateType = dateRangeType.type;
@@ -587,7 +615,7 @@ export default {
}
}
this.$store.dispatch('updateTransactionStatisticsFilter', {
this.statisticsStore.updateTransactionStatisticsFilter({
dateType: newDateType,
startTime: newDateRange.minTime,
endTime: newDateRange.maxTime
@@ -617,30 +645,30 @@ export default {
}
}
if (this.$utilities.isDateRangeMatchFullYears(query.startTime, query.endTime)) {
const displayStartTime = this.$utilities.formatUnixTime(query.startTime, this.$locale.getShortYearFormat());
const displayEndTime = this.$utilities.formatUnixTime(query.endTime, this.$locale.getShortYearFormat());
if (isDateRangeMatchFullYears(query.startTime, query.endTime)) {
const displayStartTime = this.$locale.formatUnixTimeToShortYear(this.userStore, query.startTime);
const displayEndTime = this.$locale.formatUnixTimeToShortYear(this.userStore, query.endTime);
return displayStartTime !== displayEndTime ? `${displayStartTime} ~ ${displayEndTime}` : displayStartTime;
}
if (this.$utilities.isDateRangeMatchFullMonths(query.startTime, query.endTime)) {
const displayStartTime = this.$utilities.formatUnixTime(query.startTime, this.$locale.getShortYearMonthFormat());
const displayEndTime = this.$utilities.formatUnixTime(query.endTime, this.$locale.getShortYearMonthFormat());
if (isDateRangeMatchFullMonths(query.startTime, query.endTime)) {
const displayStartTime = this.$locale.formatUnixTimeToShortYearMonth(this.userStore, query.startTime);
const displayEndTime = this.$locale.formatUnixTimeToShortYearMonth(this.userStore, query.endTime);
return displayStartTime !== displayEndTime ? `${displayStartTime} ~ ${displayEndTime}` : displayStartTime;
}
const startTimeYear = this.$utilities.getYear(this.$utilities.parseDateFromUnixTime(query.startTime));
const endTimeYear = this.$utilities.getYear(this.$utilities.parseDateFromUnixTime(query.endTime));
const startTimeYear = getYear(parseDateFromUnixTime(query.startTime));
const endTimeYear = getYear(parseDateFromUnixTime(query.endTime));
const displayStartTime = this.$utilities.formatUnixTime(query.startTime, this.$locale.getShortDateFormat());
const displayEndTime = this.$utilities.formatUnixTime(query.endTime, this.$locale.getShortDateFormat());
const displayStartTime = this.$locale.formatUnixTimeToShortDate(this.userStore, query.startTime);
const displayEndTime = this.$locale.formatUnixTimeToShortDate(this.userStore, query.endTime);
if (displayStartTime === displayEndTime) {
return displayStartTime;
} else if (startTimeYear === endTimeYear) {
const displayShortEndTime = this.$utilities.formatUnixTime(query.endTime, this.$locale.getShortMonthDayFormat());
const displayShortEndTime = this.$locale.formatUnixTimeToShortMonthDay(this.userStore, query.endTime);
return `${displayStartTime} ~ ${displayShortEndTime}`;
}
@@ -690,11 +718,14 @@ export default {
}
if (textLimit) {
this.$utilities.limitText(amount, textLimit);
return limitText(amount, textLimit);
}
return amount;
},
getDisplayPercent(value, precision, lowPrecisionValue) {
return formatPercent(value, precision, lowPrecisionValue);
},
getItemLinkUrl(item) {
const querys = [];
+16 -9
View File
@@ -144,6 +144,11 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTransactionTagsStore } from '@/stores/transactionTag.js';
import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7router'
@@ -168,8 +173,10 @@ export default {
};
},
computed: {
...mapStores(useTransactionTagsStore),
tags() {
return this.$store.state.allTransactionTags;
return this.transactionTagsStore.allTransactionTags;
},
firstShowingId() {
for (let i = 0; i < this.tags.length; i++) {
@@ -207,7 +214,7 @@ export default {
self.loading = true;
self.$store.dispatch('loadAllTags', {
self.transactionTagsStore.loadAllTags({
force: false
}).then(() => {
self.loading = false;
@@ -232,7 +239,7 @@ export default {
const self = this;
self.$store.dispatch('loadAllTags', {
self.transactionTagsStore.loadAllTags({
force: true
}).then(() => {
if (done) {
@@ -272,7 +279,7 @@ export default {
return;
}
self.$store.dispatch('changeTagDisplayOrder', {
self.transactionTagsStore.changeTagDisplayOrder({
tagId: id,
from: event.from,
to: event.to
@@ -294,7 +301,7 @@ export default {
self.displayOrderSaving = true;
self.$showLoading();
self.$store.dispatch('updateTagDisplayOrders').then(() => {
self.transactionTagsStore.updateTagDisplayOrders().then(() => {
self.displayOrderSaving = false;
self.$hideLoading();
@@ -324,7 +331,7 @@ export default {
self.$showLoading();
self.$store.dispatch('saveTag', {
self.transactionTagsStore.saveTag({
tag: tag
}).then(() => {
self.$hideLoading();
@@ -363,7 +370,7 @@ export default {
self.$showLoading();
self.$store.dispatch('hideTag', {
self.transactionTagsStore.hideTag({
tag: tag,
hidden: hidden
}).then(() => {
@@ -394,10 +401,10 @@ export default {
self.tagToDelete = null;
self.$showLoading();
self.$store.dispatch('deleteTag', {
self.transactionTagsStore.deleteTag({
tag: tag,
beforeResolve: (done) => {
self.$ui.onSwipeoutDeleted(self.getTagDomId(tag), done);
onSwipeoutDeleted(self.getTagDomId(tag), done);
}
}).then(() => {
self.$hideLoading();
+179 -106
View File
@@ -10,9 +10,9 @@
<f7-subnavbar>
<f7-segmented strong :class="{ 'readonly': mode !== 'add' }">
<f7-button :text="$t('Expense')" :active="transaction.type === $constants.transaction.allTransactionTypes.Expense" @click="transaction.type = $constants.transaction.allTransactionTypes.Expense"></f7-button>
<f7-button :text="$t('Income')" :active="transaction.type === $constants.transaction.allTransactionTypes.Income" @click="transaction.type = $constants.transaction.allTransactionTypes.Income"></f7-button>
<f7-button :text="$t('Transfer')" :active="transaction.type === $constants.transaction.allTransactionTypes.Transfer" @click="transaction.type = $constants.transaction.allTransactionTypes.Transfer"></f7-button>
<f7-button :text="$t('Expense')" :active="transaction.type === allTransactionTypes.Expense" @click="transaction.type = allTransactionTypes.Expense"></f7-button>
<f7-button :text="$t('Income')" :active="transaction.type === allTransactionTypes.Income" @click="transaction.type = allTransactionTypes.Income"></f7-button>
<f7-button :text="$t('Transfer')" :active="transaction.type === allTransactionTypes.Transfer" @click="transaction.type = allTransactionTypes.Transfer"></f7-button>
</f7-segmented>
</f7-subnavbar>
</f7-navbar>
@@ -43,14 +43,14 @@
<f7-list-item
class="transaction-edit-amount"
link="#" no-chevron
:class="{ 'readonly': mode === 'view', 'color-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'color-red': transaction.type === $constants.transaction.allTransactionTypes.Income }"
:class="{ 'readonly': mode === 'view', 'color-teal': transaction.type === allTransactionTypes.Expense, 'color-red': transaction.type === allTransactionTypes.Income }"
:style="{ fontSize: sourceAmountFontSize + 'px' }"
:header="$t(sourceAmountName)"
:title="getDisplayAmount(transaction.sourceAmount, transaction.hideAmount)"
@click="showSourceAmountSheet = true"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
<number-pad-sheet :min-value="allowedMinAmount"
:max-value="allowedMaxAmount"
v-model:show="showSourceAmountSheet"
v-model="transaction.sourceAmount"
></number-pad-sheet>
@@ -64,10 +64,10 @@
:header="$t('Transfer In Amount')"
:title="getDisplayAmount(transaction.destinationAmount, transaction.hideAmount)"
@click="showDestinationAmountSheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
v-if="transaction.type === allTransactionTypes.Transfer"
>
<number-pad-sheet :min-value="$constants.transaction.minAmount"
:max-value="$constants.transaction.maxAmount"
<number-pad-sheet :min-value="allowedMinAmount"
:max-value="allowedMaxAmount"
v-model:show="showDestinationAmountSheet"
v-model="transaction.destinationAmount"
></number-pad-sheet>
@@ -80,13 +80,13 @@
:class="{ 'disabled': !hasAvailableExpenseCategories, 'readonly': mode === 'view' }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Expense"
v-if="transaction.type === allTransactionTypes.Expense"
>
<template #title>
<div class="list-item-custom-title" v-if="hasAvailableExpenseCategories">
<span>{{ getPrimaryCategoryName(transaction.expenseCategory, allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
<span>{{ getPrimaryCategoryName(transaction.expenseCategory, allCategories[allCategoryTypes.Expense]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.expenseCategory, allCategories[$constants.category.allCategoryTypes.Expense]) }}</span>
<span>{{ getSecondaryCategoryName(transaction.expenseCategory, allCategories[allCategoryTypes.Expense]) }}</span>
</div>
<div class="list-item-custom-title" v-else-if="!hasAvailableExpenseCategories">
<span>{{ $t('None') }}</span>
@@ -97,7 +97,7 @@
primary-sub-items-field="subCategories"
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
:items="allCategories[$constants.category.allCategoryTypes.Expense]"
:items="allCategories[allCategoryTypes.Expense]"
v-model:show="showCategorySheet"
v-model="transaction.expenseCategory">
</tree-view-selection-sheet>
@@ -110,13 +110,13 @@
:class="{ 'disabled': !hasAvailableIncomeCategories, 'readonly': mode === 'view' }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Income"
v-if="transaction.type === allTransactionTypes.Income"
>
<template #title>
<div class="list-item-custom-title" v-if="hasAvailableIncomeCategories">
<span>{{ getPrimaryCategoryName(transaction.incomeCategory, allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
<span>{{ getPrimaryCategoryName(transaction.incomeCategory, allCategories[allCategoryTypes.Income]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.incomeCategory, allCategories[$constants.category.allCategoryTypes.Income]) }}</span>
<span>{{ getSecondaryCategoryName(transaction.incomeCategory, allCategories[allCategoryTypes.Income]) }}</span>
</div>
<div class="list-item-custom-title" v-else-if="!hasAvailableIncomeCategories">
<span>{{ $t('None') }}</span>
@@ -127,7 +127,7 @@
primary-sub-items-field="subCategories"
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
:items="allCategories[$constants.category.allCategoryTypes.Income]"
:items="allCategories[allCategoryTypes.Income]"
v-model:show="showCategorySheet"
v-model="transaction.incomeCategory">
</tree-view-selection-sheet>
@@ -140,13 +140,13 @@
:class="{ 'disabled': !hasAvailableTransferCategories, 'readonly': mode === 'view' }"
:header="$t('Category')"
@click="showCategorySheet = true"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
v-if="transaction.type === allTransactionTypes.Transfer"
>
<template #title>
<div class="list-item-custom-title" v-if="hasAvailableTransferCategories">
<span>{{ getPrimaryCategoryName(transaction.transferCategory, allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
<span>{{ getPrimaryCategoryName(transaction.transferCategory, allCategories[allCategoryTypes.Transfer]) }}</span>
<f7-icon class="category-separate-icon" f7="chevron_right"></f7-icon>
<span>{{ getSecondaryCategoryName(transaction.transferCategory, allCategories[$constants.category.allCategoryTypes.Transfer]) }}</span>
<span>{{ getSecondaryCategoryName(transaction.transferCategory, allCategories[allCategoryTypes.Transfer]) }}</span>
</div>
<div class="list-item-custom-title" v-else-if="!hasAvailableTransferCategories">
<span>{{ $t('None') }}</span>
@@ -157,7 +157,7 @@
primary-sub-items-field="subCategories"
secondary-key-field="id" secondary-value-field="id" secondary-title-field="name"
secondary-icon-field="icon" secondary-icon-type="category" secondary-color-field="color"
:items="allCategories[$constants.category.allCategoryTypes.Transfer]"
:items="allCategories[allCategoryTypes.Transfer]"
v-model:show="showCategorySheet"
v-model="transaction.transferCategory">
</tree-view-selection-sheet>
@@ -167,8 +167,8 @@
class="list-item-with-header-and-title"
link="#" no-chevron
:class="{ 'disabled': !allVisibleAccounts.length, 'readonly': mode === 'view' }"
:header="$t(sourceAccountName)"
:title="transaction.sourceAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.sourceAccountId, 'id', 'name') : $t('None')"
:header="$t(sourceAccountTitle)"
:title="sourceAccountName"
@click="showSourceAccountSheet = true"
>
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
@@ -190,8 +190,8 @@
link="#" no-chevron
:class="{ 'disabled': !allVisibleAccounts.length, 'readonly': mode === 'view' }"
:header="$t('Destination Account')"
:title="transaction.destinationAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.destinationAccountId, 'id', 'name') : $t('None')"
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
:title="destinationAccountName"
v-if="transaction.type === allTransactionTypes.Transfer"
@click="showDestinationAccountSheet = true"
>
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
@@ -213,7 +213,7 @@
link="#" no-chevron
:class="{ 'readonly': mode === 'view' }"
:header="$t('Transaction Time')"
:title="$utilities.formatUnixTime($utilities.getActualUnixTimeForStore(transaction.time, $utilities.getTimezoneOffsetMinutes(), $utilities.getBrowserTimezoneOffsetMinutes()), $locale.getLongDateTimeFormat())"
:title="transactionDisplayTime"
@click="showTransactionDateTimeSheet = true"
>
<date-time-selection-sheet v-model:show="showTransactionDateTimeSheet"
@@ -234,8 +234,8 @@
</select>
<template #title>
<f7-block class="list-item-custom-title no-padding no-margin">
<span>{{ `(UTC${$utilities.getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)})` }}</span>
<span class="transaction-edit-timezone-name" v-if="transaction.timeZone || transaction.timeZone === ''">{{ $utilities.getNameByKeyValue(allTimezones, transaction.timeZone, 'name', 'displayName') }}</span>
<span>{{ `(${transactionDisplayTimezone})` }}</span>
<span class="transaction-edit-timezone-name" v-if="transaction.timeZone || transaction.timeZone === ''">{{ transactionDisplayTimezoneName }}</span>
</f7-block>
</template>
</f7-list-item>
@@ -273,7 +273,7 @@
<template #footer>
<f7-block class="margin-top-half no-padding no-margin" v-if="transaction.tagIds && transaction.tagIds.length">
<f7-chip media-bg-color="black" class="transaction-edit-tag"
:text="$utilities.getNameByKeyValue(allTags, tagId, 'id', 'name')"
:text="getTagName(tagId)"
:key="tagId"
v-for="tagId in transaction.tagIds">
<template #media>
@@ -332,6 +332,43 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useTransactionTagsStore } from '@/stores/transactionTag.js';
import { useTransactionsStore } from '@/stores/transaction.js';
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import categoryConstants from '@/consts/category.js';
import transactionConstants from '@/consts/transaction.js';
import logger from '@/lib/logger.js';
import {
isNumber,
copyObjectTo,
getNameByKeyValue
} from '@/lib/common.js';
import {
getCurrentUnixTime,
getTimezoneOffsetMinutes,
getBrowserTimezoneOffsetMinutes,
getUtcOffsetByUtcOffsetMinutes,
getDummyUnixTimeForLocalUsage,
getActualUnixTimeForStore
} from '@/lib/datetime.js';
import {
stringCurrencyToNumeric
} from '@/lib/currency.js';
import {
getCategorizedAccounts,
getAllFilteredAccountsBalance
} from '@/lib/account.js';
import {
categoryTypeToTransactionType,
getTransactionPrimaryCategoryName,
getTransactionSecondaryCategoryName
} from '@/lib/category.js';
export default {
props: [
'f7route',
@@ -340,15 +377,15 @@ export default {
data() {
const self = this;
const query = self.f7route.query;
const now = self.$utilities.getCurrentUnixTime();
const now = getCurrentUnixTime();
const currentTimezone = self.$locale.getTimezone();
let defaultType = self.$constants.transaction.allTransactionTypes.Expense;
let defaultType = transactionConstants.allTransactionTypes.Expense;
if (query.type === self.$constants.transaction.allTransactionTypes.Income.toString()) {
defaultType = self.$constants.transaction.allTransactionTypes.Income;
} else if (query.type === self.$constants.transaction.allTransactionTypes.Transfer.toString()) {
defaultType = self.$constants.transaction.allTransactionTypes.Transfer;
if (query.type === transactionConstants.allTransactionTypes.Income.toString()) {
defaultType = transactionConstants.allTransactionTypes.Income;
} else if (query.type === transactionConstants.allTransactionTypes.Transfer.toString()) {
defaultType = transactionConstants.allTransactionTypes.Transfer;
}
return {
@@ -358,7 +395,7 @@ export default {
type: defaultType,
time: now,
timeZone: currentTimezone,
utcOffset: self.$utilities.getTimezoneOffsetMinutes(currentTimezone),
utcOffset: getTimezoneOffsetMinutes(currentTimezone),
expenseCategory: '',
incomeCategory: '',
transferCategory: '',
@@ -390,6 +427,7 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useTransactionsStore, useExchangeRatesStore),
title() {
if (this.mode === 'add') {
return 'Add Transaction';
@@ -407,48 +445,51 @@ export default {
}
},
sourceAmountName() {
if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Expense) {
if (this.transaction.type === this.allTransactionTypes.Expense) {
return 'Expense Amount';
} else if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Income) {
} else if (this.transaction.type === this.allTransactionTypes.Income) {
return 'Income Amount';
} else if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Transfer) {
} else if (this.transaction.type === this.allTransactionTypes.Transfer) {
return 'Transfer Out Amount';
} else {
return '';
}
},
sourceAccountName() {
if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Expense || this.transaction.type === this.$constants.transaction.allTransactionTypes.Income) {
sourceAccountTitle() {
if (this.transaction.type === this.allTransactionTypes.Expense || this.transaction.type === this.allTransactionTypes.Income) {
return 'Account';
} else if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Transfer) {
} else if (this.transaction.type === this.allTransactionTypes.Transfer) {
return 'Source Account';
} else {
return '';
}
},
defaultCurrency() {
return this.$store.getters.currentUserDefaultCurrency;
return this.userStore.currentUserDefaultCurrency;
},
defaultAccountId() {
return this.$store.getters.currentUserDefaultAccountId;
return this.userStore.currentUserDefaultAccountId;
},
defaultFirstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
allTransactionTypes() {
return transactionConstants.allTransactionTypes;
},
allCategoryTypes() {
return categoryConstants.allCategoryTypes;
},
allTimezones() {
return this.$locale.getAllTimezones(true);
},
allAccounts() {
return this.$store.getters.allPlainAccounts;
return this.accountsStore.allPlainAccounts;
},
allVisibleAccounts() {
return this.$store.getters.allVisiblePlainAccounts;
return this.accountsStore.allVisiblePlainAccounts;
},
allAccountsMap() {
return this.$store.state.allAccountsMap;
return this.accountsStore.allAccountsMap;
},
categorizedAccounts() {
const categorizedAccounts = this.$utilities.copyObjectTo(this.$utilities.getCategorizedAccounts(this.allVisibleAccounts), {});
const categorizedAccounts = copyObjectTo(getCategorizedAccounts(this.allVisibleAccounts), {});
for (let category in categorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(categorizedAccounts, category)) {
@@ -472,7 +513,7 @@ export default {
}
if (this.showAccountBalance) {
const accountsBalance = this.$utilities.getAllFilteredAccountsBalance(categorizedAccounts, account => account.category === accountCategory.category);
const accountsBalance = getAllFilteredAccountsBalance(categorizedAccounts, account => account.category === accountCategory.category);
let totalBalance = 0;
let hasUnCalculatedAmount = false;
@@ -484,9 +525,9 @@ export default {
totalBalance -= accountsBalance[i].balance;
}
} else {
const balance = this.$store.getters.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
const balance = this.exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, this.defaultCurrency);
if (!this.$utilities.isNumber(balance)) {
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
@@ -512,38 +553,61 @@ export default {
return categorizedAccounts;
},
allCategories() {
return this.$store.state.allTransactionCategories;
return this.transactionCategoriesStore.allTransactionCategories;
},
allCategoriesMap() {
return this.$store.state.allTransactionCategoriesMap;
return this.transactionCategoriesStore.allTransactionCategoriesMap;
},
allTags() {
return this.$store.state.allTransactionTags;
return this.transactionTagsStore.allTransactionTags;
},
hasAvailableExpenseCategories() {
if (!this.allCategories || !this.allCategories[this.$constants.category.allCategoryTypes.Expense] || !this.allCategories[this.$constants.category.allCategoryTypes.Expense].length) {
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
return false;
}
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.$constants.category.allCategoryTypes.Expense]);
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Expense]);
return firstAvailableCategoryId !== '';
},
hasAvailableIncomeCategories() {
if (!this.allCategories || !this.allCategories[this.$constants.category.allCategoryTypes.Income] || !this.allCategories[this.$constants.category.allCategoryTypes.Income].length) {
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Income] || !this.allCategories[this.allCategoryTypes.Income].length) {
return false;
}
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.$constants.category.allCategoryTypes.Income]);
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Income]);
return firstAvailableCategoryId !== '';
},
hasAvailableTransferCategories() {
if (!this.allCategories || !this.allCategories[this.$constants.category.allCategoryTypes.Transfer] || !this.allCategories[this.$constants.category.allCategoryTypes.Transfer].length) {
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Transfer] || !this.allCategories[this.allCategoryTypes.Transfer].length) {
return false;
}
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.$constants.category.allCategoryTypes.Transfer]);
const firstAvailableCategoryId = this.getFirstAvailableCategoryId(this.allCategories[this.allCategoryTypes.Transfer]);
return firstAvailableCategoryId !== '';
},
sourceAccountName() {
if (this.transaction.sourceAccountId) {
return getNameByKeyValue(this.allAccounts, this.transaction.sourceAccountId, 'id', 'name');
} else {
return this.$t('None');
}
},
destinationAccountName() {
if (this.transaction.destinationAccountId) {
return getNameByKeyValue(this.allAccounts, this.transaction.destinationAccountId, 'id', 'name');
} else {
return this.$t('None');
}
},
transactionDisplayTime() {
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, getActualUnixTimeForStore(this.transaction.time, getTimezoneOffsetMinutes(), getBrowserTimezoneOffsetMinutes()))
},
transactionDisplayTimezone() {
return `UTC${getUtcOffsetByUtcOffsetMinutes(this.transaction.utcOffset)}`;
},
transactionDisplayTimezoneName() {
return getNameByKeyValue(this.allTimezones, this.transaction.timeZone, 'name', 'displayName');
},
sourceAmountFontSize() {
return this.getFontSizeByAmount(this.transaction.sourceAmount);
},
@@ -559,6 +623,12 @@ export default {
return this.$t('No Location');
}
},
allowedMinAmount() {
return transactionConstants.minAmount;
},
allowedMaxAmount() {
return transactionConstants.maxAmount;
},
inputIsEmpty() {
return !!this.inputEmptyProblemMessage;
},
@@ -572,28 +642,28 @@ export default {
return;
}
if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Expense || this.transaction.type === this.$constants.transaction.allTransactionTypes.Income) {
if (this.transaction.type === this.allTransactionTypes.Expense || this.transaction.type === this.allTransactionTypes.Income) {
this.transaction.destinationAmount = newValue;
} else if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Transfer) {
} else if (this.transaction.type === this.allTransactionTypes.Transfer) {
const sourceAccount = this.allAccountsMap[this.transaction.sourceAccountId]
const destinationAccount = this.allAccountsMap[this.transaction.destinationAccountId]
if (sourceAccount && destinationAccount && sourceAccount.currency !== destinationAccount.currency) {
const exchangedOldValue = this.$store.getters.getExchangedAmount(oldValue, sourceAccount.currency, destinationAccount.currency);
const exchangedNewValue = this.$store.getters.getExchangedAmount(newValue, sourceAccount.currency, destinationAccount.currency);
const exchangedOldValue = this.exchangeRatesStore.getExchangedAmount(oldValue, sourceAccount.currency, destinationAccount.currency);
const exchangedNewValue = this.exchangeRatesStore.getExchangedAmount(newValue, sourceAccount.currency, destinationAccount.currency);
if (this.$utilities.isNumber(exchangedOldValue)) {
if (isNumber(exchangedOldValue)) {
oldValue = Math.floor(exchangedOldValue);
}
if (this.$utilities.isNumber(exchangedNewValue)) {
if (isNumber(exchangedNewValue)) {
newValue = Math.floor(exchangedNewValue);
}
}
if ((!sourceAccount || !destinationAccount || this.transaction.destinationAmount === oldValue) &&
(this.$utilities.stringCurrencyToNumeric(this.$constants.transaction.minAmount) <= newValue &&
newValue <= this.$utilities.stringCurrencyToNumeric(this.$constants.transaction.maxAmount))) {
(stringCurrencyToNumeric(this.allowedMinAmount) <= newValue &&
newValue <= stringCurrencyToNumeric(this.allowedMaxAmount))) {
this.transaction.destinationAmount = newValue;
}
}
@@ -603,7 +673,7 @@ export default {
return;
}
if (this.transaction.type === this.$constants.transaction.allTransactionTypes.Expense || this.transaction.type === this.$constants.transaction.allTransactionTypes.Income) {
if (this.transaction.type === this.allTransactionTypes.Expense || this.transaction.type === this.allTransactionTypes.Income) {
this.transaction.sourceAmount = newValue;
}
},
@@ -629,9 +699,9 @@ export default {
self.loading = true;
const promises = [
self.$store.dispatch('loadAllAccounts', { force: false }),
self.$store.dispatch('loadAllCategories', { force: false }),
self.$store.dispatch('loadAllTags', { force: false })
self.accountsStore.loadAllAccounts({ force: false }),
self.transactionCategoriesStore.loadAllCategories({ force: false }),
self.transactionTagsStore.loadAllTags({ force: false })
];
if (query.id) {
@@ -639,12 +709,12 @@ export default {
self.editTransactionId = query.id;
}
promises.push(self.$store.dispatch('getTransaction', { transactionId: query.id }));
promises.push(self.transactionsStore.getTransaction({ transactionId: query.id }));
}
if (query.type && query.type !== '0' &&
query.type >= self.$constants.transaction.allTransactionTypes.Income &&
query.type <= self.$constants.transaction.allTransactionTypes.Transfer) {
query.type >= self.allTransactionTypes.Income &&
query.type <= self.allTransactionTypes.Transfer) {
self.transaction.type = parseInt(query.type);
}
@@ -657,43 +727,43 @@ export default {
if ((!query.type || query.type === '0') && query.categoryId && query.categoryId !== '0' && self.allCategoriesMap[query.categoryId]) {
const category = self.allCategoriesMap[query.categoryId];
const type = self.$utilities.categoryTypeToTransactionType(category.type);
const type = categoryTypeToTransactionType(category.type);
if (self.$utilities.isNumber(type)) {
if (isNumber(type)) {
self.transaction.type = type;
}
}
if (self.allCategories[self.$constants.category.allCategoryTypes.Expense] &&
self.allCategories[self.$constants.category.allCategoryTypes.Expense].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.$constants.category.allCategoryTypes.Expense], query.categoryId)) {
if (self.allCategories[self.allCategoryTypes.Expense] &&
self.allCategories[self.allCategoryTypes.Expense].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.allCategoryTypes.Expense], query.categoryId)) {
self.transaction.expenseCategory = query.categoryId;
}
if (!self.transaction.expenseCategory) {
self.transaction.expenseCategory = self.getFirstAvailableCategoryId(self.allCategories[self.$constants.category.allCategoryTypes.Expense]);
self.transaction.expenseCategory = self.getFirstAvailableCategoryId(self.allCategories[self.allCategoryTypes.Expense]);
}
}
if (self.allCategories[self.$constants.category.allCategoryTypes.Income] &&
self.allCategories[self.$constants.category.allCategoryTypes.Income].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.$constants.category.allCategoryTypes.Income], query.categoryId)) {
if (self.allCategories[self.allCategoryTypes.Income] &&
self.allCategories[self.allCategoryTypes.Income].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.allCategoryTypes.Income], query.categoryId)) {
self.transaction.incomeCategory = query.categoryId;
}
if (!self.transaction.incomeCategory) {
self.transaction.incomeCategory = self.getFirstAvailableCategoryId(self.allCategories[self.$constants.category.allCategoryTypes.Income]);
self.transaction.incomeCategory = self.getFirstAvailableCategoryId(self.allCategories[self.allCategoryTypes.Income]);
}
}
if (self.allCategories[self.$constants.category.allCategoryTypes.Transfer] &&
self.allCategories[self.$constants.category.allCategoryTypes.Transfer].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.$constants.category.allCategoryTypes.Transfer], query.categoryId)) {
if (self.allCategories[self.allCategoryTypes.Transfer] &&
self.allCategories[self.allCategoryTypes.Transfer].length) {
if (query.categoryId && query.categoryId !== '0' && self.isCategoryIdAvailable(self.allCategories[self.allCategoryTypes.Transfer], query.categoryId)) {
self.transaction.transferCategory = query.categoryId;
}
if (!self.transaction.transferCategory) {
self.transaction.transferCategory = self.getFirstAvailableCategoryId(self.allCategories[self.$constants.category.allCategoryTypes.Transfer]);
self.transaction.transferCategory = self.getFirstAvailableCategoryId(self.allCategories[self.allCategoryTypes.Transfer]);
}
}
@@ -734,18 +804,18 @@ export default {
self.transaction.type = transaction.type;
if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Expense) {
if (self.transaction.type === self.allTransactionTypes.Expense) {
self.transaction.expenseCategory = transaction.categoryId;
} else if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Income) {
} else if (self.transaction.type === self.allTransactionTypes.Income) {
self.transaction.incomeCategory = transaction.categoryId;
} else if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Transfer) {
} else if (self.transaction.type === self.allTransactionTypes.Transfer) {
self.transaction.transferCategory = transaction.categoryId;
}
if (self.mode === 'edit' || self.mode === 'view') {
self.transaction.utcOffset = transaction.utcOffset;
self.transaction.timeZone = null;
self.transaction.time = self.$utilities.getDummyUnixTimeForLocalUsage(transaction.time, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes());
self.transaction.time = getDummyUnixTimeForLocalUsage(transaction.time, self.transaction.utcOffset, getBrowserTimezoneOffsetMinutes());
}
self.transaction.sourceAccountId = transaction.sourceAccountId;
@@ -771,7 +841,7 @@ export default {
self.loading = false;
}).catch(error => {
self.$logger.error('failed to load essential data for editing transaction', error);
logger.error('failed to load essential data for editing transaction', error);
if (error.processed) {
self.loading = false;
@@ -800,7 +870,7 @@ export default {
const submitTransaction = {
type: self.transaction.type,
time: self.$utilities.getActualUnixTimeForStore(self.transaction.time, self.transaction.utcOffset, self.$utilities.getBrowserTimezoneOffsetMinutes()),
time: getActualUnixTimeForStore(self.transaction.time, self.transaction.utcOffset, getBrowserTimezoneOffsetMinutes()),
sourceAccountId: self.transaction.sourceAccountId,
sourceAmount: self.transaction.sourceAmount,
destinationAccountId: '0',
@@ -812,11 +882,11 @@ export default {
utcOffset: self.transaction.utcOffset
};
if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Expense) {
if (self.transaction.type === self.allTransactionTypes.Expense) {
submitTransaction.categoryId = self.transaction.expenseCategory;
} else if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Income) {
} else if (self.transaction.type === self.allTransactionTypes.Income) {
submitTransaction.categoryId = self.transaction.incomeCategory;
} else if (self.transaction.type === self.$constants.transaction.allTransactionTypes.Transfer) {
} else if (self.transaction.type === self.allTransactionTypes.Transfer) {
submitTransaction.categoryId = self.transaction.transferCategory;
submitTransaction.destinationAccountId = self.transaction.destinationAccountId;
submitTransaction.destinationAmount = self.transaction.destinationAmount;
@@ -833,7 +903,7 @@ export default {
self.submitting = true;
self.$showLoading(() => self.submitting);
self.$store.dispatch('saveTransaction', {
self.transactionsStore.saveTransaction({
transaction: submitTransaction,
defaultCurrency: self.defaultCurrency
}).then(() => {
@@ -869,7 +939,7 @@ export default {
const self = this;
if (!self.isSupportGeoLocation) {
self.$logger.warn('this browser does not support geo location');
logger.warn('this browser does not support geo location');
if (forceUpdate) {
self.$toast('Unable to get current position');
@@ -879,7 +949,7 @@ export default {
navigator.geolocation.getCurrentPosition(function (position) {
if (!position || !position.coords) {
self.$logger.error('current position is null');
logger.error('current position is null');
self.geoLocationStatus = 'error';
if (forceUpdate) {
@@ -896,7 +966,7 @@ export default {
longitude: position.coords.longitude
};
}, function (err) {
self.$logger.error('cannot get current position', err);
logger.error('cannot get current position', err);
self.geoLocationStatus = 'error';
if (forceUpdate) {
@@ -953,10 +1023,13 @@ export default {
return this.$locale.getDisplayCurrency(amount);
},
getPrimaryCategoryName(categoryId, allCategories) {
return this.$utilities.getTransactionPrimaryCategoryName(categoryId, allCategories);
return getTransactionPrimaryCategoryName(categoryId, allCategories);
},
getSecondaryCategoryName(categoryId, allCategories) {
return this.$utilities.getTransactionSecondaryCategoryName(categoryId, allCategories);
return getTransactionSecondaryCategoryName(categoryId, allCategories);
},
getTagName(tagId) {
return getNameByKeyValue(this.allTags, tagId, 'id', 'name');
}
}
};
+116 -75
View File
@@ -123,7 +123,7 @@
<f7-list-item>
<template #title>
<small>
<span>{{ $utilities.formatTime(transactionMonthList.yearMonth, $locale.getLongYearMonthFormat()) }}</span>
<span>{{ getDisplayYearMonth(transactionMonthList) }}</span>
</small>
<small class="transaction-amount-statistics" v-if="showTotalAmountInTransactionListPage && transactionMonthList.totalAmount">
<span class="text-color-red">
@@ -144,7 +144,7 @@
<f7-list-item swipeout chevron-center
class="transaction-info"
:id="getTransactionDomId(transaction)"
:link="transaction.type !== $constants.transaction.allTransactionTypes.ModifyBalance ? `/transaction/detail?id=${transaction.id}&type=${transaction.type}` : null"
:link="transaction.type !== allTransactionTypes.ModifyBalance ? `/transaction/detail?id=${transaction.id}&type=${transaction.type}` : null"
:key="transaction.id"
v-for="(transaction, idx) in transactionMonthList.items"
>
@@ -175,20 +175,20 @@
<div class="item-title-row">
<div class="item-title">
<div class="transaction-category-name no-padding">
<span v-if="transaction.type === $constants.transaction.allTransactionTypes.ModifyBalance">
<span v-if="transaction.type === allTransactionTypes.ModifyBalance">
{{ $t('Modify Balance') }}
</span>
<span v-else-if="transaction.type !== $constants.transaction.allTransactionTypes.ModifyBalance && transaction.category">
<span v-else-if="transaction.type !== allTransactionTypes.ModifyBalance && transaction.category">
{{ transaction.category.name }}
</span>
<span v-else-if="transaction.type !== $constants.transaction.allTransactionTypes.ModifyBalance && !transaction.category">
<span v-else-if="transaction.type !== allTransactionTypes.ModifyBalance && !transaction.category">
{{ getTransactionTypeName(transaction.type, 'Transaction') }}
</span>
</div>
</div>
<div class="item-after">
<div class="transaction-amount" v-if="transaction.sourceAccount"
:class="{ 'text-color-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'text-color-red': transaction.type === $constants.transaction.allTransactionTypes.Income }">
:class="{ 'text-color-teal': transaction.type === allTransactionTypes.Expense, 'text-color-red': transaction.type === allTransactionTypes.Income }">
<span v-if="!query.accountId || query.accountId === '0' || (transaction.sourceAccount && (transaction.sourceAccount.id === query.accountId || transaction.sourceAccount.parentId === query.accountId))">{{ getDisplayAmount(transaction.sourceAmount, transaction.sourceAccount.currency, transaction.hideAmount) }}</span>
<span v-else-if="query.accountId && query.accountId !== '0' && transaction.destinationAccount && (transaction.destinationAccount.id === query.accountId || transaction.destinationAccount.parentId === query.accountId)">{{ getDisplayAmount(transaction.destinationAmount, transaction.destinationAccount.currency, transaction.hideAmount) }}</span>
<span v-else></span>
@@ -202,12 +202,12 @@
</div>
<div class="item-footer">
<div class="transaction-footer">
<span>{{ $utilities.formatUnixTime(transaction.time, $locale.getShortTimeFormat(), transaction.utcOffset, currentTimezoneOffsetMinutes) }}</span>
<span v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ `(UTC${$utilities.getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)})` }}</span>
<span>{{ getDisplayTime(transaction) }}</span>
<span v-if="transaction.utcOffset !== currentTimezoneOffsetMinutes">{{ `(${getDisplayTimezone(transaction)})` }}</span>
<span v-if="transaction.sourceAccount">·</span>
<span v-if="transaction.sourceAccount">{{ transaction.sourceAccount.name }}</span>
<span v-if="transaction.sourceAccount && transaction.type === $constants.transaction.allTransactionTypes.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id"></span>
<span v-if="transaction.sourceAccount && transaction.type === $constants.transaction.allTransactionTypes.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id">{{ transaction.destinationAccount.name }}</span>
<span v-if="transaction.sourceAccount && transaction.type === allTransactionTypes.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id"></span>
<span v-if="transaction.sourceAccount && transaction.type === allTransactionTypes.Transfer && transaction.destinationAccount && transaction.sourceAccount.id !== transaction.destinationAccount.id">{{ transaction.destinationAccount.name }}</span>
</div>
</div>
</div>
@@ -216,11 +216,11 @@
<f7-swipeout-actions right>
<f7-swipeout-button color="primary" close
:text="$t('Duplicate')"
v-if="transaction.type !== $constants.transaction.allTransactionTypes.ModifyBalance"
v-if="transaction.type !== allTransactionTypes.ModifyBalance"
@click="duplicate(transaction)"></f7-swipeout-button>
<f7-swipeout-button color="orange" close
:text="$t('Edit')"
v-if="transaction.editable && transaction.type !== $constants.transaction.allTransactionTypes.ModifyBalance"
v-if="transaction.editable && transaction.type !== allTransactionTypes.ModifyBalance"
@click="edit(transaction)"></f7-swipeout-button>
<f7-swipeout-button color="red" class="padding-left padding-right"
v-if="transaction.editable"
@@ -251,11 +251,11 @@
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.dateType === dateRange.type"></f7-icon>
</template>
<template #footer>
<div v-if="dateRange.type === $constants.datetime.allDateRanges.Custom.type && query.dateType === $constants.datetime.allDateRanges.Custom.type && query.minTime && query.maxTime">
<span>{{ $utilities.formatUnixTime(query.minTime, $locale.getLongDateTimeFormat()) }}</span>
<div v-if="dateRange.type === allDateRanges.Custom.type && query.dateType === allDateRanges.Custom.type && query.minTime && query.maxTime">
<span>{{ queryMinTime }}</span>
<span>&nbsp;-&nbsp;</span>
<br/>
<span>{{ $utilities.formatUnixTime(query.maxTime, $locale.getLongDateTimeFormat()) }}</span>
<span>{{ queryMaxTime }}</span>
</div>
</template>
</f7-list-item>
@@ -318,9 +318,9 @@
class="no-margin-vertical"
:key="categoryType"
v-for="(categories, categoryType) in allPrimaryCategories"
v-show="!query.type || $utilities.categoryTypeToTransactionType(parseInt(categoryType)) === query.type"
v-show="!query.type || getTransactionTypeFromCategoryType(categoryType) === query.type"
>
<f7-list-item divider :title="getTransactionTypeName($utilities.categoryTypeToTransactionType(parseInt(categoryType)), 'Type')"></f7-list-item>
<f7-list-item divider :title="getTransactionTypeName(getTransactionTypeFromCategoryType(categoryType), 'Type')"></f7-list-item>
<f7-list-item accordion-item
:title="category.name"
:class="getCategoryListItemCheckedClass(category, query.categoryId)"
@@ -407,6 +407,25 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
import { useTransactionsStore } from '@/stores/transaction.js';
import datetimeConstants from '@/consts/datetime.js';
import currencyConstants from '@/consts/currency.js';
import accountConstants from '@/consts/account.js';
import transactionConstants from '@/consts/transaction.js';
import { getNameByKeyValue } from '@/lib/common.js';
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes,
getDateRangeByDateType
} from '@/lib/datetime.js';
import { categoryTypeToTransactionType, transactionTypeToCategoryType } from '@/lib/category.js';
import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7route',
@@ -430,22 +449,23 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionsStore),
defaultCurrency() {
if (this.query.accountId && this.query.accountId !== '0') {
const account = this.allAccounts[this.query.accountId];
if (account && account.currency && account.currency !== this.$constants.currency.parentAccountCurrencyPlaceholder) {
if (account && account.currency && account.currency !== currencyConstants.parentAccountCurrencyPlaceholder) {
return account.currency;
}
}
return this.$store.getters.currentUserDefaultCurrency;
return this.userStore.currentUserDefaultCurrency;
},
canAddTransaction() {
if (this.query.accountId && this.query.accountId !== '0') {
const account = this.allAccounts[this.query.accountId];
if (account && account.type === this.$constants.account.allAccountTypes.MultiSubAccounts) {
if (account && account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
return false;
}
}
@@ -453,38 +473,13 @@ export default {
return true;
},
currentTimezoneOffsetMinutes() {
return this.$utilities.getTimezoneOffsetMinutes();
return getTimezoneOffsetMinutes();
},
firstDayOfWeek() {
return this.$store.getters.currentUserFirstDayOfWeek;
return this.userStore.currentUserFirstDayOfWeek;
},
query() {
return this.$store.state.transactionsFilter;
},
transactions() {
if (this.loading) {
return [];
}
return this.$store.state.transactions;
},
noTransaction() {
return this.$store.getters.noTransaction;
},
hasMoreTransaction() {
return this.$store.getters.hasMoreTransaction;
},
allAccounts() {
return this.$store.state.allAccountsMap;
},
allCategories() {
return this.$store.state.allTransactionCategoriesMap;
},
allPrimaryCategories() {
return this.$store.state.allTransactionCategories;
},
allDateRanges() {
return this.$constants.datetime.allDateRanges;
return this.transactionsStore.transactionsFilter;
},
queryDateRangeName() {
if (this.query.dateType === this.allDateRanges.All.type) {
@@ -505,24 +500,58 @@ export default {
return this.$t('Date');
},
queryMinTime() {
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.minTime);
},
queryMaxTime() {
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.maxTime);
},
queryTransactionTypeName() {
return this.getTransactionTypeName(this.query.type, 'Type');
},
queryCategoryName() {
return this.$utilities.getNameByKeyValue(this.allCategories, this.query.categoryId, null, 'name', this.$t('Category'));
return getNameByKeyValue(this.allCategories, this.query.categoryId, null, 'name', this.$t('Category'));
},
queryAccountName() {
return this.$utilities.getNameByKeyValue(this.allAccounts, this.query.accountId, null, 'name', this.$t('Account'));
return getNameByKeyValue(this.allAccounts, this.query.accountId, null, 'name', this.$t('Account'));
},
transactions() {
if (this.loading) {
return [];
}
return this.transactionsStore.transactions;
},
noTransaction() {
return this.transactionsStore.noTransaction;
},
hasMoreTransaction() {
return this.transactionsStore.hasMoreTransaction;
},
allTransactionTypes() {
return transactionConstants.allTransactionTypes;
},
allAccounts() {
return this.accountsStore.allAccountsMap;
},
allCategories() {
return this.transactionCategoriesStore.allTransactionCategoriesMap;
},
allPrimaryCategories() {
return this.transactionCategoriesStore.allTransactionCategories;
},
allDateRanges() {
return datetimeConstants.allDateRanges;
}
},
created() {
const self = this;
const query = self.f7route.query;
let dateRange = self.$utilities.getDateRangeByDateType(query.dateType ? parseInt(query.dateType) : undefined, self.firstDayOfWeek);
let dateRange = getDateRangeByDateType(query.dateType ? parseInt(query.dateType) : undefined, self.firstDayOfWeek);
if (!dateRange &&
query.dateType === self.$constants.datetime.allDateRanges.Custom.type.toString() &&
query.dateType === self.allDateRanges.Custom.type.toString() &&
parseInt(query.maxTime) > 0 && parseInt(query.minTime) > 0) {
dateRange = {
dateType: parseInt(query.dateType),
@@ -531,7 +560,7 @@ export default {
};
}
this.$store.dispatch('initTransactionListFilter', {
this.transactionsStore.initTransactionListFilter({
dateType: dateRange ? dateRange.dateType : undefined,
maxTime: dateRange ? dateRange.maxTime : undefined,
minTime: dateRange ? dateRange.minTime : undefined,
@@ -544,7 +573,7 @@ export default {
},
methods: {
onPageAfterIn() {
if (this.$store.state.transactionListStateInvalid && !this.loading) {
if (this.transactionsStore.transactionListStateInvalid && !this.loading) {
this.reload(null);
}
@@ -558,10 +587,10 @@ export default {
}
Promise.all([
self.$store.dispatch('loadAllAccounts', { force: false }),
self.$store.dispatch('loadAllCategories', { force: false })
self.accountsStore.loadAllAccounts({ force: false }),
self.transactionCategoriesStore.loadAllCategories({ force: false })
]).then(() => {
return self.$store.dispatch('loadTransactions', {
return self.transactionsStore.loadTransactions({
reload: true,
autoExpand: true,
defaultCurrency: self.defaultCurrency
@@ -603,7 +632,7 @@ export default {
self.loadingMore = true;
self.$store.dispatch('loadTransactions', {
self.transactionsStore.loadTransactions({
reload: false,
autoExpand: autoExpand,
defaultCurrency: self.defaultCurrency
@@ -618,13 +647,13 @@ export default {
});
},
collapseTransactionMonthList(month, collapse) {
this.$store.dispatch('collapseMonthInTransactionList', {
this.transactionsStore.collapseMonthInTransactionList({
month: month,
collapse: collapse
});
},
changeDateFilter(dateType) {
if (dateType === this.$constants.datetime.allDateRanges.Custom.type) { // Custom
if (dateType === this.allDateRanges.Custom.type) { // Custom
this.showCustomDateRangeSheet = true;
this.showDatePopover = false;
return;
@@ -632,13 +661,13 @@ export default {
return;
}
const dateRange = this.$utilities.getDateRangeByDateType(dateType, this.firstDayOfWeek);
const dateRange = getDateRangeByDateType(dateType, this.firstDayOfWeek);
if (!dateRange) {
return;
}
this.$store.dispatch('updateTransactionListFilter', {
this.transactionsStore.updateTransactionListFilter({
dateType: dateRange.dateType,
maxTime: dateRange.maxTime,
minTime: dateRange.minTime
@@ -652,8 +681,8 @@ export default {
return;
}
this.$store.dispatch('updateTransactionListFilter', {
dateType: this.$constants.datetime.allDateRanges.Custom.type,
this.transactionsStore.updateTransactionListFilter({
dateType: this.allDateRanges.Custom.type,
maxTime: maxTime,
minTime: minTime
});
@@ -672,12 +701,12 @@ export default {
if (type && this.query.categoryId) {
const category = this.allCategories[this.query.categoryId];
if (category && category.type !== this.$utilities.transactionTypeToCategoryType(type)) {
if (category && category.type !== transactionTypeToCategoryType(type)) {
removeCategoryFilter = true;
}
}
this.$store.dispatch('updateTransactionListFilter', {
this.transactionsStore.updateTransactionListFilter({
type: type,
categoryId: removeCategoryFilter ? '0' : undefined
});
@@ -690,7 +719,7 @@ export default {
return;
}
this.$store.dispatch('updateTransactionListFilter', {
this.transactionsStore.updateTransactionListFilter({
categoryId: categoryId
});
@@ -702,7 +731,7 @@ export default {
return;
}
this.$store.dispatch('updateTransactionListFilter', {
this.transactionsStore.updateTransactionListFilter({
accountId: accountId
});
@@ -714,7 +743,7 @@ export default {
return;
}
this.$store.dispatch('updateTransactionListFilter', {
this.transactionsStore.updateTransactionListFilter({
keyword: keyword
});
@@ -744,11 +773,11 @@ export default {
self.transactionToDelete = null;
self.$showLoading();
self.$store.dispatch('deleteTransaction', {
self.transactionsStore.deleteTransaction({
transaction: transaction,
defaultCurrency: self.defaultCurrency,
beforeResolve: (done) => {
self.$ui.onSwipeoutDeleted(self.getTransactionDomId(transaction), done);
onSwipeoutDeleted(self.getTransactionDomId(transaction), done);
}
}).then(() => {
self.$hideLoading();
@@ -781,6 +810,15 @@ export default {
container.scrollTop(targetPos);
},
getDisplayYearMonth(transactionMonthList) {
return this.$locale.formatTimeToLongYearMonth(this.userStore, transactionMonthList.yearMonth);
},
getDisplayTime(transaction) {
return this.$locale.formatUnixTimeToShortTime(this.userStore, transaction.time, transaction.utcOffset, this.currentTimezoneOffsetMinutes);
},
getDisplayTimezone(transaction) {
return `UTC${getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)}`;
},
getDisplayAmount(amount, currency, hideAmount) {
if (hideAmount) {
return this.$locale.getDisplayCurrency('***', currency);
@@ -794,18 +832,21 @@ export default {
},
getTransactionTypeName(type, defaultName) {
switch (type){
case this.$constants.transaction.allTransactionTypes.ModifyBalance:
case this.allTransactionTypes.ModifyBalance:
return this.$t('Modify Balance');
case this.$constants.transaction.allTransactionTypes.Income:
case this.allTransactionTypes.Income:
return this.$t('Income');
case this.$constants.transaction.allTransactionTypes.Expense:
case this.allTransactionTypes.Expense:
return this.$t('Expense');
case this.$constants.transaction.allTransactionTypes.Transfer:
case this.allTransactionTypes.Transfer:
return this.$t('Transfer');
default:
return this.$t(defaultName);
}
},
getTransactionTypeFromCategoryType(categoryType) {
return categoryTypeToTransactionType(parseInt(categoryType));
},
getTransactionDomId(transaction) {
return 'transaction_' + transaction.id;
},
+30 -9
View File
@@ -10,10 +10,10 @@
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-item :title="$t('Accounts')" :after="$utilities.appendThousandsSeparator(dataStatistics.totalAccountCount)"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" :after="$utilities.appendThousandsSeparator(dataStatistics.totalTransactionCategoryCount)"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" :after="$utilities.appendThousandsSeparator(dataStatistics.totalTransactionTagCount)"></f7-list-item>
<f7-list-item :title="$t('Transactions')" :after="$utilities.appendThousandsSeparator(dataStatistics.totalTransactionCount)"></f7-list-item>
<f7-list-item :title="$t('Accounts')" :after="displayDataStatistics.totalAccountCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Categories')" :after="displayDataStatistics.totalTransactionCategoryCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" :after="displayDataStatistics.totalTransactionTagCount"></f7-list-item>
<f7-list-item :title="$t('Transactions')" :after="displayDataStatistics.totalTransactionCount"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" :class="{ 'disabled': loading }">
@@ -56,6 +56,12 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useUserStore } from '@/stores/user.js';
import { appendThousandsSeparator } from '@/lib/common.js';
export default {
props: [
'f7router'
@@ -74,11 +80,26 @@ export default {
};
},
computed: {
...mapStores(useRootStore, useUserStore),
displayDataStatistics() {
const self = this;
if (!self.dataStatistics) {
return null;
}
return {
totalAccountCount: appendThousandsSeparator(self.dataStatistics.totalAccountCount),
totalTransactionCategoryCount: appendThousandsSeparator(self.dataStatistics.totalTransactionCategoryCount),
totalTransactionTagCount: appendThousandsSeparator(self.dataStatistics.totalTransactionTagCount),
totalTransactionCount: appendThousandsSeparator(self.dataStatistics.totalTransactionCount)
};
},
isDataExportingEnabled() {
return this.$settings.isDataExportingEnabled();
},
exportFileName() {
const nickname = this.$store.getters.currentUserNickname;
const nickname = this.userStore.currentUserNickname;
if (nickname) {
return this.$t('dataExport.exportFilename', {
@@ -94,7 +115,7 @@ export default {
self.loading = true;
self.$store.dispatch('getUserDataStatistics').then(dataStatistics => {
self.userStore.getUserDataStatistics().then(dataStatistics => {
self.dataStatistics = dataStatistics;
self.loading = false;
}).catch(error => {
@@ -116,7 +137,7 @@ export default {
self.$showLoading();
self.exportingData = true;
self.$store.dispatch('getExportedUserData').then(data => {
self.userStore.getExportedUserData().then(data => {
self.exportedData = URL.createObjectURL(data);
self.exportingData = false;
self.$hideLoading();
@@ -142,7 +163,7 @@ export default {
self.clearingData = true;
self.$showLoading(() => self.clearingData);
self.$store.dispatch('clearUserData', {
self.rootStore.clearUserData({
password: password
}).then(() => {
self.clearingData = false;
@@ -153,7 +174,7 @@ export default {
self.loading = true;
self.$store.dispatch('getUserDataStatistics').then(dataStatistics => {
self.userStore.getUserDataStatistics().then(dataStatistics => {
self.dataStatistics = dataStatistics;
self.loading = false;
}).catch(error => {
+24 -14
View File
@@ -43,6 +43,15 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import { useTokensStore } from '@/stores/token.js';
import iconConstants from '@/consts/icon.js';
import { parseDeviceInfo, parseUserAgent } from '@/lib/misc.js';
import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7router'
@@ -55,6 +64,7 @@ export default {
};
},
computed: {
...mapStores(useUserStore, useTokensStore),
sessions() {
if (!this.tokens) {
return this.tokens;
@@ -70,9 +80,9 @@ export default {
domId: this.getTokenDomId(token.tokenId),
isCurrent: token.isCurrent,
deviceType: this.$t(token.isCurrent ? 'Current' : 'Other Device'),
deviceInfo: this.$utilities.parseDeviceInfo(token.userAgent),
deviceInfo: parseDeviceInfo(token.userAgent),
icon: this.getTokenIcon(token),
createdAt: this.$utilities.formatUnixTime(token.createdAt, this.$locale.getLongDateTimeFormat())
createdAt: this.$locale.formatUnixTimeToLongDateTime(this.userStore, token.createdAt)
});
}
@@ -84,7 +94,7 @@ export default {
self.loading = true;
self.$store.dispatch('getAllTokens').then(tokens => {
self.tokensStore.getAllTokens().then(tokens => {
self.tokens = tokens;
self.loading = false;
}).catch(error => {
@@ -103,7 +113,7 @@ export default {
reload(done) {
const self = this;
self.$store.dispatch('getAllTokens').then(tokens => {
self.tokensStore.getAllTokens().then(tokens => {
if (done) {
done();
}
@@ -125,12 +135,12 @@ export default {
self.$confirm('Are you sure you want to logout from this session?', () => {
self.$showLoading();
self.$store.dispatch('revokeToken', {
self.tokensStore.revokeToken({
tokenId: session.tokenId
}).then(() => {
self.$hideLoading();
self.$ui.onSwipeoutDeleted(self.getTokenDomId(session.tokenId), () => {
onSwipeoutDeleted(self.getTokenDomId(session.tokenId), () => {
for (let i = 0; i < self.tokens.length; i++) {
if (self.tokens[i].tokenId === session.tokenId) {
self.tokens.splice(i, 1);
@@ -156,7 +166,7 @@ export default {
self.$confirm('Are you sure you want to logout all other sessions?', () => {
self.$showLoading();
self.$store.dispatch('revokeAllTokens').then(() => {
self.tokensStore.revokeAllTokens().then(() => {
self.$hideLoading();
for (let i = self.tokens.length - 1; i >= 0; i--) {
@@ -176,22 +186,22 @@ export default {
});
},
getTokenIcon(token) {
const ua = this.$utilities.parseUserAgent(token.userAgent);
const ua = parseUserAgent(token.userAgent);
if (!ua || !ua.device) {
return this.$constants.icons.deviceIcons.desktop.f7Icon;
return iconConstants.deviceIcons.desktop.f7Icon;
}
if (ua.device.type === 'mobile') {
return this.$constants.icons.deviceIcons.mobile.f7Icon;
return iconConstants.deviceIcons.mobile.f7Icon;
} else if (ua.device.type === 'wearable') {
return this.$constants.icons.deviceIcons.wearable.f7Icon;
return iconConstants.deviceIcons.wearable.f7Icon;
} else if (ua.device.type === 'tablet') {
return this.$constants.icons.deviceIcons.tablet.f7Icon;
return iconConstants.deviceIcons.tablet.f7Icon;
} else if (ua.device.type === 'smarttv') {
return this.$constants.icons.deviceIcons.tv.f7Icon;
return iconConstants.deviceIcons.tv.f7Icon;
} else {
return this.$constants.icons.deviceIcons.desktop.f7Icon;
return iconConstants.deviceIcons.desktop.f7Icon;
}
},
getTokenDomId(tokenId) {
+11 -5
View File
@@ -57,6 +57,9 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useTwoFactorAuthStore } from '@/stores/twoFactorAuth.js';
export default {
props: [
'f7router'
@@ -82,12 +85,15 @@ export default {
showBackupCodeSheet: false
};
},
computed: {
...mapStores(useTwoFactorAuthStore),
},
created() {
const self = this;
self.loading = true;
self.$store.dispatch('get2FAStatus').then(response => {
self.twoFactorAuthStore.get2FAStatus().then(response => {
self.status = response.enable;
self.loading = false;
}).catch(error => {
@@ -112,7 +118,7 @@ export default {
self.enabling = true;
self.$showLoading(() => self.enabling);
self.$store.dispatch('enable2FA').then(response => {
self.twoFactorAuthStore.enable2FA().then(response => {
self.enabling = false;
self.$hideLoading();
@@ -135,7 +141,7 @@ export default {
self.enableConfirming = true;
self.$showLoading(() => self.enableConfirming);
self.$store.dispatch('confirmEnable2FA', {
self.twoFactorAuthStore.confirmEnable2FA({
secret: self.new2FASecret,
passcode: self.currentPasscodeForEnable
}).then(response => {
@@ -173,7 +179,7 @@ export default {
self.disabling = true;
self.$showLoading(() => self.disabling);
self.$store.dispatch('disable2FA', {
self.twoFactorAuthStore.disable2FA({
password: password
}).then(() => {
self.disabling = false;
@@ -203,7 +209,7 @@ export default {
self.regenerating = true;
self.$showLoading(() => self.regenerating);
self.$store.dispatch('regenerate2FARecoveryCode', {
self.twoFactorAuthStore.regenerate2FARecoveryCode({
password: password
}).then(response => {
self.regenerating = false;
+27 -14
View File
@@ -76,7 +76,7 @@
link="#" no-chevron
:class="{ 'disabled': !allVisibleAccounts.length }"
:header="$t('Default Account')"
:title="$utilities.getNameByKeyValue(allAccounts, newProfile.defaultAccountId, 'id', 'name', $t('Not Specified'))"
:title="getNameByKeyValue(allAccounts, newProfile.defaultAccountId, 'id', 'name', $t('Not Specified'))"
@click="showAccountSheet = true"
>
<two-column-list-item-selection-sheet primary-key-field="id" primary-value-field="category"
@@ -96,7 +96,7 @@
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Editable Transaction Scope')"
:title="$t($utilities.getNameByKeyValue(allTransactionEditScopeTypes, newProfile.transactionEditScope, 'value', 'name'))"
:title="$t(getNameByKeyValue(allTransactionEditScopeTypes, newProfile.transactionEditScope, 'value', 'name'))"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Date Range'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Editable Transaction Scope'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.transactionEditScope">
@@ -156,7 +156,7 @@
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Long Date Format')"
:title="$utilities.getNameByKeyValue(allLongDateFormats, newProfile.longDateFormat, 'type', 'displayName')"
:title="getNameByKeyValue(allLongDateFormats, newProfile.longDateFormat, 'type', 'displayName')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Long Date Format'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Long Date Format'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.longDateFormat">
@@ -169,7 +169,7 @@
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Short Date Format')"
:title="$utilities.getNameByKeyValue(allShortDateFormats, newProfile.shortDateFormat, 'type', 'displayName')"
:title="getNameByKeyValue(allShortDateFormats, newProfile.shortDateFormat, 'type', 'displayName')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Short Date Format'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Short Date Format'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.shortDateFormat">
@@ -182,7 +182,7 @@
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Long Time Format')"
:title="$utilities.getNameByKeyValue(allLongTimeFormats, newProfile.longTimeFormat, 'type', 'displayName')"
:title="getNameByKeyValue(allLongTimeFormats, newProfile.longTimeFormat, 'type', 'displayName')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Long Time Format'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Long Time Format'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.longTimeFormat">
@@ -195,7 +195,7 @@
<f7-list-item
class="list-item-with-header-and-title list-item-no-item-after"
:header="$t('Short Time Format')"
:title="$utilities.getNameByKeyValue(allShortTimeFormats, newProfile.shortTimeFormat, 'type', 'displayName')"
:title="getNameByKeyValue(allShortTimeFormats, newProfile.shortTimeFormat, 'type', 'displayName')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Long Time Format'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Short Time Format'), popupCloseLinkText: $t('Done') }"
>
<select v-model="newProfile.shortTimeFormat">
@@ -220,6 +220,15 @@
</template>
<script>
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
import { useUserStore } from '@/stores/user.js';
import { useAccountsStore } from '@/stores/account.js';
import datetimeConstants from '@/consts/datetime.js';
import { getNameByKeyValue } from '@/lib/common.js';
import { getCategorizedAccounts } from '@/lib/account.js';
export default {
props: [
'f7router'
@@ -263,6 +272,7 @@ export default {
};
},
computed: {
...mapStores(useRootStore, useUserStore, useAccountsStore),
allLanguages() {
const ret = [];
const allLanguageInfo = this.$locale.getAllLanguageInfos();
@@ -291,16 +301,16 @@ export default {
return this.$locale.getAllCurrencies();
},
allAccounts() {
return this.$store.getters.allPlainAccounts;
return this.accountsStore.allPlainAccounts;
},
allVisibleAccounts() {
return this.$store.getters.allVisiblePlainAccounts;
return this.accountsStore.allVisiblePlainAccounts;
},
allCategorizedAccounts() {
return this.$utilities.getCategorizedAccounts(this.allVisibleAccounts);
return getCategorizedAccounts(this.allVisibleAccounts);
},
allWeekDays() {
return this.$constants.datetime.allWeekDays;
return datetimeConstants.allWeekDays;
},
allLongDateFormats() {
return this.$locale.getAllLongDateFormats();
@@ -348,7 +358,7 @@ export default {
return this.$t('Unknown');
},
currentDayOfWeekName() {
const weekName = this.$utilities.getNameByKeyValue(this.$constants.datetime.allWeekDays, this.newProfile.firstDayOfWeek, 'type', 'name');
const weekName = getNameByKeyValue(datetimeConstants.allWeekDays, this.newProfile.firstDayOfWeek, 'type', 'name');
const i18nWeekNameKey = `datetime.${weekName}.long`;
return this.$t(i18nWeekNameKey);
},
@@ -418,8 +428,8 @@ export default {
self.loading = true;
const promises = [
self.$store.dispatch('loadAllAccounts', { force: false }),
self.$store.dispatch('getCurrentUserProfile')
self.accountsStore.loadAllAccounts({ force: false }),
self.userStore.getCurrentUserProfile()
];
Promise.all(promises).then(responses => {
@@ -460,7 +470,7 @@ export default {
self.saving = true;
self.$showLoading(() => self.saving);
self.$store.dispatch('updateUserProfile', {
self.rootStore.updateUserProfile({
profile: self.newProfile,
currentPassword: self.currentPassword
}).then(response => {
@@ -485,6 +495,9 @@ export default {
}
});
},
getNameByKeyValue(src, value, keyField, nameField, defaultName) {
return getNameByKeyValue(src, value, keyField, nameField, defaultName);
},
setCurrentUserProfile(profile) {
this.oldProfile.email = profile.email;
this.oldProfile.nickname = profile.nickname;
+4 -4
View File
@@ -88,10 +88,10 @@
"licenseUrl": "https://github.com/vuejs/core/blob/v3.3.4/LICENSE"
},
{
"name": "vuex",
"copyright": "Copyright (c) 2015-present Evan You",
"url": "https://github.com/vuejs/vuex",
"licenseUrl": "https://github.com/vuejs/vuex/blob/v4.1.0/LICENSE"
"name": "Pinia",
"copyright": "Copyright (c) 2019-present Eduardo San Martin Morote",
"url": "https://github.com/vuejs/pinia",
"licenseUrl": "https://github.com/vuejs/pinia/blob/pinia%402.1.3/LICENSE"
},
{
"name": "vue-i18n",