add insights & explore page

This commit is contained in:
MaysWind
2025-12-18 00:49:14 +08:00
parent 861e4c036b
commit e9b4392163
43 changed files with 3579 additions and 43 deletions
+303
View File
@@ -0,0 +1,303 @@
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import { useSettingsStore } from './setting.ts';
import { useUserStore } from './user.ts';
import { useAccountsStore } from './account.ts';
import { useTransactionCategoriesStore } from './transactionCategory.ts';
import { useTransactionTagsStore } from './transactionTag.ts';
import { DateRangeScene, DateRange } from '@/core/datetime.ts';
import { DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE } from '@/core/explore.ts';
import { type Account } from '@/models/account.ts';
import { type TransactionCategory } from '@/models/transaction_category.ts';
import { type TransactionTag } from '@/models/transaction_tag.ts';
import {
type TransactionInfoResponse,
type TransactionInsightDataItem
} from '@/models/transaction.ts';
import {
TransactionExploreQuery
} from '@/models/explore.ts';
import { isInteger, isEquals } from '@/lib/common.ts';
import { getDateRangeByDateType } from '@/lib/datetime.ts';
import services from '@/lib/services.ts';
import logger from '@/lib/logger.ts';
export interface TransactionExplorePartialFilter {
dateRangeType?: number;
startTime?: number;
endTime?: number;
queryId?: string;
}
export interface TransactionExploreFilter extends TransactionExplorePartialFilter {
dateRangeType: number;
startTime: number;
endTime: number;
query: TransactionExploreQuery[];
}
export const useExploresStore = defineStore('explores', () => {
const settingsStore = useSettingsStore();
const userStore = useUserStore();
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTagsStore = useTransactionTagsStore();
const transactionExploreFilter = ref<TransactionExploreFilter>({
dateRangeType: DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type,
startTime: 0,
endTime: 0,
query: []
});
const transactionExploreAllData = ref<TransactionInfoResponse[]>([]);
const transactionExploreStateInvalid = ref<boolean>(true);
const allTransactions = computed<TransactionInsightDataItem[]>(() => {
if (!transactionExploreAllData.value || transactionExploreAllData.value.length < 1) {
return [];
}
const result: TransactionInsightDataItem[] = [];
for (const transaction of transactionExploreAllData.value) {
const sourceAccount: Account | undefined = accountsStore.allAccountsMap[transaction.sourceAccountId];
if (!sourceAccount) {
continue;
}
let destinationAccount: Account | undefined = undefined
if (transaction.destinationAccountId && transaction.destinationAccountId !== '0') {
destinationAccount = accountsStore.allAccountsMap[transaction.destinationAccountId];
if (!destinationAccount) {
continue;
}
}
const secondaryCategory: TransactionCategory | undefined = transactionCategoriesStore.allTransactionCategoriesMap[transaction.categoryId];
if (!secondaryCategory) {
continue;
}
const primaryCategory: TransactionCategory | undefined = transactionCategoriesStore.allTransactionCategoriesMap[secondaryCategory.parentId];
if (!primaryCategory) {
continue;
}
const tags: TransactionTag[] = [];
for (const tagId of transaction.tagIds) {
const tag: TransactionTag | undefined = transactionTagsStore.allTransactionTagsMap[tagId];
if (tag) {
tags.push(tag);
}
}
const item: TransactionInsightDataItem = {
...transaction,
id: transaction.id,
time: transaction.time,
utcOffset: transaction.utcOffset,
type: transaction.type,
primaryCategory: primaryCategory,
primaryCategoryName: primaryCategory.name,
secondaryCategory: secondaryCategory,
secondaryCategoryName: secondaryCategory.name,
sourceAccount: sourceAccount,
sourceAccountName: sourceAccount.name,
destinationAccount: destinationAccount,
destinationAccountName: destinationAccount?.name,
sourceAmount: transaction.sourceAmount,
destinationAmount: transaction.destinationAmount,
hideAmount: transaction.hideAmount,
tags: tags,
comment: transaction.comment,
geoLocation: transaction.geoLocation
};
result.push(item);
}
return result;
});
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => {
if (!allTransactions.value || allTransactions.value.length < 1) {
return [];
}
if (!transactionExploreFilter.value.query || transactionExploreFilter.value.query.length < 1) {
return allTransactions.value;
}
const result: TransactionInsightDataItem[] = [];
for (const transaction of allTransactions.value) {
for (const query of transactionExploreFilter.value.query) {
if (query.match(transaction)) {
result.push(transaction);
break;
}
}
}
return result;
});
function updateTransactionExploreInvalidState(invalidState: boolean): void {
transactionExploreStateInvalid.value = invalidState;
}
function resetTransactionExplores(): void {
transactionExploreFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type;
transactionExploreFilter.value.startTime = 0;
transactionExploreFilter.value.endTime = 0;
transactionExploreFilter.value.query = [];
transactionExploreAllData.value = [];
transactionExploreStateInvalid.value = true;
}
function initTransactionExploreFilter(filter?: TransactionExplorePartialFilter, resetQuery?: boolean): void {
if (filter && isInteger(filter.dateRangeType)) {
transactionExploreFilter.value.dateRangeType = filter.dateRangeType;
} else {
transactionExploreFilter.value.dateRangeType = settingsStore.appSettings.insightsExploreDefaultDateRangeType;
}
let dateRangeTypeValid = true;
if (!DateRange.isAvailableForScene(transactionExploreFilter.value.dateRangeType, DateRangeScene.InsightsExplore)) {
transactionExploreFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORE_DATE_RANGE.type;
dateRangeTypeValid = false;
}
if (dateRangeTypeValid && transactionExploreFilter.value.dateRangeType === DateRange.Custom.type) {
if (filter && isInteger(filter.startTime)) {
transactionExploreFilter.value.startTime = filter.startTime;
} else {
transactionExploreFilter.value.startTime = 0;
}
if (filter && isInteger(filter.endTime)) {
transactionExploreFilter.value.endTime = filter.endTime;
} else {
transactionExploreFilter.value.endTime = 0;
}
} else {
const dateRange = getDateRangeByDateType(transactionExploreFilter.value.dateRangeType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart);
if (dateRange) {
transactionExploreFilter.value.dateRangeType = dateRange.dateType;
transactionExploreFilter.value.startTime = dateRange.minTime;
transactionExploreFilter.value.endTime = dateRange.maxTime;
}
}
if (resetQuery) {
transactionExploreFilter.value.query = [];
}
}
function updateTransactionExploreFilter(filter: TransactionExplorePartialFilter): boolean {
let changed = false;
if (filter && isInteger(filter.dateRangeType) && transactionExploreFilter.value.dateRangeType !== filter.dateRangeType) {
transactionExploreFilter.value.dateRangeType = filter.dateRangeType;
changed = true;
}
if (filter && isInteger(filter.startTime) && transactionExploreFilter.value.startTime !== filter.startTime) {
transactionExploreFilter.value.startTime = filter.startTime;
changed = true;
}
if (filter && isInteger(filter.endTime) && transactionExploreFilter.value.endTime !== filter.endTime) {
transactionExploreFilter.value.endTime = filter.endTime;
changed = true;
}
return changed;
}
function getTransactionExplorePageParams(currentExploreId: string, activeTab: string): string {
const querys: string[] = [];
if (currentExploreId) {
querys.push('id=' + currentExploreId);
}
if (activeTab) {
querys.push('activeTab=' + activeTab);
}
querys.push('dateRangeType=' + transactionExploreFilter.value.dateRangeType);
querys.push('startTime=' + transactionExploreFilter.value.startTime);
querys.push('endTime=' + transactionExploreFilter.value.endTime);
return querys.join('&');
}
function loadAllTransactions({ force }: { force: boolean }): Promise<TransactionInfoResponse[]> {
return new Promise((resolve, reject) => {
services.getAllTransactions({
startTime: transactionExploreFilter.value.startTime,
endTime: transactionExploreFilter.value.endTime
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to retrieve all transactions' });
return;
}
if (transactionExploreStateInvalid.value) {
updateTransactionExploreInvalidState(false);
}
if (force && data.result && isEquals(transactionExploreAllData.value, data.result)) {
reject({ message: 'Data is up to date', isUpToDate: true });
return;
}
transactionExploreAllData.value = data.result;
resolve(data.result);
}).catch(error => {
logger.error('failed to load all transactions', 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 all transactions' });
} else {
reject(error);
}
});
});
}
return {
// states
transactionExploreFilter,
transactionExploreStateInvalid,
// computed
filteredTransactions,
// functions
updateTransactionExploreInvalidState,
resetTransactionExplores,
initTransactionExploreFilter,
updateTransactionExploreFilter,
getTransactionExplorePageParams,
loadAllTransactions
};
});
+3
View File
@@ -10,6 +10,7 @@ import { useTransactionTemplatesStore } from './transactionTemplate.ts';
import { useTransactionsStore } from './transaction.ts';
import { useOverviewStore } from './overview.ts';
import { useStatisticsStore } from './statistics.ts';
import { useExploresStore } from './explore.ts';
import { useExchangeRatesStore } from './exchangeRates.ts';
import type { AuthResponse, RegisterResponse } from '@/models/auth_response.ts';
@@ -49,6 +50,7 @@ export const useRootStore = defineStore('root', () => {
const transactionsStore = useTransactionsStore();
const overviewStore = useOverviewStore();
const statisticsStore = useStatisticsStore();
const exploresStore = useExploresStore();
const exchangeRatesStore = useExchangeRatesStore();
const currentNotification = ref<string | null>(null);
@@ -60,6 +62,7 @@ export const useRootStore = defineStore('root', () => {
setNotificationContent(null);
exploresStore.resetTransactionExplores();
statisticsStore.resetTransactionStatistics();
overviewStore.resetTransactionOverview();
transactionsStore.resetTransactions();
+16
View File
@@ -245,6 +245,19 @@ export const useSettingsStore = defineStore('settings', () => {
updateUserApplicationCloudSettingValue('alwaysShowTransactionPicturesInMobileTransactionEditPage', value);
}
// Insights & Explore Page
function setInsightsExploreDefaultDateRangeType(value: number): void {
updateApplicationSettingsValue('insightsExploreDefaultDateRangeType', value);
appSettings.value.insightsExploreDefaultDateRangeType = value;
updateUserApplicationCloudSettingValue('insightsExploreDefaultDateRangeType', value);
}
function setTimezoneUsedForInsightsExplorePage(value: number): void {
updateApplicationSettingsValue('timezoneUsedForInsightsExplorePage', value);
appSettings.value.timezoneUsedForInsightsExplorePage = value;
updateUserApplicationCloudSettingValue('timezoneUsedForInsightsExplorePage', value);
}
// Account List Page
function setTotalAmountExcludeAccountIds(value: Record<string, boolean>): void {
updateApplicationSettingsValue('totalAmountExcludeAccountIds', value);
@@ -467,6 +480,9 @@ export const useSettingsStore = defineStore('settings', () => {
setAutoSaveTransactionDraft,
setAutoGetCurrentGeoLocation,
setAlwaysShowTransactionPicturesInMobileTransactionEditPage,
// -- Insights & Explore Page
setInsightsExploreDefaultDateRangeType,
setTimezoneUsedForInsightsExplorePage,
// -- Account List Page
setTotalAmountExcludeAccountIds,
// -- Exchange Rates Data Page