From 25c8b9baf821be3363246d530f1f74c5169dd446 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Thu, 9 Jan 2025 00:19:25 +0800 Subject: [PATCH] migrate overview store to composition API and typescript --- src/core/base.ts | 5 + src/core/datetime.ts | 5 + src/lib/services.ts | 6 +- src/models/transaction.ts | 121 +++--- src/stores/index.js | 2 +- src/stores/overview.js | 306 -------------- src/stores/overview.ts | 373 ++++++++++++++++++ src/stores/transaction.js | 2 +- src/views/desktop/HomePage.vue | 2 +- .../app/settings/tabs/AppBasicSettingTab.vue | 2 +- .../list/dialogs/ImportDialog.vue | 2 +- .../settings/tabs/UserBasicSettingTab.vue | 2 +- src/views/mobile/HomePage.vue | 2 +- src/views/mobile/SettingsPage.vue | 2 +- .../mobile/settings/PageSettingsPage.vue | 2 +- src/views/mobile/users/UserProfilePage.vue | 2 +- 16 files changed, 441 insertions(+), 395 deletions(-) delete mode 100644 src/stores/overview.js create mode 100644 src/stores/overview.ts diff --git a/src/core/base.ts b/src/core/base.ts index d3323661..c2d570d6 100644 --- a/src/core/base.ts +++ b/src/core/base.ts @@ -1,3 +1,8 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type PartialRecord = { + [P in K]?: T; +} + export interface TypeAndName { readonly type: number; readonly name: string; diff --git a/src/core/datetime.ts b/src/core/datetime.ts index dadf70e4..7d20d6e0 100644 --- a/src/core/datetime.ts +++ b/src/core/datetime.ts @@ -25,6 +25,11 @@ export interface StartEndTime { readonly endTime: number; } +export interface WritableStartEndTime extends StartEndTime { + startTime: number; + endTime: number; +} + export interface UnixTimeRange { readonly minUnixTime: number; readonly maxUnixTime: number; diff --git a/src/lib/services.ts b/src/lib/services.ts index 506989c2..f23b5e37 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -55,7 +55,7 @@ import type { TransactionStatisticTrendsRequest, TransactionStatisticTrendsItem, TransactionAmountsRequestParams, - TransactionAmountsResponseItem + TransactionAmountsResponse } from '@/models/transaction.ts'; import { TransactionAmountsRequest @@ -401,9 +401,9 @@ export default { return axios.get>(`v1/transactions/statistics/trends.json?use_transaction_timezone=${req.useTransactionTimezone}` + (queryParams.length ? '&' + queryParams.join('&') : '')); }, - getTransactionAmounts: (params: TransactionAmountsRequestParams): ApiResponsePromise => { + getTransactionAmounts: (params: TransactionAmountsRequestParams): ApiResponsePromise => { const req = TransactionAmountsRequest.of(params); - return axios.get>(`v1/transactions/amounts.json?${req.buildQuery()}`); + return axios.get>(`v1/transactions/amounts.json?${req.buildQuery()}`); }, getTransaction: (req: { id: string, withPictures: boolean | undefined }): ApiResponsePromise => { if (!isDefined(req.withPictures)) { diff --git a/src/models/transaction.ts b/src/models/transaction.ts index 1285211f..3b188d88 100644 --- a/src/models/transaction.ts +++ b/src/models/transaction.ts @@ -1,3 +1,4 @@ +import type { PartialRecord } from '@/core/base.ts'; import type { StartEndTime } from '@/core/datetime.ts'; import type { AccountInfoResponse } from './account.ts'; @@ -126,23 +127,32 @@ export interface TransactionStatisticTrendsRequest extends YearMonthRangeRequest readonly useTransactionTimezone: boolean; } -export interface TransactionAmountsRequestParams { +export const ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE = [ + 'today', 'thisWeek', 'thisMonth', 'thisYear', 'lastMonth', + 'monthBeforeLastMonth', 'monthBeforeLast2Months', 'monthBeforeLast3Months', + 'monthBeforeLast4Months', 'monthBeforeLast5Months', 'monthBeforeLast6Months', + 'monthBeforeLast7Months', 'monthBeforeLast8Months', 'monthBeforeLast9Months', 'monthBeforeLast10Months' +] as const; + +export type TransactionAmountsRequestType = typeof ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE[number]; + +export interface TransactionAmountsRequestParams extends PartialRecord { readonly useTransactionTimezone: boolean; - readonly today: StartEndTime; - readonly thisWeek: StartEndTime; - readonly thisMonth: StartEndTime; - readonly thisYear: StartEndTime; - readonly lastMonth: StartEndTime; - readonly monthBeforeLastMonth: StartEndTime; - readonly monthBeforeLast2Months: StartEndTime; - readonly monthBeforeLast3Months: StartEndTime; - readonly monthBeforeLast4Months: StartEndTime; - readonly monthBeforeLast5Months: StartEndTime; - readonly monthBeforeLast6Months: StartEndTime; - readonly monthBeforeLast7Months: StartEndTime; - readonly monthBeforeLast8Months: StartEndTime; - readonly monthBeforeLast9Months: StartEndTime; - readonly monthBeforeLast10Months: StartEndTime; + today?: StartEndTime; + thisWeek?: StartEndTime; + thisMonth?: StartEndTime; + thisYear?: StartEndTime; + lastMonth?: StartEndTime; + monthBeforeLastMonth?: StartEndTime; + monthBeforeLast2Months?: StartEndTime; + monthBeforeLast3Months?: StartEndTime; + monthBeforeLast4Months?: StartEndTime; + monthBeforeLast5Months?: StartEndTime; + monthBeforeLast6Months?: StartEndTime; + monthBeforeLast7Months?: StartEndTime; + monthBeforeLast8Months?: StartEndTime; + monthBeforeLast9Months?: StartEndTime; + monthBeforeLast10Months?: StartEndTime; } export class TransactionAmountsRequest { @@ -159,67 +169,13 @@ export class TransactionAmountsRequest { } public static of(params: TransactionAmountsRequestParams): TransactionAmountsRequest { - const queryParams = []; + const queryParams: string[] = []; - if (params.today) { - queryParams.push(`today_${params.today.startTime}_${params.today.endTime}`); - } - - if (params.thisWeek) { - queryParams.push(`thisWeek_${params.thisWeek.startTime}_${params.thisWeek.endTime}`); - } - - if (params.thisMonth) { - queryParams.push(`thisMonth_${params.thisMonth.startTime}_${params.thisMonth.endTime}`); - } - - if (params.thisYear) { - queryParams.push(`thisYear_${params.thisYear.startTime}_${params.thisYear.endTime}`); - } - - if (params.lastMonth) { - queryParams.push(`lastMonth_${params.lastMonth.startTime}_${params.lastMonth.endTime}`); - } - - if (params.monthBeforeLastMonth) { - queryParams.push(`monthBeforeLastMonth_${params.monthBeforeLastMonth.startTime}_${params.monthBeforeLastMonth.endTime}`); - } - - if (params.monthBeforeLast2Months) { - queryParams.push(`monthBeforeLast2Months_${params.monthBeforeLast2Months.startTime}_${params.monthBeforeLast2Months.endTime}`); - } - - if (params.monthBeforeLast3Months) { - queryParams.push(`monthBeforeLast3Months_${params.monthBeforeLast3Months.startTime}_${params.monthBeforeLast3Months.endTime}`); - } - - if (params.monthBeforeLast4Months) { - queryParams.push(`monthBeforeLast4Months_${params.monthBeforeLast4Months.startTime}_${params.monthBeforeLast4Months.endTime}`); - } - - if (params.monthBeforeLast5Months) { - queryParams.push(`monthBeforeLast5Months_${params.monthBeforeLast5Months.startTime}_${params.monthBeforeLast5Months.endTime}`); - } - - if (params.monthBeforeLast6Months) { - queryParams.push(`monthBeforeLast6Months_${params.monthBeforeLast6Months.startTime}_${params.monthBeforeLast6Months.endTime}`); - } - - if (params.monthBeforeLast7Months) { - queryParams.push(`monthBeforeLast7Months_${params.monthBeforeLast7Months.startTime}_${params.monthBeforeLast7Months.endTime}`); - } - - if (params.monthBeforeLast8Months) { - queryParams.push(`monthBeforeLast8Months_${params.monthBeforeLast8Months.startTime}_${params.monthBeforeLast8Months.endTime}`); - } - - if (params.monthBeforeLast9Months) { - queryParams.push(`monthBeforeLast9Months_${params.monthBeforeLast9Months.startTime}_${params.monthBeforeLast9Months.endTime}`); - } - - if (params.monthBeforeLast10Months) { - queryParams.push(`monthBeforeLast10Months_${params.monthBeforeLast10Months.startTime}_${params.monthBeforeLast10Months.endTime}`); - } + ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE.forEach((type) => { + if (params[type]) { + queryParams.push(`${type}_${params[type].startTime}_${params[type].endTime}`); + } + }); return new TransactionAmountsRequest(params.useTransactionTimezone, (queryParams.length ? queryParams.join('|') : '')); } @@ -254,6 +210,8 @@ export interface TransactionStatisticTrendsItem { readonly items: TransactionStatisticResponseItem[]; } +export type TransactionAmountsResponse = PartialRecord; + export interface TransactionAmountsResponseItem { readonly startTime: number; readonly endTime: number; @@ -265,3 +223,14 @@ export interface TransactionAmountsResponseItemAmountInfo { readonly incomeAmount: number; readonly expenseAmount: number; } + +export type TransactionOverviewResponse = PartialRecord; + +export interface TransactionOverviewResponseItem { + valid: boolean; + incomeAmount: number; + expenseAmount: number; + incompleteIncomeAmount: boolean; + incompleteExpenseAmount: boolean; + amounts?: TransactionAmountsResponseItemAmountInfo[]; +} diff --git a/src/stores/index.js b/src/stores/index.js index d9264a17..006c201f 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -7,7 +7,7 @@ import { useTransactionCategoriesStore } from './transactionCategory.js'; import { useTransactionTagsStore } from './transactionTag.js'; import { useTransactionTemplatesStore } from './transactionTemplate.js'; import { useTransactionsStore } from './transaction.js'; -import { useOverviewStore } from './overview.js'; +import { useOverviewStore } from './overview.ts'; import { useStatisticsStore } from './statistics.js'; import { useExchangeRatesStore } from './exchangeRates.ts'; diff --git a/src/stores/overview.js b/src/stores/overview.js deleted file mode 100644 index e831addd..00000000 --- a/src/stores/overview.js +++ /dev/null @@ -1,306 +0,0 @@ -import { defineStore } from 'pinia'; - -import { useSettingsStore } from '@/stores/setting.ts'; -import { useUserStore } from './user.ts'; -import { useExchangeRatesStore } from './exchangeRates.ts'; - -import { isNumber, isEquals } from '@/lib/common.ts'; -import { - getUnixTimeBeforeUnixTime, - getTodayFirstUnixTime, - getTodayLastUnixTime, - getThisWeekFirstUnixTime, - getThisWeekLastUnixTime, - getThisMonthFirstUnixTime, - getThisMonthLastUnixTime, - getThisYearFirstUnixTime, - getThisYearLastUnixTime -} from '@/lib/datetime.ts'; -import services from '@/lib/services.ts'; -import logger from '@/lib/logger.ts'; - -function updateTransactionDateRange(state) { - const userStore = useUserStore(); - - state.transactionDataRange.today.startTime = getTodayFirstUnixTime(); - state.transactionDataRange.today.endTime = getTodayLastUnixTime(); - - state.transactionDataRange.thisWeek.startTime = getThisWeekFirstUnixTime(userStore.currentUserFirstDayOfWeek); - state.transactionDataRange.thisWeek.endTime = getThisWeekLastUnixTime(userStore.currentUserFirstDayOfWeek); - - state.transactionDataRange.thisMonth.startTime = getThisMonthFirstUnixTime(); - state.transactionDataRange.thisMonth.endTime = getThisMonthLastUnixTime(); - - state.transactionDataRange.thisYear.startTime = getThisYearFirstUnixTime(); - state.transactionDataRange.thisYear.endTime = getThisYearLastUnixTime(); - - state.transactionDataRange.lastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'); - state.transactionDataRange.lastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months'); - - state.transactionDataRange.monthBeforeLastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months'); - state.transactionDataRange.monthBeforeLastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months'); - - for (let i = 2; i <= 10; i++) { - state.transactionDataRange[`monthBeforeLast${i}Months`].startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), i + 1, 'months'); - state.transactionDataRange[`monthBeforeLast${i}Months`].endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), i + 1, 'months'); - } -} - -export const useOverviewStore = defineStore('overview', { - state: () => ({ - transactionDataRange: { - today: { - startTime: getTodayFirstUnixTime(), - endTime: getTodayLastUnixTime() - }, - thisWeek: { - startTime: getThisWeekFirstUnixTime(useUserStore().currentUserFirstDayOfWeek), - endTime: getThisWeekLastUnixTime(useUserStore().currentUserFirstDayOfWeek) - }, - thisMonth: { - startTime: getThisMonthFirstUnixTime(), - endTime: getThisMonthLastUnixTime() - }, - thisYear: { - startTime: getThisYearFirstUnixTime(), - endTime: getThisYearLastUnixTime() - }, - lastMonth: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months') - }, - monthBeforeLastMonth: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months') - }, - monthBeforeLast2Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 3, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 3, 'months') - }, - monthBeforeLast3Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 4, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 4, 'months') - }, - monthBeforeLast4Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 5, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 5, 'months') - }, - monthBeforeLast5Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 6, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 6, 'months') - }, - monthBeforeLast6Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 7, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 7, 'months') - }, - monthBeforeLast7Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 8, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 8, 'months') - }, - monthBeforeLast8Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 9, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 9, 'months') - }, - monthBeforeLast9Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 10, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 10, 'months') - }, - monthBeforeLast10Months: { - startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 11, 'months'), - endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 11, 'months') - } - }, - transactionOverviewOptions: { - loadLast11Months: false - }, - transactionOverviewData: {}, - transactionOverviewStateInvalid: true - }), - getters: { - transactionOverview(state) { - const userStore = useUserStore(); - const exchangeRatesStore = useExchangeRatesStore(); - - const overviewData = state.transactionOverviewData; - - if (!overviewData || !overviewData.thisMonth) { - return { - thisMonth: { - valid: false, - incomeAmount: 0, - expenseAmount: 0, - incompleteIncomeAmount: false, - incompleteExpenseAmount: false - } - }; - } - - const finalOverviewData = {}; - const defaultCurrency = userStore.currentUserDefaultCurrency; - - [ - 'today', - 'thisWeek', - 'thisMonth', - 'thisYear', - 'lastMonth', - 'monthBeforeLastMonth', - 'monthBeforeLast2Months', - 'monthBeforeLast3Months', - 'monthBeforeLast4Months', - 'monthBeforeLast5Months', - 'monthBeforeLast6Months', - 'monthBeforeLast7Months', - 'monthBeforeLast8Months', - 'monthBeforeLast9Months', - 'monthBeforeLast10Months' - ].forEach(field => { - if (!Object.prototype.hasOwnProperty.call(overviewData, field)) { - return; - } - - const item = overviewData[field]; - - if (!item) { - return; - } - - let totalIncomeAmount = 0; - let totalExpenseAmount = 0; - let hasUnCalculatedTotalIncome = false; - let hasUnCalculatedTotalExpense = false; - - if (item.amounts) { - 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; - } - } - } - - finalOverviewData[field] = { - valid: true, - incomeAmount: totalIncomeAmount, - expenseAmount: totalExpenseAmount, - incompleteIncomeAmount: hasUnCalculatedTotalIncome, - incompleteExpenseAmount: hasUnCalculatedTotalExpense, - amounts: item.amounts || [] - }; - }); - - return finalOverviewData; - } - }, - actions: { - updateTransactionOverviewInvalidState(invalidState) { - this.transactionOverviewStateInvalid = invalidState; - }, - resetTransactionOverview() { - updateTransactionDateRange(this); - this.transactionOverviewOptions.loadLast11Months = false; - this.transactionOverviewData = {}; - this.transactionOverviewStateInvalid = true; - }, - loadTransactionOverview({ force, loadLast11Months }) { - const settingsStore = useSettingsStore(); - - const self = this; - let dateChanged = false; - let rangeChanged = false; - - if (self.transactionDataRange.today.startTime !== getTodayFirstUnixTime()) { - dateChanged = true; - updateTransactionDateRange(self); - } - - if (loadLast11Months && !self.transactionOverviewOptions.loadLast11Months) { - rangeChanged = true; - } - - if (!dateChanged && !rangeChanged && !force && !self.transactionOverviewStateInvalid) { - return new Promise((resolve) => { - resolve(self.transactionOverviewData); - }); - } - - const requestParams = { - useTransactionTimezone: settingsStore.appSettings.timezoneUsedForStatisticsInHomePage, - today: self.transactionDataRange.today, - thisWeek: self.transactionDataRange.thisWeek, - thisMonth: self.transactionDataRange.thisMonth, - thisYear: self.transactionDataRange.thisYear - }; - - if (loadLast11Months) { - requestParams.lastMonth = self.transactionDataRange.lastMonth; - requestParams.monthBeforeLastMonth = self.transactionDataRange.monthBeforeLastMonth; - requestParams.monthBeforeLast2Months = self.transactionDataRange.monthBeforeLast2Months; - requestParams.monthBeforeLast3Months = self.transactionDataRange.monthBeforeLast3Months; - requestParams.monthBeforeLast4Months = self.transactionDataRange.monthBeforeLast4Months; - requestParams.monthBeforeLast5Months = self.transactionDataRange.monthBeforeLast5Months; - requestParams.monthBeforeLast6Months = self.transactionDataRange.monthBeforeLast6Months; - requestParams.monthBeforeLast7Months = self.transactionDataRange.monthBeforeLast7Months; - requestParams.monthBeforeLast8Months = self.transactionDataRange.monthBeforeLast8Months; - requestParams.monthBeforeLast9Months = self.transactionDataRange.monthBeforeLast9Months; - requestParams.monthBeforeLast10Months = self.transactionDataRange.monthBeforeLast10Months; - } - - return new Promise((resolve, reject) => { - services.getTransactionAmounts(requestParams).then(response => { - const data = response.data; - - if (!data || !data.success || !data.result) { - reject({ message: 'Unable to retrieve transaction overview' }); - return; - } - - if (self.transactionOverviewStateInvalid) { - self.updateTransactionOverviewInvalidState(false); - } - - if (force && data.result && isEquals(self.transactionOverviewData, data.result)) { - reject({ message: 'Data is up to date' }); - return; - } - - self.transactionOverviewData = data.result; - self.transactionOverviewOptions.loadLast11Months = loadLast11Months; - - resolve(data.result); - }).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 retrieve transaction overview' }); - } else { - reject(error); - } - }); - }); - } - } -}); diff --git a/src/stores/overview.ts b/src/stores/overview.ts new file mode 100644 index 00000000..b38b236b --- /dev/null +++ b/src/stores/overview.ts @@ -0,0 +1,373 @@ +import { type Ref, ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +import { useSettingsStore } from './setting.ts'; +import { useUserStore } from './user.ts'; +import { useExchangeRatesStore } from './exchangeRates.ts'; + +import type { WritableStartEndTime } from '@/core/datetime.ts'; +import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; +import type { + TransactionAmountsRequestParams, + TransactionAmountsResponse, + TransactionOverviewResponse +} from '@/models/transaction.ts'; +import { ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE } from '@/models/transaction.ts'; + +import { isNumber, isEquals } from '@/lib/common.ts'; +import { + getUnixTimeBeforeUnixTime, + getTodayFirstUnixTime, + getTodayLastUnixTime, + getThisWeekFirstUnixTime, + getThisWeekLastUnixTime, + getThisMonthFirstUnixTime, + getThisMonthLastUnixTime, + getThisYearFirstUnixTime, + getThisYearLastUnixTime +} from '@/lib/datetime.ts'; +import logger from '@/lib/logger.ts'; +import services from '@/lib/services.ts'; + +interface TransactionDataRange extends Record { + today: { + startTime: number; + endTime: number; + }; + thisWeek: { + startTime: number; + endTime: number; + }; + thisMonth: { + startTime: number; + endTime: number; + }; + thisYear: { + startTime: number; + endTime: number; + }; + lastMonth: { + startTime: number; + endTime: number; + }; + monthBeforeLastMonth: { + startTime: number; + endTime: number; + }; + monthBeforeLast2Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast3Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast4Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast5Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast6Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast7Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast8Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast9Months: { + startTime: number; + endTime: number; + }; + monthBeforeLast10Months: { + startTime: number; + endTime: number; + }; +} + +interface TransactionOverviewOptions { + loadLast11Months: boolean; +} + +export const useOverviewStore = defineStore('overview', () => { + const settingsStore = useSettingsStore(); + const userStore = useUserStore(); + const exchangeRatesStore = useExchangeRatesStore(); + + const transactionDataRange: Ref = ref({ + today: { + startTime: getTodayFirstUnixTime(), + endTime: getTodayLastUnixTime() + }, + thisWeek: { + startTime: getThisWeekFirstUnixTime(userStore.currentUserFirstDayOfWeek), + endTime: getThisWeekLastUnixTime(userStore.currentUserFirstDayOfWeek) + }, + thisMonth: { + startTime: getThisMonthFirstUnixTime(), + endTime: getThisMonthLastUnixTime() + }, + thisYear: { + startTime: getThisYearFirstUnixTime(), + endTime: getThisYearLastUnixTime() + }, + lastMonth: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months') + }, + monthBeforeLastMonth: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months') + }, + monthBeforeLast2Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 3, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 3, 'months') + }, + monthBeforeLast3Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 4, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 4, 'months') + }, + monthBeforeLast4Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 5, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 5, 'months') + }, + monthBeforeLast5Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 6, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 6, 'months') + }, + monthBeforeLast6Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 7, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 7, 'months') + }, + monthBeforeLast7Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 8, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 8, 'months') + }, + monthBeforeLast8Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 9, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 9, 'months') + }, + monthBeforeLast9Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 10, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 10, 'months') + }, + monthBeforeLast10Months: { + startTime: getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 11, 'months'), + endTime: getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 11, 'months') + } + }); + + const transactionOverviewOptions: Ref = ref({ + loadLast11Months: false + }); + + const transactionOverviewData: Ref = ref({}); + const transactionOverviewStateInvalid: Ref = ref(true); + + const transactionOverview = computed(() => { + const overviewData = transactionOverviewData.value; + + if (!overviewData || !overviewData.thisMonth) { + return { + thisMonth: { + valid: false, + incomeAmount: 0, + expenseAmount: 0, + incompleteIncomeAmount: false, + incompleteExpenseAmount: false + } + } as TransactionOverviewResponse; + } + + const finalOverviewData: TransactionOverviewResponse = {}; + const defaultCurrency = userStore.currentUserDefaultCurrency; + + ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE.forEach(field => { + const item = overviewData[field]; + + if (!item) { + return; + } + + let totalIncomeAmount = 0; + let totalExpenseAmount = 0; + let hasUnCalculatedTotalIncome = false; + let hasUnCalculatedTotalExpense = false; + + if (item.amounts) { + 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 as number); + } else { + hasUnCalculatedTotalIncome = true; + } + + if (isNumber(expenseAmount)) { + totalExpenseAmount += Math.floor(expenseAmount as number); + } else { + hasUnCalculatedTotalExpense = true; + } + } else { + totalIncomeAmount += amount.incomeAmount; + totalExpenseAmount += amount.expenseAmount; + } + } + } + + finalOverviewData[field] = { + valid: true, + incomeAmount: totalIncomeAmount, + expenseAmount: totalExpenseAmount, + incompleteIncomeAmount: hasUnCalculatedTotalIncome, + incompleteExpenseAmount: hasUnCalculatedTotalExpense, + amounts: item.amounts || [] + }; + }); + + return finalOverviewData; + }); + + function updateTransactionDateRange(): void { + transactionDataRange.value.today.startTime = getTodayFirstUnixTime(); + transactionDataRange.value.today.endTime = getTodayLastUnixTime(); + + transactionDataRange.value.thisWeek.startTime = getThisWeekFirstUnixTime(userStore.currentUserFirstDayOfWeek); + transactionDataRange.value.thisWeek.endTime = getThisWeekLastUnixTime(userStore.currentUserFirstDayOfWeek); + + transactionDataRange.value.thisMonth.startTime = getThisMonthFirstUnixTime(); + transactionDataRange.value.thisMonth.endTime = getThisMonthLastUnixTime(); + + transactionDataRange.value.thisYear.startTime = getThisYearFirstUnixTime(); + transactionDataRange.value.thisYear.endTime = getThisYearLastUnixTime(); + + transactionDataRange.value.lastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'); + transactionDataRange.value.lastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 1, 'months'); + + transactionDataRange.value.monthBeforeLastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 2, 'months'); + transactionDataRange.value.monthBeforeLastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), 2, 'months'); + + for (let i = 2; i <= 10; i++) { + transactionDataRange.value[`monthBeforeLast${i}Months`].startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), i + 1, 'months'); + transactionDataRange.value[`monthBeforeLast${i}Months`].endTime = getUnixTimeBeforeUnixTime(getThisMonthLastUnixTime(), i + 1, 'months'); + } + } + + function updateTransactionOverviewInvalidState(invalidState: boolean): void { + transactionOverviewStateInvalid.value = invalidState; + } + + function resetTransactionOverview() { + updateTransactionDateRange(); + transactionOverviewOptions.value.loadLast11Months = false; + transactionOverviewData.value = {}; + transactionOverviewStateInvalid.value = true; + } + + function loadTransactionOverview(params: { force: boolean, loadLast11Months: boolean }): Promise { + let dateChanged = false; + let rangeChanged = false; + + if (transactionDataRange.value.today.startTime !== getTodayFirstUnixTime()) { + dateChanged = true; + updateTransactionDateRange(); + } + + if (params.loadLast11Months && !transactionOverviewOptions.value.loadLast11Months) { + rangeChanged = true; + } + + if (!dateChanged && !rangeChanged && !params.force && !transactionOverviewStateInvalid.value) { + return new Promise((resolve) => { + resolve(transactionOverviewData.value); + }); + } + + const requestParams: TransactionAmountsRequestParams = { + useTransactionTimezone: settingsStore.appSettings.timezoneUsedForStatisticsInHomePage == TimezoneTypeForStatistics.TransactionTimezone.type, + today: transactionDataRange.value.today, + thisWeek: transactionDataRange.value.thisWeek, + thisMonth: transactionDataRange.value.thisMonth, + thisYear: transactionDataRange.value.thisYear + }; + + if (params.loadLast11Months) { + requestParams.lastMonth = transactionDataRange.value.lastMonth; + requestParams.monthBeforeLastMonth = transactionDataRange.value.monthBeforeLastMonth; + requestParams.monthBeforeLast2Months = transactionDataRange.value.monthBeforeLast2Months; + requestParams.monthBeforeLast3Months = transactionDataRange.value.monthBeforeLast3Months; + requestParams.monthBeforeLast4Months = transactionDataRange.value.monthBeforeLast4Months; + requestParams.monthBeforeLast5Months = transactionDataRange.value.monthBeforeLast5Months; + requestParams.monthBeforeLast6Months = transactionDataRange.value.monthBeforeLast6Months; + requestParams.monthBeforeLast7Months = transactionDataRange.value.monthBeforeLast7Months; + requestParams.monthBeforeLast8Months = transactionDataRange.value.monthBeforeLast8Months; + requestParams.monthBeforeLast9Months = transactionDataRange.value.monthBeforeLast9Months; + requestParams.monthBeforeLast10Months = transactionDataRange.value.monthBeforeLast10Months; + } + + return new Promise((resolve, reject) => { + services.getTransactionAmounts(requestParams).then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + reject({ message: 'Unable to retrieve transaction overview' }); + return; + } + + if (transactionOverviewStateInvalid.value) { + updateTransactionOverviewInvalidState(false); + } + + if (params.force && data.result && isEquals(transactionOverviewData.value, data.result)) { + reject({ message: 'Data is up to date' }); + return; + } + + transactionOverviewData.value = data.result; + transactionOverviewOptions.value.loadLast11Months = params.loadLast11Months; + + resolve(data.result); + }).catch(error => { + if (params.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 retrieve transaction overview' }); + } else { + reject(error); + } + }); + }); + } + + return { + // states + transactionDataRange, + transactionOverviewOptions, + transactionOverviewData, + transactionOverviewStateInvalid, + // computed states, + transactionOverview, + // functions + updateTransactionOverviewInvalidState, + resetTransactionOverview, + loadTransactionOverview + }; +}); diff --git a/src/stores/transaction.js b/src/stores/transaction.js index 89ea2ae5..8ff9f5d2 100644 --- a/src/stores/transaction.js +++ b/src/stores/transaction.js @@ -4,7 +4,7 @@ import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; import { useAccountsStore } from './account.js'; import { useTransactionCategoriesStore } from './transactionCategory.js'; -import { useOverviewStore } from './overview.js'; +import { useOverviewStore } from './overview.ts'; import { useStatisticsStore } from './statistics.js'; import { useExchangeRatesStore } from './exchangeRates.ts'; diff --git a/src/views/desktop/HomePage.vue b/src/views/desktop/HomePage.vue index dfe5c9fd..cea5b500 100644 --- a/src/views/desktop/HomePage.vue +++ b/src/views/desktop/HomePage.vue @@ -194,7 +194,7 @@ import { mapStores } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useAccountsStore } from '@/stores/account.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { DateRange } from '@/core/datetime.ts'; import { ThemeType } from '@/core/theme.ts'; diff --git a/src/views/desktop/app/settings/tabs/AppBasicSettingTab.vue b/src/views/desktop/app/settings/tabs/AppBasicSettingTab.vue index 371867a7..5e0f9396 100644 --- a/src/views/desktop/app/settings/tabs/AppBasicSettingTab.vue +++ b/src/views/desktop/app/settings/tabs/AppBasicSettingTab.vue @@ -210,7 +210,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useTransactionsStore } from '@/stores/transaction.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { useStatisticsStore } from '@/stores/statistics.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; diff --git a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue index 776b99a7..77dc29d1 100644 --- a/src/views/desktop/transactions/list/dialogs/ImportDialog.vue +++ b/src/views/desktop/transactions/list/dialogs/ImportDialog.vue @@ -602,7 +602,7 @@ 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 { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { useStatisticsStore } from '@/stores/statistics.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; diff --git a/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue b/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue index 30eff771..109191f3 100644 --- a/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue +++ b/src/views/desktop/user/settings/tabs/UserBasicSettingTab.vue @@ -335,7 +335,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useAccountsStore } from '@/stores/account.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { WeekDay } from '@/core/datetime.ts'; import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts'; diff --git a/src/views/mobile/HomePage.vue b/src/views/mobile/HomePage.vue index ae440e87..d7867b00 100644 --- a/src/views/mobile/HomePage.vue +++ b/src/views/mobile/HomePage.vue @@ -205,7 +205,7 @@ import { mapStores } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { DateRange } from '@/core/datetime.ts'; import { TemplateType } from '@/core/template.ts'; diff --git a/src/views/mobile/SettingsPage.vue b/src/views/mobile/SettingsPage.vue index 0cb2d609..e5f63b12 100644 --- a/src/views/mobile/SettingsPage.vue +++ b/src/views/mobile/SettingsPage.vue @@ -76,7 +76,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useTransactionsStore } from '@/stores/transaction.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { useStatisticsStore } from '@/stores/statistics.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; diff --git a/src/views/mobile/settings/PageSettingsPage.vue b/src/views/mobile/settings/PageSettingsPage.vue index 193254b7..079f4c66 100644 --- a/src/views/mobile/settings/PageSettingsPage.vue +++ b/src/views/mobile/settings/PageSettingsPage.vue @@ -68,7 +68,7 @@ import { mapStores } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useTransactionsStore } from '@/stores/transaction.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; export default { computed: { diff --git a/src/views/mobile/users/UserProfilePage.vue b/src/views/mobile/users/UserProfilePage.vue index 936d17f9..fdf25c69 100644 --- a/src/views/mobile/users/UserProfilePage.vue +++ b/src/views/mobile/users/UserProfilePage.vue @@ -337,7 +337,7 @@ import { useRootStore } from '@/stores/index.js'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; import { useAccountsStore } from '@/stores/account.js'; -import { useOverviewStore } from '@/stores/overview.js'; +import { useOverviewStore } from '@/stores/overview.ts'; import { getNameByKeyValue } from '@/lib/common.ts'; import { getCategorizedAccounts } from '@/lib/account.js';