mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 07:57:33 +08:00
add asset trends in statistics & analysis (#314)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user