add asset trends in statistics & analysis (#314)

This commit is contained in:
MaysWind
2025-11-09 22:51:46 +08:00
parent d3abb279e3
commit 4c8bb5a0b7
52 changed files with 1917 additions and 266 deletions
@@ -10,6 +10,7 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import type { TypeAndDisplayName } from '@/core/base.ts';
import type { WeekDayValue } from '@/core/datetime.ts';
import { TransactionType } from '@/core/transaction.ts';
import { StatisticsAnalysisType } from '@/core/statistics.ts';
import { KnownFileType } from '@/core/file.ts';
import type { Account } from '@/models/account.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts';
@@ -55,7 +56,7 @@ export function useReconciliationStatementPageBase() {
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllAccountBalanceTrendChartTypes());
const allDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypesWithShortName());
const allDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypesWithShortName(StatisticsAnalysisType.AssetTrends));
const currentAccount = computed(() => allAccountsMap.value[accountId.value]);
const currentAccountCurrency = computed<string>(() => currentAccount.value?.currency ?? defaultCurrency.value);
@@ -88,6 +88,14 @@ export const ALL_APPLICATION_CLOUD_SETTINGS: CategorizedApplicationCloudSettingI
{ settingKey: 'statistics.defaultTrendChartType', settingName: 'Default Chart Type', mobile: false, desktop: true },
{ settingKey: 'statistics.defaultTrendChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Asset Trends Settings',
items: [
{ settingKey: 'statistics.defaultAssetTrendsChartType', settingName: 'Default Chart Type', mobile: false, desktop: true },
{ settingKey: 'statistics.defaultAssetTrendsChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
}
];
@@ -27,6 +27,7 @@ export function useStatisticsSettingPageBase() {
const allCategoricalChartDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.Normal, false));
const allTrendChartTypes = computed<TypeAndDisplayName[]>(() => getAllTrendChartTypes());
const allTrendChartDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.TrendAnalysis, false));
const allAssetTrendsChartDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.AssetTrends, false));
const defaultChartDataType = computed<number>({
get: () => settingsStore.appSettings.statistics.defaultChartDataType,
@@ -63,6 +64,16 @@ export function useStatisticsSettingPageBase() {
set: (value: number) => settingsStore.setStatisticsDefaultTrendChartDateRange(value)
});
const defaultAssetTrendsChartType = computed<number>({
get: () => settingsStore.appSettings.statistics.defaultAssetTrendsChartType,
set: (value: number) => settingsStore.setStatisticsDefaultAssetTrendsChartType(value)
});
const defaultAssetTrendsChartDateRange = computed<number>({
get: () => settingsStore.appSettings.statistics.defaultAssetTrendsChartDataRangeType,
set: (value: number) => settingsStore.setStatisticsDefaultAssetTrendsChartDateRange(value)
});
return {
// computed states
allChartDataTypes,
@@ -72,12 +83,15 @@ export function useStatisticsSettingPageBase() {
allCategoricalChartDateRanges,
allTrendChartTypes,
allTrendChartDateRanges,
allAssetTrendsChartDateRanges,
defaultChartDataType,
defaultTimezoneType,
defaultSortingType,
defaultCategoricalChartType,
defaultCategoricalChartDateRange,
defaultTrendChartType,
defaultTrendChartDateRange
defaultTrendChartDateRange,
defaultAssetTrendsChartType,
defaultAssetTrendsChartDateRange
};
}
@@ -23,7 +23,8 @@ import type {
TransactionCategoricalOverviewAnalysisData,
TransactionCategoricalAnalysisData,
TransactionCategoricalAnalysisDataItem,
TransactionTrendsAnalysisData
TransactionTrendsAnalysisData,
TransactionAssetTrendsAnalysisData
} from '@/models/transaction.ts';
import { limitText, findNameByType, findDisplayNameByType } from '@/lib/common.ts';
@@ -49,6 +50,7 @@ export function useStatisticsTransactionPageBase() {
const loading = ref<boolean>(true);
const analysisType = ref<StatisticsAnalysisType>(StatisticsAnalysisType.CategoricalAnalysis);
const trendDateAggregationType = ref<number>(ChartDateAggregationType.Default.type);
const assetTrendsDateAggregationType = ref<number>(ChartDateAggregationType.Default.type);
const showAccountBalance = computed<boolean>(() => settingsStore.appSettings.showAccountBalance);
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
@@ -60,12 +62,15 @@ export function useStatisticsTransactionPageBase() {
return getAllDateRanges(DateRangeScene.Normal, true);
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return getAllDateRanges(DateRangeScene.TrendAnalysis, true);
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return getAllDateRanges(DateRangeScene.AssetTrends, true);
} else {
return [];
}
});
const allSortingTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsSortingTypes());
const allDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypes());
const allTrendAnalysisDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypes(StatisticsAnalysisType.TrendAnalysis));
const allAssetTrendsDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypes(StatisticsAnalysisType.AssetTrends));
const query = computed<TransactionStatisticsFilter>(() => statisticsStore.transactionStatisticsFilter);
const queryChartDataCategory = computed<string>(() => statisticsStore.categoricalAnalysisChartDataCategory);
@@ -74,6 +79,8 @@ export function useStatisticsTransactionPageBase() {
return query.value.categoricalChartDateType;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return query.value.trendChartDateType;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return query.value.assetTrendsChartDateType;
} else {
return null;
}
@@ -84,6 +91,8 @@ export function useStatisticsTransactionPageBase() {
return formatUnixTimeToLongDateTime(query.value.categoricalChartStartTime);
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthFirstUnixTime(query.value.trendChartStartYearMonth));
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartStartTime);
} else {
return '';
}
@@ -94,21 +103,25 @@ export function useStatisticsTransactionPageBase() {
return formatUnixTimeToLongDateTime(query.value.categoricalChartEndTime);
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return formatUnixTimeToGregorianLikeLongYearMonth(getYearMonthLastUnixTime(query.value.trendChartEndYearMonth));
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return formatUnixTimeToLongDateTime(query.value.assetTrendsChartEndTime);
} else {
return '';
}
});
const queryDateRangeName = computed<string>(() => {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return tt(DateRange.All.name);
}
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return tt(DateRange.All.name);
}
return formatDateRange(query.value.categoricalChartDateType, query.value.categoricalChartStartTime, query.value.categoricalChartEndTime);
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return formatDateRange(query.value.trendChartDateType, getYearMonthFirstUnixTime(query.value.trendChartStartYearMonth), getYearMonthLastUnixTime(query.value.trendChartEndYearMonth));
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return formatDateRange(query.value.assetTrendsChartDateType, query.value.assetTrendsChartStartTime, query.value.assetTrendsChartEndTime);
} else {
return '';
}
@@ -124,7 +137,8 @@ export function useStatisticsTransactionPageBase() {
return tt(querySortingTypeName);
});
const queryTrendDateAggregationTypeName = computed<string>(() => findDisplayNameByType(allDateAggregationTypes.value, trendDateAggregationType.value) || '');
const queryTrendDateAggregationTypeName = computed<string>(() => findDisplayNameByType(allTrendAnalysisDateAggregationTypes.value, trendDateAggregationType.value) || '');
const queryAssetTrendsDateAggregationTypeName = computed<string>(() => findDisplayNameByType(allAssetTrendsDateAggregationTypes.value, assetTrendsDateAggregationType.value) || '');
const isQueryDateRangeChanged = computed<boolean>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
@@ -144,13 +158,31 @@ export function useStatisticsTransactionPageBase() {
}
return !!query.value.trendChartStartYearMonth || !!query.value.trendChartEndYearMonth;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
if (query.value.assetTrendsChartDateType === settingsStore.appSettings.statistics.defaultAssetTrendsChartDataRangeType) {
return false;
}
return !!query.value.assetTrendsChartStartTime || !!query.value.assetTrendsChartEndTime;
} else {
return false;
}
});
const canChangeDateRange = computed<boolean>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return false;
}
return true;
} else {
return true;
}
});
const canShiftDateRange = computed<boolean>(() => {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
if (!canChangeDateRange.value) {
return false;
}
@@ -158,13 +190,31 @@ export function useStatisticsTransactionPageBase() {
return query.value.categoricalChartDateType !== DateRange.All.type;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return query.value.trendChartDateType !== DateRange.All.type;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return query.value.assetTrendsChartDateType !== DateRange.All.type;
} else {
return false;
}
});
const canUseCategoryFilter = computed<boolean>(() => {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return false;
}
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return false;
}
return true;
});
const canUseServerCustomFilter = computed<boolean>(() => {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return false;
}
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return false;
}
@@ -172,25 +222,19 @@ export function useStatisticsTransactionPageBase() {
});
const canUseTagFilter = computed<boolean>(() => {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return false;
}
return true;
return canUseServerCustomFilter.value;
});
const canUseKeywordFilter = computed<boolean>(() => {
if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
return false;
}
return true;
return canUseServerCustomFilter.value;
});
const showAmountInChart = computed<boolean>(() => {
if (!showAccountBalance.value
&& (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)) {
return false;
if (!showAccountBalance.value) {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis
&& (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)) {
return false;
}
}
return true;
@@ -231,7 +275,8 @@ export function useStatisticsTransactionPageBase() {
query.value.chartDataType !== ChartDataType.TotalInflows.type &&
query.value.chartDataType !== ChartDataType.TotalIncome.type &&
query.value.chartDataType !== ChartDataType.NetCashFlow.type &&
query.value.chartDataType !== ChartDataType.NetIncome.type;
query.value.chartDataType !== ChartDataType.NetIncome.type &&
query.value.chartDataType !== ChartDataType.NetWorth.type;
});
const showStackedInTrendsChart = computed<boolean>(() => {
@@ -246,18 +291,22 @@ export function useStatisticsTransactionPageBase() {
query.value.chartDataType === ChartDataType.TotalInflows.type ||
query.value.chartDataType === ChartDataType.TotalIncome.type ||
query.value.chartDataType === ChartDataType.NetCashFlow.type ||
query.value.chartDataType === ChartDataType.NetIncome.type;
query.value.chartDataType === ChartDataType.NetIncome.type ||
query.value.chartDataType === ChartDataType.NetWorth.type;
});
const categoricalOverviewAnalysisData = computed<TransactionCategoricalOverviewAnalysisData | null>(() => statisticsStore.categoricalOverviewAnalysisData);
const categoricalAnalysisData = computed<TransactionCategoricalAnalysisData>(() => statisticsStore.categoricalAnalysisData);
const trendsAnalysisData = computed<TransactionTrendsAnalysisData | null>(() => statisticsStore.trendsAnalysisData);
const assetTrendsData = computed<TransactionAssetTrendsAnalysisData | null>(() => statisticsStore.assetTrendsData);
function canShowCustomDateRange(dateRangeType: number): boolean {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
return query.value.categoricalChartDateType === dateRangeType && !!query.value.categoricalChartStartTime && !!query.value.categoricalChartEndTime;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return query.value.trendChartDateType === dateRangeType && !!query.value.trendChartStartYearMonth && !!query.value.trendChartEndYearMonth;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return query.value.assetTrendsChartDateType === dateRangeType && !!query.value.assetTrendsChartStartTime && !!query.value.assetTrendsChartEndTime;
} else {
return false;
}
@@ -276,11 +325,11 @@ export function useStatisticsTransactionPageBase() {
function getDisplayAmount(amount: number, currency: string, textLimit?: number): string {
const finalAmount = formatAmountToLocalizedNumeralsWithCurrency(amount, currency);
if (!showAccountBalance.value
&& (query.value.chartDataType === ChartDataType.AccountTotalAssets.type
|| query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)
) {
return DISPLAY_HIDDEN_AMOUNT;
if (!showAccountBalance.value) {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis
&& (query.value.chartDataType === ChartDataType.AccountTotalAssets.type || query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)) {
return DISPLAY_HIDDEN_AMOUNT;
}
}
if (textLimit) {
@@ -295,6 +344,7 @@ export function useStatisticsTransactionPageBase() {
loading,
analysisType,
trendDateAggregationType,
assetTrendsDateAggregationType,
// computed states
showAccountBalance,
defaultCurrency,
@@ -302,7 +352,8 @@ export function useStatisticsTransactionPageBase() {
fiscalYearStart,
allDateRanges,
allSortingTypes,
allDateAggregationTypes,
allTrendAnalysisDateAggregationTypes,
allAssetTrendsDateAggregationTypes,
query,
queryChartDataCategory,
queryDateType,
@@ -312,7 +363,9 @@ export function useStatisticsTransactionPageBase() {
queryChartDataTypeName,
querySortingTypeName,
queryTrendDateAggregationTypeName,
queryAssetTrendsDateAggregationTypeName,
isQueryDateRangeChanged,
canChangeDateRange,
canShiftDateRange,
canUseCategoryFilter,
canUseTagFilter,
@@ -326,6 +379,7 @@ export function useStatisticsTransactionPageBase() {
categoricalOverviewAnalysisData,
categoricalAnalysisData,
trendsAnalysisData,
assetTrendsData,
// functions
canShowCustomDateRange,
getTransactionCategoricalAnalysisDataItemDisplayColor,
@@ -29,10 +29,6 @@
v-for="type in allChartTypes"></v-list-item>
<v-divider class="my-2"/>
<v-list-subheader :title="tt('Time Granularity')"/>
<v-list-item :prepend-icon="mdiCalendarTodayOutline"
:append-icon="chartDataDateAggregationType === undefined ? mdiCheck : undefined"
:title="tt('granularity.Daily')"
@click="chartDataDateAggregationType = undefined"></v-list-item>
<v-list-item :key="dateAggregationType.type"
:prepend-icon="chartDataDateAggregationTypeIconMap[dateAggregationType.type]"
:append-icon="chartDataDateAggregationType === dateAggregationType.type ? mdiCheck : undefined"
@@ -360,6 +356,7 @@ const chartTypeIconMap = {
};
const chartDataDateAggregationTypeIconMap = {
[ChartDateAggregationType.Day.type]: mdiCalendarTodayOutline,
[ChartDateAggregationType.Month.type]: mdiCalendarMonthOutline,
[ChartDateAggregationType.Quarter.type]: mdiLayersTripleOutline,
[ChartDateAggregationType.Year.type]: mdiLayersTripleOutline,
@@ -376,7 +373,7 @@ const currentPage = ref<number>(1);
const countPerPage = ref<number>(10);
const showAccountBalanceTrendsCharts = ref<boolean>(false);
const chartType = ref<number>(AccountBalanceTrendChartType.Default.type);
const chartDataDateAggregationType = ref<number | undefined>(undefined);
const chartDataDateAggregationType = ref<number>(ChartDateAggregationType.Day.type);
let rejectFunc: ((reason?: unknown) => void) | null = null;
@@ -453,7 +450,7 @@ function open(options: { accountId: string, startTime: number, endTime: number }
countPerPage.value = 10;
showAccountBalanceTrendsCharts.value = false;
chartType.value = AccountBalanceTrendChartType.Default.type;
chartDataDateAggregationType.value = undefined;
chartDataDateAggregationType.value = ChartDateAggregationType.Day.type;
showState.value = true;
loading.value = true;
@@ -114,6 +114,40 @@
</v-card>
</v-col>
<v-col cols="12">
<v-card :title="tt('Asset Trends Settings')">
<v-form>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
item-value="type"
persistent-placeholder
:label="tt('Default Chart Type')"
:placeholder="tt('Default Chart Type')"
:items="allTrendChartTypes"
v-model="defaultAssetTrendsChartType"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
item-title="displayName"
item-value="type"
persistent-placeholder
:label="tt('Default Date Range')"
:placeholder="tt('Default Date Range')"
:items="allAssetTrendsChartDateRanges"
v-model="defaultAssetTrendsChartDateRange"
/>
</v-col>
</v-row>
</v-card-text>
</v-form>
</v-card>
</v-col>
<v-col cols="12">
<account-filter-settings-card type="statisticsDefault" :auto-save="true" />
</v-col>
@@ -140,13 +174,16 @@ const {
allCategoricalChartDateRanges,
allTrendChartTypes,
allTrendChartDateRanges,
allAssetTrendsChartDateRanges,
defaultChartDataType,
defaultTimezoneType,
defaultSortingType,
defaultCategoricalChartType,
defaultCategoricalChartDateRange,
defaultTrendChartType,
defaultTrendChartDateRange
defaultTrendChartDateRange,
defaultAssetTrendsChartType,
defaultAssetTrendsChartDateRange
} = useStatisticsSettingPageBase();
</script>
+214 -20
View File
@@ -7,7 +7,8 @@
<div class="mx-6 my-4">
<btn-vertical-group :disabled="loading" :buttons="[
{ name: tt('Categorical Analysis'), value: StatisticsAnalysisType.CategoricalAnalysis },
{ name: tt('Trend Analysis'), value: StatisticsAnalysisType.TrendAnalysis }
{ name: tt('Trend Analysis'), value: StatisticsAnalysisType.TrendAnalysis },
{ name: tt('Asset Trends'), value: StatisticsAnalysisType.AssetTrends }
]" v-model="queryAnalysisType" />
</div>
<v-divider />
@@ -59,7 +60,7 @@
<v-main>
<v-window class="d-flex flex-grow-1 disable-tab-transition w-100-window-container" v-model="activeTab">
<v-window-item value="statisticsPage">
<v-card variant="flat" :min-height="queryAnalysisType === StatisticsAnalysisType.TrendAnalysis ? '860' : '760'">
<v-card variant="flat" :min-height="queryAnalysisType === StatisticsAnalysisType.TrendAnalysis || queryAnalysisType === StatisticsAnalysisType.AssetTrends ? '900' : '800'">
<template #title>
<div class="title-and-toolbar d-flex align-center">
<v-btn class="me-3 d-md-none" density="compact" color="default" variant="plain"
@@ -73,7 +74,7 @@
@click="shiftDateRange(-1)"/>
<v-menu location="bottom">
<template #activator="{ props }">
<v-btn :disabled="loading || queryChartDataType === ChartDataType.AccountTotalAssets.type || queryChartDataType === ChartDataType.AccountTotalLiabilities.type"
<v-btn :disabled="loading || !canChangeDateRange"
v-bind="props">{{ queryDateRangeName }}</v-btn>
</template>
<v-list :selected="[queryDateType]">
@@ -110,12 +111,28 @@
<v-list-item class="cursor-pointer" :key="aggregationType.type" :value="aggregationType.type"
:append-icon="(trendDateAggregationType === aggregationType.type ? mdiCheck : undefined)"
:title="aggregationType.displayName"
v-for="aggregationType in allDateAggregationTypes"
v-for="aggregationType in allTrendAnalysisDateAggregationTypes"
@click="setTrendDateAggregationType(aggregationType.type)">
</v-list-item>
</v-list>
</v-menu>
<v-menu location="bottom" v-if="queryAnalysisType === StatisticsAnalysisType.AssetTrends">
<template #activator="{ props }">
<v-btn class="ms-3" color="default" variant="outlined"
:prepend-icon="mdiCalendarRangeOutline" :disabled="loading"
v-bind="props">{{ queryAssetTrendsDateAggregationTypeName }}</v-btn>
</template>
<v-list>
<v-list-item class="cursor-pointer" :key="aggregationType.type" :value="aggregationType.type"
:append-icon="(assetTrendsDateAggregationType === aggregationType.type ? mdiCheck : undefined)"
:title="aggregationType.displayName"
v-for="aggregationType in allAssetTrendsDateAggregationTypes"
@click="setAssetTrendsDateAggregationType(aggregationType.type)">
</v-list-item>
</v-list>
</v-menu>
<v-btn density="compact" color="default" variant="text" size="24"
class="ms-2" :icon="true" :loading="loading" @click="reload(true)">
<template #loader>
@@ -205,10 +222,11 @@
</v-card-text>
<v-card-text class="statistics-overview-title pt-0"
v-else-if="!initing && (
v-else-if="!loading && (
(queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && isQuerySpecialChartType && queryChartDataType === ChartDataType.Overview.type && (!categoricalOverviewAnalysisData || !categoricalOverviewAnalysisData.items || !categoricalOverviewAnalysisData.items.length))
|| (queryAnalysisType === StatisticsAnalysisType.CategoricalAnalysis && !isQuerySpecialChartType && (!categoricalAnalysisData || !categoricalAnalysisData.items || !categoricalAnalysisData.items.length))
|| (queryAnalysisType === StatisticsAnalysisType.TrendAnalysis && (!trendsAnalysisData || !trendsAnalysisData.items || !trendsAnalysisData.items.length))
|| (queryAnalysisType === StatisticsAnalysisType.AssetTrends && (!assetTrendsData || !assetTrendsData.items || !assetTrendsData.items.length))
)">
<span class="statistics-subtitle statistics-overview-empty-tip">{{ tt('No transaction data') }}</span>
</v-card-text>
@@ -345,11 +363,15 @@
</v-card-text>
<v-card-text :class="{ 'readonly': loading }" v-if="queryAnalysisType === StatisticsAnalysisType.TrendAnalysis">
<monthly-trends-chart
<trends-chart
chart-mode="monthly"
:type="queryChartType"
:start-time="undefined"
:end-time="undefined"
:start-year-month="query.trendChartStartYearMonth"
:end-year-month="query.trendChartEndYearMonth"
:sorting-type="querySortingType"
:data-aggregation-type="ChartDataAggregationType.Sum"
:date-aggregation-type="trendDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="[]"
@@ -360,11 +382,15 @@
color-field="color"
v-if="initing"
/>
<monthly-trends-chart
<trends-chart
chart-mode="monthly"
:type="queryChartType"
:start-time="undefined"
:end-time="undefined"
:start-year-month="query.trendChartStartYearMonth"
:end-year-month="query.trendChartEndYearMonth"
:sorting-type="querySortingType"
:data-aggregation-type="ChartDataAggregationType.Sum"
:date-aggregation-type="trendDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="trendsAnalysisData && trendsAnalysisData.items && trendsAnalysisData.items.length ? trendsAnalysisData.items : []"
@@ -384,6 +410,55 @@
@click="onClickTrendChartItem"
/>
</v-card-text>
<v-card-text :class="{ 'readonly': loading }" v-if="queryAnalysisType === StatisticsAnalysisType.AssetTrends">
<trends-chart
chart-mode="daily"
:type="queryChartType"
:start-time="query.assetTrendsChartStartTime"
:end-time="query.assetTrendsChartEndTime"
:start-year-month="undefined"
:end-year-month="undefined"
:sorting-type="querySortingType"
:data-aggregation-type="ChartDataAggregationType.Last"
:date-aggregation-type="assetTrendsDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="[]"
:skeleton="true"
id-field="id"
name-field="name"
value-field="value"
color-field="color"
v-if="initing"
/>
<trends-chart
chart-mode="daily"
:type="queryChartType"
:start-time="query.assetTrendsChartStartTime"
:end-time="query.assetTrendsChartEndTime"
:start-year-month="undefined"
:end-year-month="undefined"
:sorting-type="querySortingType"
:data-aggregation-type="ChartDataAggregationType.Last"
:date-aggregation-type="assetTrendsDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="assetTrendsData && assetTrendsData.items && assetTrendsData.items.length ? assetTrendsData.items : []"
:translate-name="translateNameInTrendsChart"
:show-value="showAmountInChart"
:enable-click-item="true"
:default-currency="defaultCurrency"
:stacked="showStackedInTrendsChart"
:show-total-amount-in-tooltip="showTotalAmountInTrendsChart"
ref="dailyTrendsChart"
id-field="id"
name-field="name"
value-field="totalAmount"
hidden-field="hidden"
display-orders-field="displayOrders"
v-else-if="!initing && assetTrendsData && assetTrendsData.items && assetTrendsData.items.length"
@click="onClickTrendChartItem"
/>
</v-card-text>
</v-card>
</v-window-item>
</v-window>
@@ -429,7 +504,7 @@
<script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue';
import MonthlyTrendsChart from '@/components/desktop/MonthlyTrendsChart.vue';
import TrendsChart from '@/components/desktop/TrendsChart.vue';
import AccountFilterSettingsCard from '@/views/desktop/common/cards/AccountFilterSettingsCard.vue';
import CategoryFilterSettingsCard from '@/views/desktop/common/cards/CategoryFilterSettingsCard.vue';
import TransactionTagFilterSettingsCard from '@/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue';
@@ -450,6 +525,7 @@ import type { TypeAndDisplayName } from '@/core/base.ts';
import { type TextualYearMonth, type TimeRangeAndDateType, DateRangeScene, DateRange } from '@/core/datetime.ts';
import { ThemeType } from '@/core/theme.ts';
import {
ChartDataAggregationType,
StatisticsAnalysisType,
CategoricalChartType,
ChartDataType,
@@ -488,7 +564,7 @@ import {
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
type MonthlyTrendsChartType = InstanceType<typeof MonthlyTrendsChart>;
type TrendsChartType = InstanceType<typeof TrendsChart>;
type ExportDialogType = InstanceType<typeof ExportDialog>;
interface TransactionStatisticsProps {
@@ -505,6 +581,7 @@ interface TransactionStatisticsProps {
initKeyword?: string;
initSortingType?: string,
initTrendDateAggregationType?: string
initAssetTrendsDateAggregationType?: string
}
const props = defineProps<TransactionStatisticsProps>();
@@ -525,12 +602,14 @@ const {
loading,
analysisType,
trendDateAggregationType,
assetTrendsDateAggregationType,
defaultCurrency,
firstDayOfWeek,
fiscalYearStart,
allDateRanges,
allSortingTypes,
allDateAggregationTypes,
allTrendAnalysisDateAggregationTypes,
allAssetTrendsDateAggregationTypes,
query,
queryChartDataCategory,
queryDateType,
@@ -538,6 +617,8 @@ const {
queryEndTime,
queryDateRangeName,
queryTrendDateAggregationTypeName,
queryAssetTrendsDateAggregationTypeName,
canChangeDateRange,
canShiftDateRange,
canUseCategoryFilter,
canUseTagFilter,
@@ -551,6 +632,7 @@ const {
categoricalOverviewAnalysisData,
categoricalAnalysisData,
trendsAnalysisData,
assetTrendsData,
canShowCustomDateRange,
getTransactionCategoricalAnalysisDataItemDisplayColor,
getDisplayAmount
@@ -561,7 +643,8 @@ const transactionCategoriesStore = useTransactionCategoriesStore();
const statisticsStore = useStatisticsStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const monthlyTrendsChart = useTemplateRef<MonthlyTrendsChartType>('monthlyTrendsChart');
const monthlyTrendsChart = useTemplateRef<TrendsChartType>('monthlyTrendsChart');
const dailyTrendsChart = useTemplateRef<TrendsChartType>('dailyTrendsChart');
const exportDialog = useTemplateRef<ExportDialogType>('exportDialog');
const activeTab = ref<string>('statisticsPage');
@@ -582,6 +665,8 @@ const statisticsDataHasData = computed<boolean>(() => {
return !!categoricalAnalysisData.value && !!categoricalAnalysisData.value.items && categoricalAnalysisData.value.items.length > 0;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return !!trendsAnalysisData.value && !!trendsAnalysisData.value.items && trendsAnalysisData.value.items.length > 0 && !!monthlyTrendsChart.value;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return !!assetTrendsData.value && !!assetTrendsData.value.items && assetTrendsData.value.items.length > 0 && !!dailyTrendsChart.value;
}
return false;
@@ -592,6 +677,8 @@ const allChartTypes = computed<TypeAndDisplayName[]>(() => {
return getAllCategoricalChartTypes(true);
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return getAllTrendChartTypes();
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return getAllTrendChartTypes();
} else {
return [];
}
@@ -610,6 +697,8 @@ const queryChartType = computed<number | undefined>({
return query.value.categoricalChartType;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return query.value.trendChartType;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return query.value.assetTrendsChartType;
} else {
return undefined;
}
@@ -654,7 +743,7 @@ const statisticsTextColor = computed<string>(() => {
});
function getFilterLinkUrl(): string {
return `/statistics/transaction?${statisticsStore.getTransactionStatisticsPageParams(analysisType.value, trendDateAggregationType.value)}`;
return `/statistics/transaction?${statisticsStore.getTransactionStatisticsPageParams(analysisType.value, trendDateAggregationType.value, assetTrendsDateAggregationType.value)}`;
}
function getTransactionItemLinkUrl(itemId: string, dateRange?: TimeRangeAndDateType): string {
@@ -718,6 +807,29 @@ function init(initProps: TransactionStatisticsProps): void {
if (initProps.initTrendDateAggregationType) {
trendDateAggregationType.value = parseInt(initProps.initTrendDateAggregationType);
}
} else if (initProps.initAnalysisType === StatisticsAnalysisType.AssetTrends.toString()) {
filter.assetTrendsChartType = initProps.initChartType ? parseInt(initProps.initChartType) : undefined;
filter.assetTrendsChartDateType = initProps.initChartDateType ? parseInt(initProps.initChartDateType) : undefined;
filter.assetTrendsChartStartTime = initProps.initStartTime ? parseInt(initProps.initStartTime) : undefined;
filter.assetTrendsChartEndTime = initProps.initEndTime ? parseInt(initProps.initEndTime) : undefined;
if (filter.assetTrendsChartDateType !== query.value.assetTrendsChartDateType) {
needReload = true;
} else if (filter.assetTrendsChartDateType === DateRange.Custom.type) {
if (filter.assetTrendsChartStartTime !== query.value.assetTrendsChartStartTime
|| filter.assetTrendsChartEndTime !== query.value.assetTrendsChartEndTime) {
needReload = true;
}
}
if (initProps.initAnalysisType !== analysisType.value.toString()) {
analysisType.value = StatisticsAnalysisType.AssetTrends;
needReload = true;
}
if (initProps.initAssetTrendsDateAggregationType) {
assetTrendsDateAggregationType.value = parseInt(initProps.initAssetTrendsDateAggregationType);
}
}
if (!isDefined(initProps.initAnalysisType)) {
@@ -745,6 +857,10 @@ function init(initProps: TransactionStatisticsProps): void {
return statisticsStore.loadTrendAnalysis({
force: false
}) as Promise<unknown>;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return statisticsStore.loadAssetTrends({
force: false
}) as Promise<unknown>;
} else {
return Promise.reject('An error occurred');
}
@@ -780,7 +896,8 @@ function reload(force: boolean): Promise<unknown> | null {
query.value.chartDataType === ChartDataType.TotalInflows.type ||
query.value.chartDataType === ChartDataType.TotalIncome.type ||
query.value.chartDataType === ChartDataType.NetCashFlow.type ||
query.value.chartDataType === ChartDataType.NetIncome.type) {
query.value.chartDataType === ChartDataType.NetIncome.type ||
query.value.chartDataType === ChartDataType.NetWorth.type) {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
dispatchPromise = statisticsStore.loadCategoricalAnalysis({
force: force
@@ -789,12 +906,22 @@ function reload(force: boolean): Promise<unknown> | null {
dispatchPromise = statisticsStore.loadTrendAnalysis({
force: force
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
dispatchPromise = statisticsStore.loadAssetTrends({
force: force
});
}
} else if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
dispatchPromise = accountsStore.loadAllAccounts({
force: force
});
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
dispatchPromise = accountsStore.loadAllAccounts({
force: force
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
dispatchPromise = statisticsStore.loadAssetTrends({
force: force
});
}
}
if (dispatchPromise) {
@@ -822,13 +949,21 @@ function setAnalysisType(type: StatisticsAnalysisType): void {
}
if (!ChartDataType.isAvailableForAnalysisType(query.value.chartDataType, type)) {
let defaultChartDataType: ChartDataType = ChartDataType.Default;
if (type === StatisticsAnalysisType.AssetTrends) {
defaultChartDataType = ChartDataType.DefaultForAssetTrends;
}
statisticsStore.updateTransactionStatisticsFilter({
chartDataType: ChartDataType.Default.type
chartDataType: defaultChartDataType.type
});
}
if (analysisType.value !== StatisticsAnalysisType.TrendAnalysis) {
trendDateAggregationType.value = ChartDateAggregationType.Month.type;
if (analysisType.value !== StatisticsAnalysisType.TrendAnalysis && type === StatisticsAnalysisType.TrendAnalysis) {
trendDateAggregationType.value = ChartDateAggregationType.Default.type;
} else if (analysisType.value !== StatisticsAnalysisType.AssetTrends && type === StatisticsAnalysisType.AssetTrends) {
assetTrendsDateAggregationType.value = ChartDateAggregationType.Default.type;
}
analysisType.value = type;
@@ -848,6 +983,10 @@ function setChartType(type?: number): void {
changed = statisticsStore.updateTransactionStatisticsFilter({
trendChartType: type
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartType: type
});
}
if (changed) {
@@ -888,6 +1027,15 @@ function setTrendDateAggregationType(type: number): void {
}
}
function setAssetTrendsDateAggregationType(type: number): void {
const changed = assetTrendsDateAggregationType.value !== type;
assetTrendsDateAggregationType.value = type;
if (changed) {
router.push(getFilterLinkUrl());
}
}
function setDateFilter(dateType: number): void {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (dateType === DateRange.Custom.type) { // Custom
@@ -903,6 +1051,13 @@ function setDateFilter(dateType: number): void {
} else if (query.value.trendChartDateType === dateType) {
return;
}
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
if (dateType === DateRange.Custom.type) { // Custom
showCustomDateRangeDialog.value = true;
return;
} else if (query.value.assetTrendsChartDateType === dateType) {
return;
}
}
const dateRange = getDateRangeByDateType(dateType, firstDayOfWeek.value, fiscalYearStart.value);
@@ -925,6 +1080,12 @@ function setDateFilter(dateType: number): void {
trendChartStartYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(dateRange.minTime),
trendChartEndYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(dateRange.maxTime)
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: dateRange.dateType,
assetTrendsChartStartTime: dateRange.minTime,
assetTrendsChartEndTime: dateRange.maxTime
});
}
if (changed) {
@@ -961,6 +1122,16 @@ function setCustomDateFilter(startTime: number | TextualYearMonth, endTime: numb
});
showCustomMonthRangeDialog.value = false;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends && isNumber(startTime) && isNumber(endTime)) {
const chartDateType = getDateTypeByDateRange(startTime, endTime, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.AssetTrends);
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: chartDateType,
assetTrendsChartStartTime: startTime,
assetTrendsChartEndTime: endTime
});
showCustomDateRangeDialog.value = false;
}
if (changed) {
@@ -993,6 +1164,18 @@ function shiftDateRange(scale: number): void {
trendChartStartYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(newDateRange.minTime),
trendChartEndYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(newDateRange.maxTime)
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
if (query.value.assetTrendsChartDateType === DateRange.All.type) {
return;
}
const newDateRange = getShiftedDateRangeAndDateType(query.value.assetTrendsChartStartTime, query.value.assetTrendsChartEndTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.AssetTrends);
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: newDateRange.dateType,
assetTrendsChartStartTime: newDateRange.minTime,
assetTrendsChartEndTime: newDateRange.maxTime
});
}
if (changed) {
@@ -1033,6 +1216,10 @@ function setTagFilter(changed: boolean): void {
}
function setKeywordFilter(keyword: string): void {
if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return;
}
if (query.value.keyword === keyword) {
return;
}
@@ -1078,6 +1265,12 @@ function exportResults(): void {
headers: exportData.headers || [],
data: exportData.data || []
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends && assetTrendsData.value && assetTrendsData.value.items && dailyTrendsChart.value) {
const exportData = dailyTrendsChart.value.exportData();
exportDialog.value?.open({
headers: exportData.headers || [],
data: exportData.data || []
});
}
}
@@ -1125,7 +1318,8 @@ onBeforeRouteUpdate((to) => {
initTagFilterType: (to.query['tagFilterType'] as string | null) || undefined,
initKeyword: (to.query['keyword'] as string | null) || undefined,
initSortingType: (to.query['sortingType'] as string | null) || undefined,
initTrendDateAggregationType: (to.query['trendDateAggregationType'] as string | null) || undefined
initTrendDateAggregationType: (to.query['trendDateAggregationType'] as string | null) || undefined,
initAssetTrendsDateAggregationType: (to.query['assetTrendsDateAggregationType'] as string | null) || undefined
});
} else {
init({});
+5 -1
View File
@@ -1795,12 +1795,16 @@ init(props);
<style>
.transaction-keyword-filter .v-input--density-compact {
--v-input-control-height: 36px !important;
--v-input-control-height: 38px !important;
--v-input-padding-top: 5px !important;
--v-input-padding-bottom: 5px !important;
--v-input-chips-margin-top: 0px !important;
--v-input-chips-margin-bottom: 0px !important;
inline-size: 20rem;
.v-field__input {
min-block-size: 38px !important;
}
}
.transaction-list-datetime-range {
@@ -281,14 +281,6 @@
<f7-popover class="chart-data-date-aggregation-type-popover-menu"
v-model:opened="showChartDataDateAggregationTypePopover">
<f7-list dividers>
<f7-list-item :title="tt('granularity.Daily')"
:class="{ 'list-item-selected': chartDataDateAggregationType === undefined }"
key="daily"
@click="setChartDataDateAggregationType(undefined)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="chartDataDateAggregationType === undefined"></f7-icon>
</template>
</f7-list-item>
<f7-list-item :title="dateAggregationType.displayName"
:class="{ 'list-item-selected': chartDataDateAggregationType === dateAggregationType.type }"
:key="dateAggregationType.type"
@@ -358,6 +350,7 @@ import { TextDirection } from '@/core/text.ts';
import { type TimeRangeAndDateType, DateRange, DateRangeScene } from '@/core/datetime.ts';
import { AccountType } from '@/core/account.ts';
import { TransactionType } from '@/core/transaction.ts';
import { ChartDateAggregationType } from '@/core/statistics.ts';
import { TRANSACTION_MIN_AMOUNT, TRANSACTION_MAX_AMOUNT } from '@/consts/transaction.ts';
import { type TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
@@ -436,7 +429,7 @@ const loading = ref<boolean>(false);
const loadingError = ref<unknown | null>(null);
const queryDateRangeType = ref<number>(DateRange.ThisMonth.type);
const showAccountBalanceTrendsCharts = ref<boolean>(false);
const chartDataDateAggregationType = ref<number | undefined>(undefined);
const chartDataDateAggregationType = ref<number>(ChartDateAggregationType.Day.type);
const transactionToDelete = ref<TransactionReconciliationStatementResponseItem | null>(null);
const newClosingBalance = ref<number>(0);
const showDisplayModePopover = ref<boolean>(false);
@@ -489,10 +482,6 @@ const allReconciliationStatementVirtualListItems = computed<ReconciliationStatem
});
const chartDataDateAggregationTypeDisplayName = computed<string>(() => {
if (chartDataDateAggregationType.value === undefined) {
return tt('granularity.Daily');
}
return findDisplayNameByType(allDateAggregationTypes.value, chartDataDateAggregationType.value) || tt('Unknown');
});
@@ -681,7 +670,7 @@ function removeTransaction(transaction: TransactionReconciliationStatementRespon
});
}
function setChartDataDateAggregationType(type: number | undefined): void {
function setChartDataDateAggregationType(type: number): void {
chartDataDateAggregationType.value = type;
showChartDataDateAggregationTypePopover.value = false;
}
+26 -1
View File
@@ -128,6 +128,28 @@
</list-item-selection-popup>
</f7-list-item>
</f7-list>
<f7-block-title>{{ tt('Asset Trends Settings') }}</f7-block-title>
<f7-list strong inset dividers>
<f7-list-item
link="#"
:title="tt('Default Date Range')"
:after="findDisplayNameByType(allAssetTrendsChartDateRanges, defaultAssetTrendsChartDateRange)"
@click="showDefaultAssetTrendsChartDateRangePopup = true"
>
<list-item-selection-popup value-type="item"
key-field="type" value-field="type"
title-field="displayName"
:title="tt('Default Date Range')"
:enable-filter="true"
:filter-placeholder="tt('Date Range')"
:filter-no-items-text="tt('No results')"
:items="allAssetTrendsChartDateRanges"
v-model:show="showDefaultAssetTrendsChartDateRangePopup"
v-model="defaultAssetTrendsChartDateRange">
</list-item-selection-popup>
</f7-list-item>
</f7-list>
</f7-page>
</template>
@@ -145,12 +167,14 @@ const {
allCategoricalChartTypes,
allCategoricalChartDateRanges,
allTrendChartDateRanges,
allAssetTrendsChartDateRanges,
defaultChartDataType,
defaultTimezoneType,
defaultSortingType,
defaultCategoricalChartType,
defaultCategoricalChartDateRange,
defaultTrendChartDateRange
defaultTrendChartDateRange,
defaultAssetTrendsChartDateRange
} = useStatisticsSettingPageBase();
import { findDisplayNameByType } from '@/lib/common.ts';
@@ -161,4 +185,5 @@ const showDefaultSortingTypePopup = ref<boolean>(false);
const showDefaultCategoricalChartTypePopup = ref<boolean>(false);
const showDefaultCategoricalChartDateRangePopup = ref<boolean>(false);
const showDefaultTrendChartDateRangePopup = ref<boolean>(false);
const showDefaultAssetTrendsChartDateRangePopup = ref<boolean>(false);
</script>
+150 -9
View File
@@ -45,6 +45,20 @@
</template>
</f7-list-item>
</f7-list-group>
<f7-list-group>
<f7-list-item group-title>
<small>{{ tt('Asset Trends') }}</small>
</f7-list-item>
<f7-list-item :title="tt(dataType.name)"
:class="{ 'list-item-selected': analysisType === StatisticsAnalysisType.AssetTrends && query.chartDataType === dataType.type }"
:key="dataType.type"
v-for="dataType in ChartDataType.values(StatisticsAnalysisType.AssetTrends)"
@click="setChartDataType(StatisticsAnalysisType.AssetTrends, dataType.type)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="analysisType === StatisticsAnalysisType.AssetTrends && query.chartDataType === dataType.type"></f7-icon>
</template>
</f7-list-item>
</f7-list-group>
</f7-list>
</f7-popover>
@@ -203,11 +217,15 @@
</div>
</f7-card-header>
<f7-card-content style="margin-top: -14px" :padding="false">
<monthly-trends-bar-chart
<trends-bar-chart
chart-mode="monthly"
:loading="loading || reloading"
:start-time="undefined"
:end-time="undefined"
:start-year-month="query.trendChartStartYearMonth"
:end-year-month="query.trendChartEndYearMonth"
:sorting-type="query.sortingType"
:data-aggregation-type="ChartDataAggregationType.Sum"
:date-aggregation-type="trendDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="trendsAnalysisData && trendsAnalysisData.items && trendsAnalysisData.items.length ? trendsAnalysisData.items : []"
@@ -224,6 +242,42 @@
</f7-card-content>
</f7-card>
<f7-card v-else-if="analysisType === StatisticsAnalysisType.AssetTrends">
<f7-card-header class="no-border display-block">
<div class="statistics-chart-header display-flex full-line justify-content-space-between">
<div></div>
<div class="align-self-flex-end">
<span style="margin-inline-end: 4px;">{{ tt('Sort by') }}</span>
<f7-link href="#" popover-open=".sorting-type-popover-menu">{{ querySortingTypeName }}</f7-link>
</div>
</div>
</f7-card-header>
<f7-card-content style="margin-top: -14px" :padding="false">
<trends-bar-chart
chart-mode="daily"
:loading="loading || reloading"
:start-time="query.assetTrendsChartStartTime"
:end-time="query.assetTrendsChartEndTime"
:start-year-month="undefined"
:end-year-month="undefined"
:sorting-type="query.sortingType"
:data-aggregation-type="ChartDataAggregationType.Last"
:date-aggregation-type="assetTrendsDateAggregationType"
:fiscal-year-start="fiscalYearStart"
:items="assetTrendsData && assetTrendsData.items && assetTrendsData.items.length ? assetTrendsData.items : []"
:stacked="showStackedInTrendsChart"
:translate-name="translateNameInTrendsChart"
:default-currency="defaultCurrency"
id-field="id"
name-field="name"
value-field="totalAmount"
hidden-field="hidden"
display-orders-field="displayOrders"
@click="onClickTrendChartItem"
/>
</f7-card-content>
</f7-card>
<f7-popover class="sorting-type-popover-menu"
v-model:opened="showSortingTypePopover">
<f7-list dividers>
@@ -243,7 +297,7 @@
<f7-link :class="{ 'disabled': reloading || !canShiftDateRange }" @click="shiftDateRange(-1)">
<f7-icon class="icon-with-direction" f7="arrow_left_square"></f7-icon>
</f7-link>
<f7-link :class="{ 'tabbar-text-with-ellipsis': true, 'disabled': reloading || query.chartDataType === ChartDataType.AccountTotalAssets.type || query.chartDataType === ChartDataType.AccountTotalLiabilities.type }" popover-open=".date-popover-menu">
<f7-link :class="{ 'tabbar-text-with-ellipsis': true, 'disabled': reloading || !canChangeDateRange }" popover-open=".date-popover-menu">
<span :class="{ 'tabbar-item-changed': isQueryDateRangeChanged }">{{ queryDateRangeName }}</span>
</f7-link>
<f7-link :class="{ 'disabled': reloading || !canShiftDateRange }" @click="shiftDateRange(1)">
@@ -253,6 +307,10 @@
v-if="analysisType === StatisticsAnalysisType.TrendAnalysis">
<span :class="{ 'tabbar-item-changed': trendDateAggregationType !== ChartDateAggregationType.Default.type }">{{ queryTrendDateAggregationTypeName }}</span>
</f7-link>
<f7-link :class="{ 'tabbar-text-with-ellipsis': true, 'disabled': reloading }" popover-open=".date-aggregation-popover-menu"
v-if="analysisType === StatisticsAnalysisType.AssetTrends">
<span :class="{ 'tabbar-item-changed': assetTrendsDateAggregationType !== ChartDateAggregationType.Default.type }">{{ queryAssetTrendsDateAggregationTypeName }}</span>
</f7-link>
<f7-link class="tabbar-text-with-ellipsis" :key="chartType.type"
v-for="chartType in allChartTypes" @click="setChartType(chartType.type)">
<span :class="{ 'tabbar-item-changed': queryChartType === chartType.type }">{{ chartType.displayName }}</span>
@@ -286,17 +344,28 @@
<f7-popover class="date-aggregation-popover-menu"
v-model:opened="showDateAggregationPopover"
@popover:open="scrollPopoverToSelectedItem">
<f7-list dividers>
<f7-list dividers v-if="analysisType === StatisticsAnalysisType.TrendAnalysis">
<f7-list-item :title="aggregationType.displayName"
:class="{ 'list-item-selected': trendDateAggregationType === aggregationType.type }"
:key="aggregationType.type"
v-for="aggregationType in allDateAggregationTypes"
v-for="aggregationType in allTrendAnalysisDateAggregationTypes"
@click="setTrendDateAggregationType(aggregationType.type)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="trendDateAggregationType === aggregationType.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
<f7-list dividers v-else-if="analysisType === StatisticsAnalysisType.AssetTrends">
<f7-list-item :title="aggregationType.displayName"
:class="{ 'list-item-selected': assetTrendsDateAggregationType === aggregationType.type }"
:key="aggregationType.type"
v-for="aggregationType in allAssetTrendsDateAggregationTypes"
@click="setAssetTrendsDateAggregationType(aggregationType.type)">
<template #after>
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="assetTrendsDateAggregationType === aggregationType.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</f7-popover>
<date-range-selection-sheet :title="tt('Custom Date Range')"
@@ -348,6 +417,7 @@ import type { TypeAndDisplayName } from '@/core/base.ts';
import { TextDirection } from '@/core/text.ts';
import { type TextualYearMonth, type TimeRangeAndDateType, DateRangeScene, DateRange } from '@/core/datetime.ts';
import {
ChartDataAggregationType,
StatisticsAnalysisType,
CategoricalChartType,
ChartDataType,
@@ -383,12 +453,14 @@ const {
loading,
analysisType,
trendDateAggregationType,
assetTrendsDateAggregationType,
defaultCurrency,
firstDayOfWeek,
fiscalYearStart,
allDateRanges,
allSortingTypes,
allDateAggregationTypes,
allTrendAnalysisDateAggregationTypes,
allAssetTrendsDateAggregationTypes,
query,
queryChartDataCategory,
queryDateType,
@@ -398,7 +470,9 @@ const {
queryChartDataTypeName,
querySortingTypeName,
queryTrendDateAggregationTypeName,
queryAssetTrendsDateAggregationTypeName,
isQueryDateRangeChanged,
canChangeDateRange,
canShiftDateRange,
canUseCategoryFilter,
canUseTagFilter,
@@ -410,6 +484,7 @@ const {
translateNameInTrendsChart,
categoricalAnalysisData,
trendsAnalysisData,
assetTrendsData,
canShowCustomDateRange,
getTransactionCategoricalAnalysisDataItemDisplayColor,
getDisplayAmount
@@ -445,6 +520,8 @@ const queryChartType = computed<number | undefined>({
return query.value.categoricalChartType;
} else if (analysisType.value === StatisticsAnalysisType.TrendAnalysis) {
return query.value.trendChartType;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return query.value.assetTrendsChartType;
} else {
return undefined;
}
@@ -473,6 +550,10 @@ function init(): void {
return statisticsStore.loadTrendAnalysis({
force: false
}) as Promise<unknown>;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return statisticsStore.loadAssetTrends({
force: false
}) as Promise<unknown>;
} else {
return Promise.reject('An error occurred');
}
@@ -507,7 +588,8 @@ function reload(done?: () => void): void {
query.value.chartDataType === ChartDataType.TotalInflows.type ||
query.value.chartDataType === ChartDataType.TotalIncome.type ||
query.value.chartDataType === ChartDataType.NetCashFlow.type ||
query.value.chartDataType === ChartDataType.NetIncome.type) {
query.value.chartDataType === ChartDataType.NetIncome.type ||
query.value.chartDataType === ChartDataType.NetWorth.type) {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
dispatchPromise = statisticsStore.loadCategoricalAnalysis({
force: force
@@ -516,12 +598,22 @@ function reload(done?: () => void): void {
dispatchPromise = statisticsStore.loadTrendAnalysis({
force: force
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
dispatchPromise = statisticsStore.loadAssetTrends({
force: force
});
}
} else if (query.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
query.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
dispatchPromise = accountsStore.loadAllAccounts({
force: force
});
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
dispatchPromise = accountsStore.loadAllAccounts({
force: force
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
dispatchPromise = statisticsStore.loadAssetTrends({
force: force
});
}
}
if (dispatchPromise) {
@@ -556,6 +648,10 @@ function setChartType(type?: number): void {
statisticsStore.updateTransactionStatisticsFilter({
trendChartType: type
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartType: type
});
}
}
@@ -603,6 +699,11 @@ function setTrendDateAggregationType(type: number): void {
showDateAggregationPopover.value = false;
}
function setAssetTrendsDateAggregationType(type: number): void {
assetTrendsDateAggregationType.value = type;
showDateAggregationPopover.value = false;
}
function setDateFilter(dateType: number): void {
if (analysisType.value === StatisticsAnalysisType.CategoricalAnalysis) {
if (dateType === DateRange.Custom.type) { // Custom
@@ -620,6 +721,14 @@ function setDateFilter(dateType: number): void {
} else if (query.value.trendChartDateType === dateType) {
return;
}
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
if (dateType === DateRange.Custom.type) { // Custom
showCustomDateRangeSheet.value = true;
showDatePopover.value = false;
return;
} else if (query.value.assetTrendsChartDateType === dateType) {
return;
}
}
const dateRange = getDateRangeByDateType(dateType, firstDayOfWeek.value, fiscalYearStart.value);
@@ -642,6 +751,12 @@ function setDateFilter(dateType: number): void {
trendChartStartYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(dateRange.minTime),
trendChartEndYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(dateRange.maxTime)
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: dateRange.dateType,
assetTrendsChartStartTime: dateRange.minTime,
assetTrendsChartEndTime: dateRange.maxTime
});
}
showDatePopover.value = false;
@@ -678,6 +793,16 @@ function setCustomDateFilter(startTime: number | TextualYearMonth, endTime: numb
});
showCustomMonthRangeSheet.value = false;
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends && isNumber(startTime) && isNumber(endTime)) {
const chartDateType = getDateTypeByDateRange(startTime, endTime, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.AssetTrends);
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: chartDateType,
assetTrendsChartStartTime: startTime,
assetTrendsChartEndTime: endTime
});
showCustomDateRangeSheet.value = false;
}
if (changed) {
@@ -708,6 +833,18 @@ function shiftDateRange(scale: number): void {
trendChartStartYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(newDateRange.minTime),
trendChartEndYearMonth: getGregorianCalendarYearAndMonthFromUnixTime(newDateRange.maxTime)
});
} else if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
if (query.value.assetTrendsChartDateType === DateRange.All.type) {
return;
}
const newDateRange = getShiftedDateRangeAndDateType(query.value.assetTrendsChartStartTime, query.value.assetTrendsChartEndTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.AssetTrends);
changed = statisticsStore.updateTransactionStatisticsFilter({
assetTrendsChartDateType: newDateRange.dateType,
assetTrendsChartStartTime: newDateRange.minTime,
assetTrendsChartEndTime: newDateRange.maxTime
});
}
if (changed) {
@@ -728,6 +865,10 @@ function filterTags(): void {
}
function filterDescription(): void {
if (analysisType.value === StatisticsAnalysisType.AssetTrends) {
return;
}
showPrompt('Filter transaction description', query.value.keyword, value => {
if (query.value.keyword === value) {
return;