add asset trends in statistics & analysis (#314)
This commit is contained in:
@@ -385,6 +385,7 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.GET("/transactions/reconciliation_statements.json", bindApi(api.Transactions.TransactionReconciliationStatementHandler))
|
||||
apiV1Route.GET("/transactions/statistics.json", bindApi(api.Transactions.TransactionStatisticsHandler))
|
||||
apiV1Route.GET("/transactions/statistics/trends.json", bindApi(api.Transactions.TransactionStatisticsTrendsHandler))
|
||||
apiV1Route.GET("/transactions/statistics/asset_trends.json", bindApi(api.Transactions.TransactionStatisticsAssetTrendsHandler))
|
||||
apiV1Route.GET("/transactions/amounts.json", bindApi(api.Transactions.TransactionAmountsHandler))
|
||||
apiV1Route.GET("/transactions/get.json", bindApi(api.Transactions.TransactionGetHandler))
|
||||
apiV1Route.POST("/transactions/add.json", bindApi(api.Transactions.TransactionCreateHandler))
|
||||
|
||||
@@ -340,7 +340,7 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(reconciliationStatementRequest.StartTime)
|
||||
}
|
||||
|
||||
transactionsWithAccountBalance, totalInflows, totalOutflows, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId, account.Category)
|
||||
transactionsWithAccountBalance, totalInflows, totalOutflows, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsInOneAccountWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId, account.Category)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", reconciliationStatementRequest.StartTime, reconciliationStatementRequest.EndTime, uid, err.Error())
|
||||
@@ -532,6 +532,71 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
|
||||
return statisticTrendsResp, nil
|
||||
}
|
||||
|
||||
// TransactionStatisticsAssetTrendsHandler returns transaction statistics asset trends of current user
|
||||
func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var statisticAssetTrendsReq models.TransactionStatisticAssetTrendsRequest
|
||||
err := c.ShouldBindQuery(&statisticAssetTrendsReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
utcOffset, err := c.GetClientTimezoneOffset()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] cannot get client timezone offset, because %s", err.Error())
|
||||
return nil, errs.ErrClientTimezoneOffsetInvalid
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxTransactionTime := int64(0)
|
||||
|
||||
if statisticAssetTrendsReq.EndTime > 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(statisticAssetTrendsReq.EndTime)
|
||||
}
|
||||
|
||||
minTransactionTime := int64(0)
|
||||
|
||||
if statisticAssetTrendsReq.StartTime > 0 {
|
||||
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(statisticAssetTrendsReq.StartTime)
|
||||
}
|
||||
|
||||
accountDailyBalances, err := a.transactions.GetAllAccountsDailyOpeningAndClosingBalance(c, uid, maxTransactionTime, minTransactionTime, utcOffset)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", statisticAssetTrendsReq.StartTime, statisticAssetTrendsReq.EndTime, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
statisticAssetTrendsResp := make(models.TransactionStatisticAssetTrendsResponseItemSlice, 0)
|
||||
|
||||
for yearMonthDay, dailyAccountBalances := range accountDailyBalances {
|
||||
dailyStatisticResp := &models.TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: yearMonthDay / 10000,
|
||||
Month: (yearMonthDay % 10000) / 100,
|
||||
Day: yearMonthDay % 100,
|
||||
Items: make([]*models.TransactionStatisticAssetTrendsResponseDataItem, len(dailyAccountBalances)),
|
||||
}
|
||||
|
||||
for i := 0; i < len(dailyAccountBalances); i++ {
|
||||
accountBalance := dailyAccountBalances[i]
|
||||
dailyStatisticResp.Items[i] = &models.TransactionStatisticAssetTrendsResponseDataItem{
|
||||
AccountId: accountBalance.AccountId,
|
||||
AccountOpeningBalance: accountBalance.AccountOpeningBalance,
|
||||
AccountClosingBalance: accountBalance.AccountClosingBalance,
|
||||
}
|
||||
}
|
||||
|
||||
statisticAssetTrendsResp = append(statisticAssetTrendsResp, dailyStatisticResp)
|
||||
}
|
||||
|
||||
sort.Sort(statisticAssetTrendsResp)
|
||||
|
||||
return statisticAssetTrendsResp, nil
|
||||
}
|
||||
|
||||
// TransactionAmountsHandler returns transaction amounts of current user
|
||||
func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var transactionAmountsReq models.TransactionAmountsRequest
|
||||
|
||||
@@ -275,6 +275,12 @@ type TransactionStatisticTrendsRequest struct {
|
||||
UseTransactionTimezone bool `form:"use_transaction_timezone"`
|
||||
}
|
||||
|
||||
// TransactionStatisticAssetTrendsRequest represents all parameters of transaction statistic asset trends request
|
||||
type TransactionStatisticAssetTrendsRequest struct {
|
||||
StartTime int64 `form:"start_time"`
|
||||
EndTime int64 `form:"end_time"`
|
||||
}
|
||||
|
||||
// TransactionAmountsRequest represents all parameters of transaction amounts request
|
||||
type TransactionAmountsRequest struct {
|
||||
Query string `form:"query"`
|
||||
@@ -403,6 +409,21 @@ type TransactionStatisticTrendsResponseItem struct {
|
||||
Items []*TransactionStatisticResponseItem `json:"items"`
|
||||
}
|
||||
|
||||
// TransactionStatisticAssetTrendsResponseItem represents the data within each statistic interval
|
||||
type TransactionStatisticAssetTrendsResponseItem struct {
|
||||
Year int32 `json:"year"`
|
||||
Month int32 `json:"month"`
|
||||
Day int32 `json:"day"`
|
||||
Items []*TransactionStatisticAssetTrendsResponseDataItem `json:"items"`
|
||||
}
|
||||
|
||||
// TransactionStatisticAssetTrendsResponseDataItem represents an asset trends data item
|
||||
type TransactionStatisticAssetTrendsResponseDataItem struct {
|
||||
AccountId int64 `json:"accountId,string"`
|
||||
AccountOpeningBalance int64 `json:"accountOpeningBalance"`
|
||||
AccountClosingBalance int64 `json:"accountClosingBalance"`
|
||||
}
|
||||
|
||||
// TransactionAmountsResponseItem represents an item of transaction amounts
|
||||
type TransactionAmountsResponseItem struct {
|
||||
StartTime int64 `json:"startTime"`
|
||||
@@ -600,6 +621,32 @@ func (s TransactionStatisticTrendsResponseItemSlice) Less(i, j int) bool {
|
||||
return s[i].Month < s[j].Month
|
||||
}
|
||||
|
||||
// TransactionStatisticAssetTrendsResponseItemSlice represents the slice data structure of TransactionStatisticAssetTrendsResponseItem
|
||||
type TransactionStatisticAssetTrendsResponseItemSlice []*TransactionStatisticAssetTrendsResponseItem
|
||||
|
||||
// Len returns the count of items
|
||||
func (s TransactionStatisticAssetTrendsResponseItemSlice) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps two items
|
||||
func (s TransactionStatisticAssetTrendsResponseItemSlice) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less reports whether the first item is less than the second one
|
||||
func (s TransactionStatisticAssetTrendsResponseItemSlice) Less(i, j int) bool {
|
||||
if s[i].Year != s[j].Year {
|
||||
return s[i].Year < s[j].Year
|
||||
}
|
||||
|
||||
if s[i].Month != s[j].Month {
|
||||
return s[i].Month < s[j].Month
|
||||
}
|
||||
|
||||
return s[i].Day < s[j].Day
|
||||
}
|
||||
|
||||
// TransactionAmountsResponseItemAmountInfoSlice represents the slice data structure of TransactionAmountsResponseItemAmountInfo
|
||||
type TransactionAmountsResponseItemAmountInfoSlice []*TransactionAmountsResponseItemAmountInfo
|
||||
|
||||
|
||||
@@ -164,6 +164,61 @@ func TestTransactionStatisticTrendsResponseItemSliceLess(t *testing.T) {
|
||||
assert.Equal(t, int32(9), transactionTrendsSlice[4].Month)
|
||||
}
|
||||
|
||||
func TestTransactionStatisticAssetTrendsResponseItemSliceLess(t *testing.T) {
|
||||
var transactionTrendsSlice TransactionStatisticAssetTrendsResponseItemSlice
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2024,
|
||||
Month: 9,
|
||||
Day: 1,
|
||||
})
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2024,
|
||||
Month: 9,
|
||||
Day: 2,
|
||||
})
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2024,
|
||||
Month: 10,
|
||||
Day: 1,
|
||||
})
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2022,
|
||||
Month: 10,
|
||||
Day: 1,
|
||||
})
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2023,
|
||||
Month: 1,
|
||||
Day: 1,
|
||||
})
|
||||
transactionTrendsSlice = append(transactionTrendsSlice, &TransactionStatisticAssetTrendsResponseItem{
|
||||
Year: 2024,
|
||||
Month: 2,
|
||||
Day: 2,
|
||||
})
|
||||
|
||||
sort.Sort(transactionTrendsSlice)
|
||||
|
||||
assert.Equal(t, int32(2022), transactionTrendsSlice[0].Year)
|
||||
assert.Equal(t, int32(10), transactionTrendsSlice[0].Month)
|
||||
assert.Equal(t, int32(1), transactionTrendsSlice[0].Day)
|
||||
assert.Equal(t, int32(2023), transactionTrendsSlice[1].Year)
|
||||
assert.Equal(t, int32(1), transactionTrendsSlice[1].Month)
|
||||
assert.Equal(t, int32(1), transactionTrendsSlice[1].Day)
|
||||
assert.Equal(t, int32(2024), transactionTrendsSlice[2].Year)
|
||||
assert.Equal(t, int32(2), transactionTrendsSlice[2].Month)
|
||||
assert.Equal(t, int32(2), transactionTrendsSlice[2].Day)
|
||||
assert.Equal(t, int32(2024), transactionTrendsSlice[3].Year)
|
||||
assert.Equal(t, int32(9), transactionTrendsSlice[3].Month)
|
||||
assert.Equal(t, int32(1), transactionTrendsSlice[3].Day)
|
||||
assert.Equal(t, int32(2024), transactionTrendsSlice[4].Year)
|
||||
assert.Equal(t, int32(9), transactionTrendsSlice[4].Month)
|
||||
assert.Equal(t, int32(2), transactionTrendsSlice[4].Day)
|
||||
assert.Equal(t, int32(2024), transactionTrendsSlice[5].Year)
|
||||
assert.Equal(t, int32(10), transactionTrendsSlice[5].Month)
|
||||
assert.Equal(t, int32(1), transactionTrendsSlice[5].Day)
|
||||
}
|
||||
|
||||
func TestTransactionAmountsResponseItemAmountInfoSliceLess(t *testing.T) {
|
||||
var amountInfoSlice TransactionAmountsResponseItemAmountInfoSlice
|
||||
amountInfoSlice = append(amountInfoSlice, &TransactionAmountsResponseItemAmountInfo{
|
||||
|
||||
@@ -43,6 +43,8 @@ var ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES = map[string]UserApplicationClo
|
||||
"statistics.defaultCategoricalChartDataRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
|
||||
"statistics.defaultTrendChartType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
|
||||
"statistics.defaultTrendChartDataRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
|
||||
"statistics.defaultAssetTrendsChartType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
|
||||
"statistics.defaultAssetTrendsChartDataRangeType": USER_APPLICATION_CLOUD_SETTING_TYPE_NUMBER,
|
||||
}
|
||||
|
||||
// UserApplicationCloudSetting represents user application cloud setting stored in database
|
||||
|
||||
@@ -107,8 +107,8 @@ func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int
|
||||
return allTransactions, nil
|
||||
}
|
||||
|
||||
// GetAllTransactionsWithAccountBalanceByMaxTime returns account statement within time range
|
||||
func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64, accountCategory models.AccountCategory) ([]*models.TransactionWithAccountBalance, int64, int64, int64, int64, error) {
|
||||
// GetAllTransactionsInOneAccountWithAccountBalanceByMaxTime returns account statement within time range
|
||||
func (s *TransactionService) GetAllTransactionsInOneAccountWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64, accountCategory models.AccountCategory) ([]*models.TransactionWithAccountBalance, int64, int64, int64, int64, error) {
|
||||
if maxTransactionTime <= 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
|
||||
}
|
||||
@@ -158,7 +158,7 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||
accumulatedBalance = accumulatedBalance + transaction.Amount
|
||||
} else {
|
||||
log.Errorf(c, "[transactions.GetAllTransactionsWithAccountBalanceByMaxTime] trasaction type (%d) is invalid (id:%d)", transaction.TransactionId, transaction.Type)
|
||||
log.Errorf(c, "[transactions.GetAllTransactionsInOneAccountWithAccountBalanceByMaxTime] trasaction type (%d) is invalid (id:%d)", transaction.TransactionId, transaction.Type)
|
||||
return nil, 0, 0, 0, 0, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
@@ -197,6 +197,132 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor
|
||||
return allTransactionsAndAccountBalance, totalInflows, totalOutflows, openingBalance, accumulatedBalance, nil
|
||||
}
|
||||
|
||||
// GetAllAccountsDailyOpeningAndClosingBalance returns daily opening and closing balance of all accounts within time range
|
||||
func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, utcOffset int16) (map[int32][]*models.TransactionWithAccountBalance, error) {
|
||||
if maxTransactionTime <= 0 {
|
||||
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
|
||||
}
|
||||
|
||||
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
|
||||
var allTransactions []*models.Transaction
|
||||
|
||||
for maxTransactionTime > 0 {
|
||||
transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, pageCountForLoadTransactionAmounts, false, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allTransactions = append(allTransactions, transactions...)
|
||||
|
||||
if len(transactions) < pageCountForLoadTransactionAmounts {
|
||||
maxTransactionTime = 0
|
||||
break
|
||||
}
|
||||
|
||||
maxTransactionTime = transactions[len(transactions)-1].TransactionTime - 1
|
||||
}
|
||||
|
||||
accountDailyLastBalances := make(map[string]*models.TransactionWithAccountBalance)
|
||||
accountDailyBalances := make(map[int32][]*models.TransactionWithAccountBalance)
|
||||
|
||||
if len(allTransactions) < 1 {
|
||||
return accountDailyBalances, nil
|
||||
}
|
||||
|
||||
accumulatedBalances := make(map[int64]int64)
|
||||
accumulatedBalancesBeforeStartTime := make(map[int64]int64)
|
||||
|
||||
for i := len(allTransactions) - 1; i >= 0; i-- {
|
||||
transaction := allTransactions[i]
|
||||
accumulatedBalance := accumulatedBalances[transaction.AccountId]
|
||||
lastAccumulatedBalance := accumulatedBalances[transaction.AccountId]
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||
accumulatedBalance = accumulatedBalance + transaction.RelatedAccountAmount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
||||
accumulatedBalance = accumulatedBalance + transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||
accumulatedBalance = accumulatedBalance - transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
accumulatedBalance = accumulatedBalance - transaction.Amount
|
||||
} else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||
accumulatedBalance = accumulatedBalance + transaction.Amount
|
||||
} else {
|
||||
log.Errorf(c, "[transactions.GetAllTransactionsWithAccountBalanceByMaxTime] trasaction type (%d) is invalid (id:%d)", transaction.TransactionId, transaction.Type)
|
||||
return nil, errs.ErrTransactionTypeInvalid
|
||||
}
|
||||
|
||||
accumulatedBalances[transaction.AccountId] = accumulatedBalance
|
||||
|
||||
if transaction.TransactionTime < minTransactionTime {
|
||||
accumulatedBalancesBeforeStartTime[transaction.AccountId] = accumulatedBalance
|
||||
continue
|
||||
}
|
||||
|
||||
yearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), clientLocation)
|
||||
groupKey := fmt.Sprintf("%d_%d", yearMonthDay, transaction.AccountId)
|
||||
dailyAccountBalance, exists := accountDailyLastBalances[groupKey]
|
||||
|
||||
if exists {
|
||||
dailyAccountBalance.AccountClosingBalance = accumulatedBalance
|
||||
} else {
|
||||
dailyAccountBalance = &models.TransactionWithAccountBalance{
|
||||
Transaction: &models.Transaction{
|
||||
AccountId: transaction.AccountId,
|
||||
},
|
||||
AccountOpeningBalance: lastAccumulatedBalance,
|
||||
AccountClosingBalance: accumulatedBalance,
|
||||
}
|
||||
accountDailyLastBalances[groupKey] = dailyAccountBalance
|
||||
}
|
||||
}
|
||||
|
||||
firstTransactionTime := allTransactions[len(allTransactions)-1].TransactionTime
|
||||
|
||||
if minTransactionTime > firstTransactionTime {
|
||||
firstTransactionTime = minTransactionTime
|
||||
}
|
||||
|
||||
firstYearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(firstTransactionTime), clientLocation)
|
||||
|
||||
// fill in the opening balance for accounts that do not have transactions on the first day
|
||||
for accountId, accumulatedBalance := range accumulatedBalancesBeforeStartTime {
|
||||
if accumulatedBalance == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
groupKey := fmt.Sprintf("%d_%d", firstYearMonthDay, accountId)
|
||||
|
||||
if _, exists := accountDailyLastBalances[groupKey]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
accountDailyLastBalances[groupKey] = &models.TransactionWithAccountBalance{
|
||||
Transaction: &models.Transaction{
|
||||
AccountId: accountId,
|
||||
},
|
||||
AccountOpeningBalance: accumulatedBalance,
|
||||
AccountClosingBalance: accumulatedBalance,
|
||||
}
|
||||
}
|
||||
|
||||
for groupKey, transactionWithAccountBalance := range accountDailyLastBalances {
|
||||
groupKeyParts := strings.Split(groupKey, "_")
|
||||
yearMonthDay, _ := utils.StringToInt32(groupKeyParts[0])
|
||||
dailyAccountBalances, exists := accountDailyBalances[yearMonthDay]
|
||||
|
||||
if !exists {
|
||||
dailyAccountBalances = make([]*models.TransactionWithAccountBalance, 0)
|
||||
}
|
||||
|
||||
dailyAccountBalances = append(dailyAccountBalances, transactionWithAccountBalance)
|
||||
accountDailyBalances[yearMonthDay] = dailyAccountBalances
|
||||
}
|
||||
|
||||
return accountDailyBalances, nil
|
||||
}
|
||||
|
||||
// GetTransactionsByMaxTime returns transactions before given time
|
||||
func (s *TransactionService) GetTransactionsByMaxTime(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionType, categoryIds []int64, accountIds []int64, tagIds []int64, noTags bool, tagFilterType models.TransactionTagFilterType, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
|
||||
@@ -155,6 +155,17 @@ func FormatUnixTimeToNumericYearMonth(unixTime int64, timezone *time.Location) i
|
||||
return int32(t.Year())*100 + int32(t.Month())
|
||||
}
|
||||
|
||||
// FormatUnixTimeToNumericYearMonthDay returns numeric year, month and day of specified unix time
|
||||
func FormatUnixTimeToNumericYearMonthDay(unixTime int64, timezone *time.Location) int32 {
|
||||
t := parseFromUnixTime(unixTime)
|
||||
|
||||
if timezone != nil {
|
||||
t = t.In(timezone)
|
||||
}
|
||||
|
||||
return int32(t.Year())*10000 + int32(t.Month())*100 + int32(t.Day())
|
||||
}
|
||||
|
||||
// FormatUnixTimeToNumericLocalDateTime returns numeric year, month, day, hour, minute and second of specified unix time
|
||||
func FormatUnixTimeToNumericLocalDateTime(unixTime int64, timezone *time.Location) int64 {
|
||||
t := parseFromUnixTime(unixTime)
|
||||
|
||||
@@ -133,6 +133,20 @@ func TestFormatUnixTimeToNumericYearMonth(t *testing.T) {
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestFormatUnixTimeToNumericYearMonthDay(t *testing.T) {
|
||||
unixTime := int64(1617228083)
|
||||
utcTimezone := time.FixedZone("Test Timezone", 0) // UTC
|
||||
utc8Timezone := time.FixedZone("Test Timezone", 28800) // UTC+8
|
||||
|
||||
expectedValue := int32(20210331)
|
||||
actualValue := FormatUnixTimeToNumericYearMonthDay(unixTime, utcTimezone)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
|
||||
expectedValue = int32(20210401)
|
||||
actualValue = FormatUnixTimeToNumericYearMonthDay(unixTime, utc8Timezone)
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
}
|
||||
|
||||
func TestFormatUnixTimeToNumericLocalDateTime(t *testing.T) {
|
||||
unixTime := int64(1617228083)
|
||||
utcTimezone := time.FixedZone("Test Timezone", 0) // UTC
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import type { AccountInfoResponse } from '@/models/account.ts';
|
||||
import type { TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
|
||||
|
||||
import { isDefined, isArray } from '@/lib/common.ts';
|
||||
import { isArray } from '@/lib/common.ts';
|
||||
import { sumAmounts } from '@/lib/numeral.ts';
|
||||
import {
|
||||
getGregorianCalendarYearAndMonthFromUnixTime,
|
||||
@@ -45,7 +45,7 @@ export interface AccountBalanceTrendsChartItem {
|
||||
|
||||
export interface CommonAccountBalanceTrendsChartProps {
|
||||
items: TransactionReconciliationStatementResponseItem[] | undefined;
|
||||
dateAggregationType?: number;
|
||||
dateAggregationType: number;
|
||||
fiscalYearStart: number;
|
||||
account: AccountInfoResponse;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTren
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isDefined(props.dateAggregationType)) {
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
return getAllDaysStartAndEndUnixTimes(dataDateRange.value.minUnixTime, dataDateRange.value.maxUnixTime);
|
||||
} else {
|
||||
const startYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(dataDateRange.value.minUnixTime);
|
||||
@@ -129,8 +129,10 @@ export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTren
|
||||
dateRangeMinUnixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeMinUnixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else {
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
dateRangeMinUnixTime = getDayFirstUnixTimeBySpecifiedUnixTime(dateItem.time);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const dataItems: TransactionReconciliationStatementResponseItem[] = dayDataItemsMap[dateRangeMinUnixTime] || [];
|
||||
@@ -159,8 +161,10 @@ export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTren
|
||||
displayDate = formatUnixTimeToGregorianLikeYearQuarter(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
displayDate = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime);
|
||||
} else {
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
displayDate = formatUnixTimeToShortDate(dateRange.minUnixTime);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (isArray(dataItems)) {
|
||||
|
||||
136
src/components/base/TrendsChartBase.ts
Normal file
136
src/components/base/TrendsChartBase.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
|
||||
import type {
|
||||
TextualYearMonth,
|
||||
Year1BasedMonth,
|
||||
YearMonthDay,
|
||||
TimeRangeAndDateType,
|
||||
YearUnixTime,
|
||||
YearQuarterUnixTime,
|
||||
YearMonthUnixTime,
|
||||
YearMonthDayUnixTime
|
||||
} from '@/core/datetime.ts';
|
||||
import type { FiscalYearUnixTime } from '@/core/fiscalyear.ts';
|
||||
import { ChartDataAggregationType, ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import type { YearMonthItems, YearMonthDayItems } from '@/models/transaction.ts';
|
||||
|
||||
import {
|
||||
getYearMonthDayDateTime,
|
||||
getGregorianCalendarYearAndMonthFromUnixTime,
|
||||
getAllDaysStartAndEndUnixTimes
|
||||
} from '@/lib/datetime.ts';
|
||||
import {
|
||||
getAllDateRangesFromItems,
|
||||
getAllDateRangesByYearMonthRange
|
||||
} from '@/lib/statistics.ts';
|
||||
|
||||
export type TrendsChartDateType = 'daily' | 'monthly';
|
||||
|
||||
interface TrendsChartTypes {
|
||||
daily: {
|
||||
ItemsType: YearMonthDayItems<YearMonthDay>;
|
||||
DateTimeRangeType: number;
|
||||
MonthRangeType: undefined;
|
||||
};
|
||||
monthly: {
|
||||
ItemsType: YearMonthItems<Year1BasedMonth>;
|
||||
DateTimeRangeType: undefined;
|
||||
MonthRangeType: TextualYearMonth | '';
|
||||
};
|
||||
}
|
||||
|
||||
export interface CommonTrendsChartProps<T extends TrendsChartDateType> {
|
||||
chartMode: T;
|
||||
items: TrendsChartTypes[T]['ItemsType'][];
|
||||
stacked?: boolean;
|
||||
startTime: TrendsChartTypes[T]['DateTimeRangeType'];
|
||||
endTime: TrendsChartTypes[T]['DateTimeRangeType'];
|
||||
startYearMonth: TrendsChartTypes[T]['MonthRangeType'];
|
||||
endYearMonth: TrendsChartTypes[T]['MonthRangeType'];
|
||||
fiscalYearStart: number;
|
||||
sortingType: number;
|
||||
dataAggregationType: ChartDataAggregationType;
|
||||
dateAggregationType: number;
|
||||
idField?: string;
|
||||
nameField: string;
|
||||
valueField: string;
|
||||
colorField?: string;
|
||||
hiddenField?: string;
|
||||
displayOrdersField?: string;
|
||||
translateName?: boolean;
|
||||
defaultCurrency?: string;
|
||||
enableClickItem?: boolean;
|
||||
}
|
||||
|
||||
export interface TrendsBarChartClickEvent {
|
||||
itemId: string;
|
||||
dateRange: TimeRangeAndDateType;
|
||||
}
|
||||
|
||||
function buildDailyAllDateRanges(props: CommonTrendsChartProps<'daily'>): YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] | YearMonthDayUnixTime[] {
|
||||
let startTime: number = props.startTime;
|
||||
let endTime: number = props.endTime;
|
||||
|
||||
if ((!startTime || !endTime) && props.items && props.items.length) {
|
||||
let minUnixTime = Number.MAX_SAFE_INTEGER, maxUnixTime = 0;
|
||||
|
||||
for (const accountItem of props.items) {
|
||||
for (const dataItem of accountItem.items) {
|
||||
const dateTime = getYearMonthDayDateTime(dataItem.year, dataItem.month, dataItem.day);
|
||||
const unixTime = dateTime.getUnixTime();
|
||||
|
||||
if (unixTime < minUnixTime) {
|
||||
minUnixTime = unixTime;
|
||||
}
|
||||
|
||||
if (unixTime > maxUnixTime) {
|
||||
maxUnixTime = unixTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minUnixTime < Number.MAX_SAFE_INTEGER && maxUnixTime > 0) {
|
||||
startTime = minUnixTime;
|
||||
endTime = maxUnixTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
return getAllDaysStartAndEndUnixTimes(startTime, endTime);
|
||||
} else {
|
||||
const startYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(startTime);
|
||||
const endYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(endTime);
|
||||
return getAllDateRangesByYearMonthRange(startYearMonth, endYearMonth, props.fiscalYearStart, props.dateAggregationType);
|
||||
}
|
||||
}
|
||||
|
||||
function buildMonthlyAllDateRanges(props: CommonTrendsChartProps<'monthly'>): YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] {
|
||||
return getAllDateRangesFromItems(props.items, props.startYearMonth, props.endYearMonth, props.fiscalYearStart, props.dateAggregationType);
|
||||
}
|
||||
|
||||
export function useTrendsChartBase<T extends TrendsChartDateType>(props: CommonTrendsChartProps<T>) {
|
||||
const { tt } = useI18n();
|
||||
|
||||
const allDateRanges = computed<YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] | YearMonthDayUnixTime[]>(() => {
|
||||
if (props.chartMode === 'daily') {
|
||||
return buildDailyAllDateRanges(props as CommonTrendsChartProps<'daily'>);
|
||||
} else if (props.chartMode === 'monthly') {
|
||||
return buildMonthlyAllDateRanges(props as CommonTrendsChartProps<'monthly'>);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
function getItemName(name: string): string {
|
||||
return props.translateName ? tt(name) : name;
|
||||
}
|
||||
|
||||
return {
|
||||
// computed states
|
||||
allDateRanges,
|
||||
// functions
|
||||
getItemName
|
||||
};
|
||||
}
|
||||
@@ -263,7 +263,7 @@ function onLegendSelectChanged(e: { selected: Record<string, boolean> }): void {
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.pie-chart-container {
|
||||
height: 610px;
|
||||
height: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ const chartOptions = computed<object>(() => {
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.radar-chart-container {
|
||||
height: 610px;
|
||||
height: 650px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-chart autoresize class="monthly-trends-chart-container" :class="{ 'transition-in': skeleton }" :option="chartOptions"
|
||||
<v-chart autoresize class="trends-chart-container" :class="{ 'transition-in': skeleton }" :option="chartOptions"
|
||||
@click="clickItem" @legendselectchanged="onLegendSelectChanged" />
|
||||
</template>
|
||||
|
||||
@@ -10,20 +10,33 @@ import type { ECElementEvent } from 'echarts/core';
|
||||
import type { CallbackDataParams } from 'echarts/types/dist/shared';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { type CommonMonthlyTrendsChartProps, type MonthlyTrendsBarChartClickEvent, useMonthlyTrendsChartBase } from '@/components/base/MonthlyTrendsChartBase.ts'
|
||||
import {
|
||||
type TrendsChartDateType,
|
||||
type CommonTrendsChartProps,
|
||||
type TrendsBarChartClickEvent,
|
||||
useTrendsChartBase
|
||||
} from '@/components/base/TrendsChartBase.ts'
|
||||
|
||||
import { useUserStore } from '@/stores/user.ts';
|
||||
|
||||
import { itemAndIndex } from '@/core/base.ts';
|
||||
import { TextDirection } from '@/core/text.ts';
|
||||
import { type Year1BasedMonth, DateRangeScene } from '@/core/datetime.ts';
|
||||
import {
|
||||
type Year1BasedMonth,
|
||||
type YearMonthDay,
|
||||
DateRangeScene
|
||||
} from '@/core/datetime.ts';
|
||||
import type { ColorStyleValue } from '@/core/color.ts';
|
||||
import { ThemeType } from '@/core/theme.ts';
|
||||
import { TrendChartType, ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import {
|
||||
ChartDataAggregationType,
|
||||
TrendChartType,
|
||||
ChartDateAggregationType
|
||||
} from '@/core/statistics.ts';
|
||||
|
||||
import { DEFAULT_CHART_COLORS } from '@/consts/color.ts';
|
||||
|
||||
import type { YearMonthDataItem, SortableTransactionStatisticDataItem } from '@/models/transaction.ts';
|
||||
import type { SortableTransactionStatisticDataItem } from '@/models/transaction.ts';
|
||||
|
||||
import {
|
||||
isArray,
|
||||
@@ -42,14 +55,14 @@ import {
|
||||
sortStatisticsItems
|
||||
} from '@/lib/statistics.ts';
|
||||
|
||||
interface DesktopMonthlyTrendsChartProps<T extends Year1BasedMonth> extends CommonMonthlyTrendsChartProps<T> {
|
||||
interface DesktopTrendsChartProps<T extends TrendsChartDateType> extends CommonTrendsChartProps<T> {
|
||||
skeleton?: boolean;
|
||||
type?: number;
|
||||
showValue?: boolean;
|
||||
showTotalAmountInTooltip?: boolean;
|
||||
}
|
||||
|
||||
interface MonthlyTrendsChartDataItem {
|
||||
interface TrendsChartDataItem {
|
||||
id: string;
|
||||
name: string;
|
||||
itemStyle: {
|
||||
@@ -64,17 +77,17 @@ interface MonthlyTrendsChartDataItem {
|
||||
data: number[];
|
||||
}
|
||||
|
||||
interface MonthlyTrendsChartTooltipItem extends SortableTransactionStatisticDataItem {
|
||||
interface TrendsChartTooltipItem extends SortableTransactionStatisticDataItem {
|
||||
readonly name: string;
|
||||
readonly color: unknown;
|
||||
readonly displayOrders: number[];
|
||||
readonly totalAmount: number;
|
||||
}
|
||||
|
||||
const props = defineProps<DesktopMonthlyTrendsChartProps<YearMonthDataItem>>();
|
||||
const props = defineProps<DesktopTrendsChartProps<TrendsChartDateType>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', value: MonthlyTrendsBarChartClickEvent): void;
|
||||
(e: 'click', value: TrendsBarChartClickEvent): void;
|
||||
}>();
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -82,6 +95,7 @@ const theme = useTheme();
|
||||
const {
|
||||
tt,
|
||||
getCurrentLanguageTextDirection,
|
||||
formatUnixTimeToShortDate,
|
||||
formatUnixTimeToGregorianLikeShortYear,
|
||||
formatUnixTimeToGregorianLikeShortYearMonth,
|
||||
formatYearQuarterToGregorianLikeYearQuarter,
|
||||
@@ -90,7 +104,7 @@ const {
|
||||
formatAmountToLocalizedNumeralsWithCurrency
|
||||
} = useI18n();
|
||||
|
||||
const { allDateRanges, getItemName } = useMonthlyTrendsChartBase(props);
|
||||
const { allDateRanges, getItemName } = useTrendsChartBase(props);
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -143,16 +157,18 @@ const allDisplayDateRanges = computed<string[]>(() => {
|
||||
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime));
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
|
||||
allDisplayDateRanges.push(formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter));
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
allDisplayDateRanges.push(formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime));
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
|
||||
allDisplayDateRanges.push(formatUnixTimeToShortDate(dateRange.minUnixTime));
|
||||
}
|
||||
}
|
||||
|
||||
return allDisplayDateRanges;
|
||||
});
|
||||
|
||||
const allSeries = computed<MonthlyTrendsChartDataItem[]>(() => {
|
||||
const allSeries: MonthlyTrendsChartDataItem[] = [];
|
||||
const allSeries = computed<TrendsChartDataItem[]>(() => {
|
||||
const allSeries: TrendsChartDataItem[] = [];
|
||||
let maxAmount: number = 0;
|
||||
|
||||
for (const [item, index] of itemAndIndex(props.items)) {
|
||||
@@ -161,23 +177,41 @@ const allSeries = computed<MonthlyTrendsChartDataItem[]>(() => {
|
||||
}
|
||||
|
||||
const allAmounts: number[] = [];
|
||||
const dateRangeAmountMap: Record<string, YearMonthDataItem[]> = {};
|
||||
const dateRangeAmountMap: Record<string, (Year1BasedMonth | YearMonthDay)[]> = {};
|
||||
|
||||
for (const dataItem of item.items) {
|
||||
let dateRangeKey = '';
|
||||
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month1base }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month1base - 1) / 3) + 1}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month1base}`;
|
||||
if (props.chartMode === 'daily' && 'month' in dataItem) {
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month - 1) / 3) + 1}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month}-${dataItem.day}`;
|
||||
}
|
||||
} else if (props.chartMode === 'monthly' && 'month1base' in dataItem) {
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month1base }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month1base - 1) / 3) + 1}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month1base}`;
|
||||
}
|
||||
}
|
||||
|
||||
const dataItems = dateRangeAmountMap[dateRangeKey] || [];
|
||||
@@ -197,6 +231,8 @@ const allSeries = computed<MonthlyTrendsChartDataItem[]>(() => {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.quarter}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type && 'month0base' in dateRange) {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.month0base + 1}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && 'day' in dateRange && props.chartMode === 'daily') {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.month}-${dateRange.day}`;
|
||||
}
|
||||
|
||||
let amount = 0;
|
||||
@@ -204,8 +240,14 @@ const allSeries = computed<MonthlyTrendsChartDataItem[]>(() => {
|
||||
|
||||
if (isArray(dataItems)) {
|
||||
for (const dataItem of dataItems) {
|
||||
if (isNumber(dataItem[props.valueField])) {
|
||||
amount += dataItem[props.valueField] as number;
|
||||
const value = (dataItem as unknown as Record<string, unknown>)[props.valueField];
|
||||
|
||||
if (isNumber(value)) {
|
||||
if (props.dataAggregationType === ChartDataAggregationType.Sum) {
|
||||
amount += value;
|
||||
} else if (props.dataAggregationType === ChartDataAggregationType.Last) {
|
||||
amount = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +259,7 @@ const allSeries = computed<MonthlyTrendsChartDataItem[]>(() => {
|
||||
allAmounts.push(amount);
|
||||
}
|
||||
|
||||
const finalItem: MonthlyTrendsChartDataItem = {
|
||||
const finalItem: TrendsChartDataItem = {
|
||||
id: (props.idField && item[props.idField]) ? item[props.idField] as string : getItemName(item[props.nameField] as string),
|
||||
name: (props.idField && item[props.idField]) ? item[props.idField] as string : getItemName(item[props.nameField] as string),
|
||||
itemStyle: {
|
||||
@@ -317,7 +359,7 @@ const chartOptions = computed<object>(() => {
|
||||
let tooltip = '';
|
||||
let totalAmount = 0;
|
||||
let actualDisplayItemCount = 0;
|
||||
const displayItems: MonthlyTrendsChartTooltipItem[] = [];
|
||||
const displayItems: TrendsChartTooltipItem[] = [];
|
||||
|
||||
for (const param of params) {
|
||||
const id = param.seriesId as string;
|
||||
@@ -435,19 +477,33 @@ function clickItem(e: ECElementEvent): void {
|
||||
let minUnixTime = dateRange.minUnixTime;
|
||||
let maxUnixTime = dateRange.maxUnixTime;
|
||||
|
||||
if (props.startYearMonth) {
|
||||
const startMinUnixTime = getYearMonthFirstUnixTime(props.startYearMonth);
|
||||
|
||||
if (startMinUnixTime > minUnixTime) {
|
||||
minUnixTime = startMinUnixTime;
|
||||
if (props.chartMode === 'daily') {
|
||||
if (props.startTime) {
|
||||
if (props.startTime > minUnixTime) {
|
||||
minUnixTime = props.startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.endYearMonth) {
|
||||
const endMaxUnixTime = getYearMonthLastUnixTime(props.endYearMonth);
|
||||
if (props.endTime) {
|
||||
if (props.endTime < maxUnixTime) {
|
||||
maxUnixTime = props.endTime;
|
||||
}
|
||||
}
|
||||
} else if (props.chartMode === 'monthly') {
|
||||
if (props.startYearMonth) {
|
||||
const startMinUnixTime = getYearMonthFirstUnixTime(props.startYearMonth);
|
||||
|
||||
if (endMaxUnixTime < maxUnixTime) {
|
||||
maxUnixTime = endMaxUnixTime;
|
||||
if (startMinUnixTime > minUnixTime) {
|
||||
minUnixTime = startMinUnixTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.endYearMonth) {
|
||||
const endMaxUnixTime = getYearMonthLastUnixTime(props.endYearMonth);
|
||||
|
||||
if (endMaxUnixTime < maxUnixTime) {
|
||||
maxUnixTime = endMaxUnixTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,15 +554,15 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monthly-trends-chart-container {
|
||||
.trends-chart-container {
|
||||
width: 100%;
|
||||
height: 720px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.monthly-trends-chart-container {
|
||||
height: 760px;
|
||||
.trends-chart-container {
|
||||
height: 790px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -30,23 +30,31 @@
|
||||
<f7-list-item :title="tt('No transaction data')"></f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list v-else-if="!loading && allDisplayDataItems && allDisplayDataItems.data && allDisplayDataItems.data.length">
|
||||
<f7-list v-if="!loading && allDisplayDataItems && allDisplayDataItems.data && allDisplayDataItems.data.length">
|
||||
<f7-list-item v-if="allDisplayDataItems.legends && allDisplayDataItems.legends.length > 1">
|
||||
<div class="display-flex" style="flex-wrap: wrap">
|
||||
<div class="monthly-trends-bar-chart-legend display-flex align-items-center"
|
||||
:class="{ 'monthly-trends-bar-chart-legend-unselected': !!unselectedLegends[legend.id] }"
|
||||
<div class="trends-bar-chart-legend display-flex align-items-center"
|
||||
:class="{ 'trends-bar-chart-legend-unselected': !!unselectedLegends[legend.id] }"
|
||||
:key="idx"
|
||||
v-for="(legend, idx) in allDisplayDataItems.legends"
|
||||
@click="toggleLegend(legend)">
|
||||
<f7-icon f7="app_fill" class="monthly-trends-bar-chart-legend-icon" :style="{ 'color': unselectedLegends[legend.id] ? '' : legend.color }"></f7-icon>
|
||||
<span class="monthly-trends-bar-chart-legend-text">{{ legend.name }}</span>
|
||||
<f7-icon f7="app_fill" class="trends-bar-chart-legend-icon" :style="{ 'color': unselectedLegends[legend.id] ? '' : legend.color }"></f7-icon>
|
||||
<span class="trends-bar-chart-legend-text">{{ legend.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list :key="`trends-bar-chart-${allDisplayDataItemsVersion}`"
|
||||
:virtual-list="useVirtualList"
|
||||
:virtual-list-params="useVirtualList ? { items: allDisplayDataItems.data, renderExternal, height: 'auto' } : undefined"
|
||||
v-if="!loading && allDisplayDataItems && allDisplayDataItems.data && allDisplayDataItems.data.length">
|
||||
<f7-list-item link="#"
|
||||
:key="idx"
|
||||
:key="item.index"
|
||||
:class="{ 'statistics-list-item': true, 'statistics-list-item-stacked': stacked, 'statistics-list-item-non-stacked': !stacked }"
|
||||
v-for="(item, idx) in allDisplayDataItems.data"
|
||||
:style="useVirtualList ? `top: ${virtualDataItems.topPosition}px` : undefined"
|
||||
:virtual-list-index="item.index"
|
||||
v-for="item in (useVirtualList ? virtualDataItems.items : allDisplayDataItems.data)"
|
||||
@click="clickItem(item)"
|
||||
>
|
||||
<template #media>
|
||||
@@ -105,21 +113,32 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
import { type CommonMonthlyTrendsChartProps, type MonthlyTrendsBarChartClickEvent, useMonthlyTrendsChartBase } from '@/components/base/MonthlyTrendsChartBase.ts'
|
||||
import {
|
||||
type TrendsChartDateType,
|
||||
type CommonTrendsChartProps,
|
||||
type TrendsBarChartClickEvent,
|
||||
useTrendsChartBase
|
||||
} from '@/components/base/TrendsChartBase.ts'
|
||||
|
||||
import { useUserStore } from '@/stores/user.ts';
|
||||
|
||||
import { itemAndIndex } from '@/core/base.ts';
|
||||
import { type Year1BasedMonth, type UnixTimeRange, DateRangeScene } from '@/core/datetime.ts';
|
||||
import {
|
||||
type UnixTimeRange,
|
||||
DateRangeScene
|
||||
} from '@/core/datetime.ts';
|
||||
import type { ColorStyleValue } from '@/core/color.ts';
|
||||
import { ChartDateAggregationType } from '@/core/statistics.ts';
|
||||
import {
|
||||
ChartDataAggregationType,
|
||||
ChartDateAggregationType
|
||||
} from '@/core/statistics.ts';
|
||||
|
||||
import { DEFAULT_CHART_COLORS } from '@/consts/color.ts';
|
||||
|
||||
import type { YearMonthDataItem, SortableTransactionStatisticDataItem } from '@/models/transaction.ts';
|
||||
import type { SortableTransactionStatisticDataItem } from '@/models/transaction.ts';
|
||||
|
||||
import {
|
||||
isNumber
|
||||
@@ -144,37 +163,44 @@ interface TrendsBarChartLegend {
|
||||
readonly displayOrders: number[];
|
||||
}
|
||||
|
||||
interface MonthlyTrendsBarChartDataAmount extends SortableTransactionStatisticDataItem, TrendsBarChartLegend {
|
||||
interface TrendsBarChartDataAmount extends SortableTransactionStatisticDataItem, TrendsBarChartLegend {
|
||||
totalAmount: number;
|
||||
}
|
||||
|
||||
interface MonthlyTrendsBarChartDataItem {
|
||||
interface TrendsBarChartDataItem {
|
||||
index: number;
|
||||
dateRange: UnixTimeRange;
|
||||
displayDateRange: string;
|
||||
items: MonthlyTrendsBarChartDataAmount[];
|
||||
items: TrendsBarChartDataAmount[];
|
||||
totalAmount: number;
|
||||
totalPositiveAmount: number;
|
||||
maxAmount: number;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
interface MonthlyTrendsBarChartData {
|
||||
readonly data: MonthlyTrendsBarChartDataItem[];
|
||||
interface TrendsBarChartVirtualListData {
|
||||
items: TrendsBarChartDataItem[],
|
||||
topPosition: number
|
||||
}
|
||||
|
||||
interface TrendsBarChartData {
|
||||
readonly data: TrendsBarChartDataItem[];
|
||||
readonly legends: TrendsBarChartLegend[];
|
||||
}
|
||||
|
||||
interface MobileMonthlyTrendsChartProps<T extends Year1BasedMonth> extends CommonMonthlyTrendsChartProps<T> {
|
||||
interface MobileTrendsChartProps<T extends TrendsChartDateType> extends CommonTrendsChartProps<T> {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<MobileMonthlyTrendsChartProps<YearMonthDataItem>>();
|
||||
const props = defineProps<MobileTrendsChartProps<TrendsChartDateType>>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', value: MonthlyTrendsBarChartClickEvent): void;
|
||||
(e: 'click', value: TrendsBarChartClickEvent): void;
|
||||
}>();
|
||||
|
||||
const {
|
||||
tt,
|
||||
formatUnixTimeToShortDate,
|
||||
formatUnixTimeToGregorianLikeShortYear,
|
||||
formatUnixTimeToGregorianLikeShortYearMonth,
|
||||
formatYearQuarterToGregorianLikeYearQuarter,
|
||||
@@ -182,14 +208,22 @@ const {
|
||||
formatAmountToLocalizedNumeralsWithCurrency
|
||||
} = useI18n();
|
||||
|
||||
const { allDateRanges, getItemName } = useMonthlyTrendsChartBase(props);
|
||||
const { allDateRanges, getItemName } = useTrendsChartBase(props);
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const allDisplayDataItemsVersion = ref<number>(0);
|
||||
const unselectedLegends = ref<Record<string, boolean>>({});
|
||||
|
||||
const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
const allDateRangeItemsMap: Record<string, MonthlyTrendsBarChartDataAmount[]> = {};
|
||||
const virtualDataItems = ref<TrendsBarChartVirtualListData>({
|
||||
items: [],
|
||||
topPosition: 0
|
||||
});
|
||||
|
||||
const useVirtualList = computed<boolean>(() => allDisplayDataItems.value.legends.length <= 1 || props.stacked);
|
||||
|
||||
const allDisplayDataItems = computed<TrendsBarChartData>(() => {
|
||||
const allDateRangeItemsMap: Record<string, TrendsBarChartDataAmount[]> = {};
|
||||
const legends: TrendsBarChartLegend[] = [];
|
||||
|
||||
for (const [item, index] of itemAndIndex(props.items)) {
|
||||
@@ -212,31 +246,57 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dateRangeItemMap: Record<string, MonthlyTrendsBarChartDataAmount> = {};
|
||||
const dateRangeItemMap: Record<string, TrendsBarChartDataAmount> = {};
|
||||
|
||||
for (const dataItem of item.items) {
|
||||
let dateRangeKey = '';
|
||||
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month1base }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month1base - 1) / 3) + 1}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month1base}`;
|
||||
if (props.chartMode === 'daily' && 'month' in dataItem) {
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month - 1) / 3) + 1}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month}-${dataItem.day}`;
|
||||
}
|
||||
} else if (props.chartMode === 'monthly' && 'month1base' in dataItem) {
|
||||
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
|
||||
dateRangeKey = dataItem.year.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
|
||||
const fiscalYear = getFiscalYearFromUnixTime(
|
||||
getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month1base }),
|
||||
props.fiscalYearStart
|
||||
);
|
||||
dateRangeKey = fiscalYear.toString();
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
|
||||
dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month1base - 1) / 3) + 1}`;
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
dateRangeKey = `${dataItem.year}-${dataItem.month1base}`;
|
||||
}
|
||||
}
|
||||
|
||||
const value = (dataItem as unknown as Record<string, unknown>)[props.valueField];
|
||||
|
||||
if (dateRangeItemMap[dateRangeKey]) {
|
||||
dateRangeItemMap[dateRangeKey]!.totalAmount += (props.valueField && isNumber(dataItem[props.valueField])) ? dataItem[props.valueField] as number : 0;
|
||||
if (isNumber(value)) {
|
||||
if (props.dataAggregationType === ChartDataAggregationType.Sum) {
|
||||
dateRangeItemMap[dateRangeKey]!.totalAmount += value;
|
||||
} else if (props.dataAggregationType === ChartDataAggregationType.Last) {
|
||||
dateRangeItemMap[dateRangeKey]!.totalAmount = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const allDataItems: MonthlyTrendsBarChartDataAmount[] = allDateRangeItemsMap[dateRangeKey] || [];
|
||||
const finalDataItem: MonthlyTrendsBarChartDataAmount = Object.assign({}, legend, {
|
||||
totalAmount: (props.valueField && isNumber(dataItem[props.valueField])) ? dataItem[props.valueField] as number : 0
|
||||
const allDataItems: TrendsBarChartDataAmount[] = allDateRangeItemsMap[dateRangeKey] || [];
|
||||
const finalDataItem: TrendsBarChartDataAmount = Object.assign({}, legend, {
|
||||
totalAmount: isNumber(value) ? value : 0
|
||||
});
|
||||
|
||||
allDataItems.push(finalDataItem);
|
||||
@@ -246,7 +306,7 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
}
|
||||
}
|
||||
|
||||
const finalDataItems: MonthlyTrendsBarChartDataItem[] = [];
|
||||
const finalDataItems: TrendsBarChartDataItem[] = [];
|
||||
let maxTotalAmount = 0;
|
||||
|
||||
for (const dateRange of allDateRanges.value) {
|
||||
@@ -260,6 +320,8 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.quarter}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type && 'month0base' in dateRange) {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.month0base + 1}`;
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && 'day' in dateRange && props.chartMode === 'daily') {
|
||||
dateRangeKey = `${dateRange.year}-${dateRange.month}-${dateRange.day}`;
|
||||
}
|
||||
|
||||
let displayDateRange = '';
|
||||
@@ -270,8 +332,10 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
displayDateRange = formatUnixTimeToGregorianLikeFiscalYear(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
|
||||
displayDateRange = formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter);
|
||||
} else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
|
||||
displayDateRange = formatUnixTimeToGregorianLikeShortYearMonth(dateRange.minUnixTime);
|
||||
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
|
||||
displayDateRange = formatUnixTimeToShortDate(dateRange.minUnixTime);
|
||||
}
|
||||
|
||||
const dataItems = allDateRangeItemsMap[dateRangeKey] || [];
|
||||
@@ -297,7 +361,8 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
maxTotalAmount = totalAmount;
|
||||
}
|
||||
|
||||
const finalDataItem: MonthlyTrendsBarChartDataItem = {
|
||||
const finalDataItem: TrendsBarChartDataItem = {
|
||||
index: finalDataItems.length,
|
||||
dateRange: dateRange,
|
||||
displayDateRange: displayDateRange,
|
||||
items: dataItems,
|
||||
@@ -324,7 +389,7 @@ const allDisplayDataItems = computed<MonthlyTrendsBarChartData>(() => {
|
||||
};
|
||||
});
|
||||
|
||||
function clickItem(item: MonthlyTrendsBarChartDataItem): void {
|
||||
function clickItem(item: TrendsBarChartDataItem): void {
|
||||
let itemId = '';
|
||||
|
||||
for (const item of props.items) {
|
||||
@@ -349,19 +414,33 @@ function clickItem(item: MonthlyTrendsBarChartDataItem): void {
|
||||
let minUnixTime = dateRange.minUnixTime;
|
||||
let maxUnixTime = dateRange.maxUnixTime;
|
||||
|
||||
if (props.startYearMonth) {
|
||||
const startMinUnixTime = getYearMonthFirstUnixTime(props.startYearMonth);
|
||||
|
||||
if (startMinUnixTime > minUnixTime) {
|
||||
minUnixTime = startMinUnixTime;
|
||||
if (props.chartMode === 'daily') {
|
||||
if (props.startTime) {
|
||||
if (props.startTime > minUnixTime) {
|
||||
minUnixTime = props.startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props.endYearMonth) {
|
||||
const endMaxUnixTime = getYearMonthLastUnixTime(props.endYearMonth);
|
||||
if (props.endTime) {
|
||||
if (props.endTime < maxUnixTime) {
|
||||
maxUnixTime = props.endTime;
|
||||
}
|
||||
}
|
||||
} else if (props.chartMode === 'monthly') {
|
||||
if (props.startYearMonth) {
|
||||
const startMinUnixTime = getYearMonthFirstUnixTime(props.startYearMonth);
|
||||
|
||||
if (endMaxUnixTime < maxUnixTime) {
|
||||
maxUnixTime = endMaxUnixTime;
|
||||
if (startMinUnixTime > minUnixTime) {
|
||||
minUnixTime = startMinUnixTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (props.endYearMonth) {
|
||||
const endMaxUnixTime = getYearMonthLastUnixTime(props.endYearMonth);
|
||||
|
||||
if (endMaxUnixTime < maxUnixTime) {
|
||||
maxUnixTime = endMaxUnixTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,28 +463,38 @@ function toggleLegend(legend: TrendsBarChartLegend): void {
|
||||
unselectedLegends.value[legend.id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderExternal(vl: unknown, vlData: TrendsBarChartVirtualListData): void {
|
||||
virtualDataItems.value = vlData;
|
||||
}
|
||||
|
||||
watch(allDisplayDataItems, () => {
|
||||
allDisplayDataItemsVersion.value++;
|
||||
}, {
|
||||
deep: true
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.monthly-trends-bar-chart-legend {
|
||||
.trends-bar-chart-legend {
|
||||
margin-inline-end: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monthly-trends-bar-chart-legend-icon.f7-icons {
|
||||
.trends-bar-chart-legend-icon.f7-icons {
|
||||
font-size: var(--ebk-trends-bar-chart-legend-icon-font-size);
|
||||
margin-inline-end: 2px;
|
||||
}
|
||||
|
||||
.monthly-trends-bar-chart-legend-unselected .monthly-trends-bar-chart-legend-icon.f7-icons {
|
||||
.trends-bar-chart-legend-unselected .trends-bar-chart-legend-icon.f7-icons {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.monthly-trends-bar-chart-legend-text {
|
||||
.trends-bar-chart-legend-text {
|
||||
font-size: var(--ebk-trends-bar-chart-legend-text-font-size);
|
||||
}
|
||||
|
||||
.monthly-trends-bar-chart-legend-unselected .monthly-trends-bar-chart-legend-text {
|
||||
.trends-bar-chart-legend-unselected .trends-bar-chart-legend-text {
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
@@ -28,6 +28,7 @@ export interface DateTime {
|
||||
getSecond(): number;
|
||||
getDisplayAMPM(options: DateTimeFormatOptions): string;
|
||||
getTimezoneUtcOffsetMinutes(): number;
|
||||
getDateTimeAfterDays(day: number): DateTime;
|
||||
toGregorianCalendarYearMonthDay(): YearMonthDay;
|
||||
toGregorianCalendarYear0BasedMonth(): Year0BasedMonth;
|
||||
format(format: string, options: DateTimeFormatOptions): string;
|
||||
@@ -584,7 +585,8 @@ export class ShortTimeFormat implements TimeFormat {
|
||||
|
||||
export enum DateRangeScene {
|
||||
Normal = 0,
|
||||
TrendAnalysis = 1
|
||||
TrendAnalysis = 1,
|
||||
AssetTrends = 2
|
||||
}
|
||||
|
||||
export class DateRange implements TypeAndName {
|
||||
@@ -592,38 +594,38 @@ export class DateRange implements TypeAndName {
|
||||
private static readonly allInstancesByType: Record<number, DateRange> = {};
|
||||
|
||||
// All date range
|
||||
public static readonly All = new DateRange(0, 'All', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly All = new DateRange(0, 'All', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
|
||||
// Date ranges for normal scene only
|
||||
public static readonly Today = new DateRange(1, 'Today', false, false, DateRangeScene.Normal);
|
||||
public static readonly Yesterday = new DateRange(2, 'Yesterday', false, false, DateRangeScene.Normal);
|
||||
public static readonly LastSevenDays = new DateRange(3, 'Recent 7 days', false, false, DateRangeScene.Normal);
|
||||
public static readonly LastThirtyDays = new DateRange(4, 'Recent 30 days', false, false, DateRangeScene.Normal);
|
||||
public static readonly ThisWeek = new DateRange(5, 'This week', false, false, DateRangeScene.Normal);
|
||||
public static readonly LastWeek = new DateRange(6, 'Last week', false, false, DateRangeScene.Normal);
|
||||
public static readonly ThisMonth = new DateRange(7, 'This month', false, false, DateRangeScene.Normal);
|
||||
public static readonly LastMonth = new DateRange(8, 'Last month', false, false, DateRangeScene.Normal);
|
||||
public static readonly LastSevenDays = new DateRange(3, 'Recent 7 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
public static readonly LastThirtyDays = new DateRange(4, 'Recent 30 days', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
public static readonly ThisWeek = new DateRange(5, 'This week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
public static readonly LastWeek = new DateRange(6, 'Last week', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
public static readonly ThisMonth = new DateRange(7, 'This month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
public static readonly LastMonth = new DateRange(8, 'Last month', false, false, DateRangeScene.Normal, DateRangeScene.AssetTrends);
|
||||
|
||||
// Date ranges for normal and trend analysis scene
|
||||
public static readonly ThisYear = new DateRange(9, 'This year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly LastYear = new DateRange(10, 'Last year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly ThisFiscalYear = new DateRange(11, 'This fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly LastFiscalYear = new DateRange(12, 'Last fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly ThisYear = new DateRange(9, 'This year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly LastYear = new DateRange(10, 'Last year', false, false, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly ThisFiscalYear = new DateRange(11, 'This fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly LastFiscalYear = new DateRange(12, 'Last fiscal year', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
|
||||
// Billing cycle date ranges for normal scene only
|
||||
public static readonly CurrentBillingCycle = new DateRange(51, 'Current Billing Cycle', true, true, DateRangeScene.Normal);
|
||||
public static readonly PreviousBillingCycle = new DateRange(52, 'Previous Billing Cycle', true, true, DateRangeScene.Normal);
|
||||
|
||||
// Date ranges for trend analysis scene only
|
||||
public static readonly RecentTwelveMonths = new DateRange(101, 'Recent 12 months', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentTwentyFourMonths = new DateRange(102, 'Recent 24 months', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentThirtySixMonths = new DateRange(103, 'Recent 36 months', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentTwoYears = new DateRange(104, 'Recent 2 years', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentThreeYears = new DateRange(105, 'Recent 3 years', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentFiveYears = new DateRange(106, 'Recent 5 years', false, false, DateRangeScene.TrendAnalysis);
|
||||
public static readonly RecentTwelveMonths = new DateRange(101, 'Recent 12 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly RecentTwentyFourMonths = new DateRange(102, 'Recent 24 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly RecentThirtySixMonths = new DateRange(103, 'Recent 36 months', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly RecentTwoYears = new DateRange(104, 'Recent 2 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly RecentThreeYears = new DateRange(105, 'Recent 3 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
public static readonly RecentFiveYears = new DateRange(106, 'Recent 5 years', false, false, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
|
||||
// Custom date range
|
||||
public static readonly Custom = new DateRange(255, 'Custom Date', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis);
|
||||
public static readonly Custom = new DateRange(255, 'Custom Date', false, true, DateRangeScene.Normal, DateRangeScene.TrendAnalysis, DateRangeScene.AssetTrends);
|
||||
|
||||
public readonly type: number;
|
||||
public readonly name: string;
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
ChartDataType,
|
||||
ChartSortingType,
|
||||
DEFAULT_CATEGORICAL_CHART_DATA_RANGE,
|
||||
DEFAULT_TREND_CHART_DATA_RANGE
|
||||
DEFAULT_TREND_CHART_DATA_RANGE,
|
||||
DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE
|
||||
} from './statistics.ts';
|
||||
import { DEFAULT_CURRENCY_CODE } from '@/consts/currency.ts';
|
||||
|
||||
@@ -63,6 +64,8 @@ export interface ApplicationSettings extends BaseApplicationSetting {
|
||||
defaultCategoricalChartDataRangeType: number;
|
||||
defaultTrendChartType: number;
|
||||
defaultTrendChartDataRangeType: number;
|
||||
defaultAssetTrendsChartType: number;
|
||||
defaultAssetTrendsChartDataRangeType: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,6 +125,8 @@ export const ALL_ALLOWED_CLOUD_SYNC_APP_SETTING_KEY_TYPES: Record<string, UserAp
|
||||
'statistics.defaultCategoricalChartDataRangeType': UserApplicationCloudSettingType.Number,
|
||||
'statistics.defaultTrendChartType': UserApplicationCloudSettingType.Number,
|
||||
'statistics.defaultTrendChartDataRangeType': UserApplicationCloudSettingType.Number,
|
||||
'statistics.defaultAssetTrendsChartType': UserApplicationCloudSettingType.Number,
|
||||
'statistics.defaultAssetTrendsChartDataRangeType': UserApplicationCloudSettingType.Number,
|
||||
};
|
||||
|
||||
export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
|
||||
@@ -168,6 +173,8 @@ export const DEFAULT_APPLICATION_SETTINGS: ApplicationSettings = {
|
||||
defaultCategoricalChartDataRangeType: DEFAULT_CATEGORICAL_CHART_DATA_RANGE.type,
|
||||
defaultTrendChartType: TrendChartType.Default.type,
|
||||
defaultTrendChartDataRangeType: DEFAULT_TREND_CHART_DATA_RANGE.type,
|
||||
defaultAssetTrendsChartType: TrendChartType.Default.type,
|
||||
defaultAssetTrendsChartDataRangeType: DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE.type,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,13 @@ import { DateRange } from '@/core/datetime.ts';
|
||||
|
||||
export enum StatisticsAnalysisType {
|
||||
CategoricalAnalysis = 0,
|
||||
TrendAnalysis = 1
|
||||
TrendAnalysis = 1,
|
||||
AssetTrends = 2
|
||||
}
|
||||
|
||||
export enum ChartDataAggregationType {
|
||||
Sum = 0,
|
||||
Last = 1
|
||||
}
|
||||
|
||||
export class CategoricalChartType implements TypeAndName {
|
||||
@@ -115,16 +121,18 @@ export class ChartDataType implements TypeAndName {
|
||||
public static readonly IncomeByAccount = new ChartDataType(3, 'Income By Account', false, false, StatisticsAnalysisType.CategoricalAnalysis, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly IncomeByPrimaryCategory = new ChartDataType(4, 'Income By Primary Category', false, false, StatisticsAnalysisType.CategoricalAnalysis, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly IncomeBySecondaryCategory = new ChartDataType(5, 'Income By Secondary Category', false, false, StatisticsAnalysisType.CategoricalAnalysis, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly AccountTotalAssets = new ChartDataType(6, 'Account Total Assets', false, false, StatisticsAnalysisType.CategoricalAnalysis);
|
||||
public static readonly AccountTotalLiabilities = new ChartDataType(7, 'Account Total Liabilities', false, false, StatisticsAnalysisType.CategoricalAnalysis);
|
||||
public static readonly AccountTotalAssets = new ChartDataType(6, 'Account Total Assets', false, false, StatisticsAnalysisType.CategoricalAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly AccountTotalLiabilities = new ChartDataType(7, 'Account Total Liabilities', false, false, StatisticsAnalysisType.CategoricalAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly TotalOutflows = new ChartDataType(13, 'Total Outflows', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly TotalExpense = new ChartDataType(8, 'Total Expense', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly TotalInflows = new ChartDataType(14, 'Total Inflows', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly TotalIncome = new ChartDataType(9, 'Total Income', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly NetCashFlow = new ChartDataType(15, 'Net Cash Flow', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly NetIncome = new ChartDataType(10, 'Net Income', false, false, StatisticsAnalysisType.TrendAnalysis);
|
||||
public static readonly NetWorth = new ChartDataType(17, 'Net Worth', false, false, StatisticsAnalysisType.AssetTrends);
|
||||
|
||||
public static readonly Default = ChartDataType.ExpenseByPrimaryCategory;
|
||||
public static readonly DefaultForAssetTrends = ChartDataType.NetWorth;
|
||||
|
||||
public readonly type: number;
|
||||
public readonly name: string;
|
||||
@@ -221,28 +229,55 @@ export class ChartDateAggregationType {
|
||||
private static readonly allInstances: ChartDateAggregationType[] = [];
|
||||
private static readonly allInstancesByType: Record<number, ChartDateAggregationType> = {};
|
||||
|
||||
public static readonly Month = new ChartDateAggregationType(0, 'Monthly', 'Aggregate by Month');
|
||||
public static readonly Quarter = new ChartDateAggregationType(1, 'Quarterly', 'Aggregate by Quarter');
|
||||
public static readonly Year = new ChartDateAggregationType(2, 'Yearly', 'Aggregate by Year');
|
||||
public static readonly FiscalYear = new ChartDateAggregationType(3, 'FiscalYearly', 'Aggregate by Fiscal Year');
|
||||
public static readonly Day = new ChartDateAggregationType(4, 'Daily', 'Aggregate by Day', StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly Month = new ChartDateAggregationType(0, 'Monthly', 'Aggregate by Month', StatisticsAnalysisType.TrendAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly Quarter = new ChartDateAggregationType(1, 'Quarterly', 'Aggregate by Quarter', StatisticsAnalysisType.TrendAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly Year = new ChartDateAggregationType(2, 'Yearly', 'Aggregate by Year', StatisticsAnalysisType.TrendAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
public static readonly FiscalYear = new ChartDateAggregationType(3, 'FiscalYearly', 'Aggregate by Fiscal Year', StatisticsAnalysisType.TrendAnalysis, StatisticsAnalysisType.AssetTrends);
|
||||
|
||||
public static readonly Default = ChartDateAggregationType.Month;
|
||||
|
||||
public readonly type: number;
|
||||
public readonly shortName: string;
|
||||
public readonly fullName: string;
|
||||
private readonly availableAnalysisTypes: Record<number, boolean>;
|
||||
|
||||
private constructor(type: number, shortName: string, fullName: string) {
|
||||
private constructor(type: number, shortName: string, fullName: string, ...availableAnalysisTypes: StatisticsAnalysisType[]) {
|
||||
this.type = type;
|
||||
this.shortName = shortName;
|
||||
this.fullName = fullName;
|
||||
this.availableAnalysisTypes = {};
|
||||
|
||||
if (availableAnalysisTypes) {
|
||||
for (const analysisType of availableAnalysisTypes) {
|
||||
this.availableAnalysisTypes[analysisType] = true;
|
||||
}
|
||||
}
|
||||
|
||||
ChartDateAggregationType.allInstances.push(this);
|
||||
ChartDateAggregationType.allInstancesByType[type] = this;
|
||||
}
|
||||
|
||||
public static values(): ChartDateAggregationType[] {
|
||||
return ChartDateAggregationType.allInstances;
|
||||
public isAvailableAnalysisType(analysisType: StatisticsAnalysisType): boolean {
|
||||
return this.availableAnalysisTypes[analysisType] || false;
|
||||
}
|
||||
|
||||
public static values(analysisType?: StatisticsAnalysisType): ChartDateAggregationType[] {
|
||||
const availableInstances: ChartDateAggregationType[] = ChartDateAggregationType.allInstances;
|
||||
|
||||
if (analysisType === undefined) {
|
||||
return availableInstances;
|
||||
}
|
||||
|
||||
const ret: ChartDateAggregationType[] = [];
|
||||
|
||||
for (const chartDataType of availableInstances) {
|
||||
if (chartDataType.isAvailableAnalysisType(analysisType)) {
|
||||
ret.push(chartDataType);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static valueOf(type: number): ChartDateAggregationType | undefined {
|
||||
@@ -252,3 +287,4 @@ export class ChartDateAggregationType {
|
||||
|
||||
export const DEFAULT_CATEGORICAL_CHART_DATA_RANGE: DateRange = DateRange.ThisMonth;
|
||||
export const DEFAULT_TREND_CHART_DATA_RANGE: DateRange = DateRange.ThisYear;
|
||||
export const DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE: DateRange = DateRange.ThisYear;
|
||||
|
||||
@@ -100,7 +100,7 @@ import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
import PieChartComponent from '@/components/desktop/PieChart.vue';
|
||||
import RadarChartComponent from '@/components/desktop/RadarChart.vue';
|
||||
import MonthlyTrendsChart from '@/components/desktop/MonthlyTrendsChart.vue';
|
||||
import TrendsChart from '@/components/desktop/TrendsChart.vue';
|
||||
import DateRangeSelectionDialog from '@/components/desktop/DateRangeSelectionDialog.vue';
|
||||
import MonthSelectionDialog from '@/components/desktop/MonthSelectionDialog.vue';
|
||||
import MonthRangeSelectionDialog from '@/components/desktop/MonthRangeSelectionDialog.vue';
|
||||
@@ -542,7 +542,7 @@ app.component('ConfirmDialog', ConfirmDialog);
|
||||
app.component('SnackBar', SnackBar);
|
||||
app.component('PieChart', PieChartComponent);
|
||||
app.component('RadarChart', RadarChartComponent);
|
||||
app.component('MonthlyTrendsChart', MonthlyTrendsChart);
|
||||
app.component('TrendsChart', TrendsChart);
|
||||
app.component('DateRangeSelectionDialog', DateRangeSelectionDialog);
|
||||
app.component('MonthSelectionDialog', MonthSelectionDialog);
|
||||
app.component('MonthRangeSelectionDialog', MonthRangeSelectionDialog);
|
||||
|
||||
@@ -282,6 +282,10 @@ class MomentDateTime implements DateTime {
|
||||
return this.instance.utcOffset();
|
||||
}
|
||||
|
||||
public getDateTimeAfterDays(days: number): DateTime {
|
||||
return MomentDateTime.of(this.instance.clone().add(days, 'days'));
|
||||
}
|
||||
|
||||
public toGregorianCalendarYearMonthDay(): YearMonthDay {
|
||||
return {
|
||||
year: this.instance.year(),
|
||||
|
||||
@@ -75,6 +75,8 @@ import type {
|
||||
TransactionStatisticResponse,
|
||||
TransactionStatisticTrendsRequest,
|
||||
TransactionStatisticTrendsResponseItem,
|
||||
TransactionStatisticAssetTrendsRequest,
|
||||
TransactionStatisticAssetTrendsResponseItem,
|
||||
TransactionAmountsRequestParams,
|
||||
TransactionAmountsResponse
|
||||
} from '@/models/transaction.ts';
|
||||
@@ -536,6 +538,19 @@ export default {
|
||||
|
||||
return axios.get<ApiResponse<TransactionStatisticTrendsResponseItem[]>>(`v1/transactions/statistics/trends.json?use_transaction_timezone=${req.useTransactionTimezone}` + (queryParams.length ? '&' + queryParams.join('&') : ''));
|
||||
},
|
||||
getTransactionStatisticsAssetTrends: (req: TransactionStatisticAssetTrendsRequest): ApiResponsePromise<TransactionStatisticAssetTrendsResponseItem[]> => {
|
||||
const queryParams = [];
|
||||
|
||||
if (req.startTime) {
|
||||
queryParams.push(`start_time=${req.startTime}`);
|
||||
}
|
||||
|
||||
if (req.endTime) {
|
||||
queryParams.push(`end_time=${req.endTime}`);
|
||||
}
|
||||
|
||||
return axios.get<ApiResponse<TransactionStatisticAssetTrendsResponseItem[]>>('v1/transactions/statistics/asset_trends.json' + (queryParams.length ? '?' + queryParams.join('&') : ''));
|
||||
},
|
||||
getTransactionAmounts: (params: TransactionAmountsRequestParams, excludeAccountIds: string[], excludeCategoryIds: string[]): ApiResponsePromise<TransactionAmountsResponse> => {
|
||||
const req = TransactionAmountsRequest.of(params);
|
||||
let queryParams = req.buildQuery();
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Transaktionsstatistiken können nicht abgerufen werden",
|
||||
"Categorical Analysis": "Kategorische Analyse",
|
||||
"Trend Analysis": "Trendanalyse",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Gesamtbetrag",
|
||||
"Total Assets": "Gesamtvermögen",
|
||||
"Total Liabilities": "Gesamtverbindlichkeiten",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Allgemeine Einstellungen",
|
||||
"Categorical Analysis Settings": "Einstellungen für kategorische Analyse",
|
||||
"Trend Analysis Settings": "Einstellungen für Trendanalyse",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Diagrammtyp",
|
||||
"Default Chart Type": "Standarddiagrammtyp",
|
||||
"Chart Data Type": "Diagrammdatentyp",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Nach Anzeigereihenfolge sortieren",
|
||||
"Sort by Name": "Nach Name sortieren",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Nach Monat aggregieren",
|
||||
"Aggregate by Quarter": "Nach Quartal aggregieren",
|
||||
"Aggregate by Year": "Nach Jahr aggregieren",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Unable to retrieve transaction statistics",
|
||||
"Categorical Analysis": "Categorical Analysis",
|
||||
"Trend Analysis": "Trend Analysis",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Total Amount",
|
||||
"Total Assets": "Total Assets",
|
||||
"Total Liabilities": "Total Liabilities",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Common Settings",
|
||||
"Categorical Analysis Settings": "Categorical Analysis Settings",
|
||||
"Trend Analysis Settings": "Trend Analysis Settings",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Chart Type",
|
||||
"Default Chart Type": "Default Chart Type",
|
||||
"Chart Data Type": "Chart Data Type",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Sort by Display Order",
|
||||
"Sort by Name": "Sort by Name",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Aggregate by Month",
|
||||
"Aggregate by Quarter": "Aggregate by Quarter",
|
||||
"Aggregate by Year": "Aggregate by Year",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "No se pueden recuperar estadísticas de transacciones",
|
||||
"Categorical Analysis": "Análisis categórico",
|
||||
"Trend Analysis": "Análisis de tendencias",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Importe Total",
|
||||
"Total Assets": "Activos totales",
|
||||
"Total Liabilities": "Pasivos totales",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Configuraciones comunes",
|
||||
"Categorical Analysis Settings": "Configuración de análisis categórico",
|
||||
"Trend Analysis Settings": "Configuración de análisis de tendencias",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Tipo de gráfico",
|
||||
"Default Chart Type": "Tipo de gráfico predeterminado",
|
||||
"Chart Data Type": "Tipo de datos del gráfico",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Ordenar por orden de visualización",
|
||||
"Sort by Name": "Ordenar por Nombre",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Agregado por mes",
|
||||
"Aggregate by Quarter": "Agregado por trimestre",
|
||||
"Aggregate by Year": "Agregado por año",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Impossible de récupérer les statistiques de transaction",
|
||||
"Categorical Analysis": "Analyse catégorielle",
|
||||
"Trend Analysis": "Analyse de tendance",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Montant total",
|
||||
"Total Assets": "Total des actifs",
|
||||
"Total Liabilities": "Total des passifs",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total des sorties",
|
||||
"Total Inflows": "Total des entrées",
|
||||
"Net Income": "Revenus nets",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Flux de trésorerie net",
|
||||
"Total Transactions": "Total des transactions",
|
||||
"Opening Balance": "Solde d'ouverture",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Paramètres communs",
|
||||
"Categorical Analysis Settings": "Paramètres d'analyse catégorielle",
|
||||
"Trend Analysis Settings": "Paramètres d'analyse de tendance",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Type de graphique",
|
||||
"Default Chart Type": "Type de graphique par défaut",
|
||||
"Chart Data Type": "Type de données du graphique",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Trier par ordre d'affichage",
|
||||
"Sort by Name": "Trier par nom",
|
||||
"Time Granularity": "Granularité temporelle",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Agréger par mois",
|
||||
"Aggregate by Quarter": "Agréger par trimestre",
|
||||
"Aggregate by Year": "Agréger par année",
|
||||
|
||||
@@ -598,9 +598,9 @@ export function useI18n() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getLocalizedChartDateAggregationTypeAndDisplayName(fullName: boolean): TypeAndDisplayName[] {
|
||||
function getLocalizedChartDateAggregationTypeAndDisplayName(analysisType: StatisticsAnalysisType, fullName: boolean): TypeAndDisplayName[] {
|
||||
const ret: TypeAndDisplayName[] = [];
|
||||
const allTypes: ChartDateAggregationType[] = ChartDateAggregationType.values();
|
||||
const allTypes: ChartDateAggregationType[] = ChartDateAggregationType.values(analysisType);
|
||||
|
||||
for (const type of allTypes) {
|
||||
ret.push({
|
||||
@@ -2354,8 +2354,8 @@ export function useI18n() {
|
||||
getAllAccountBalanceTrendChartTypes: () => getLocalizedDisplayNameAndType(AccountBalanceTrendChartType.values()),
|
||||
getAllStatisticsChartDataTypes: (analysisType: StatisticsAnalysisType, withDesktopOnlyChart?: boolean) => getLocalizedDisplayNameAndType(ChartDataType.values(analysisType, withDesktopOnlyChart)),
|
||||
getAllStatisticsSortingTypes: () => getLocalizedDisplayNameAndType(ChartSortingType.values()),
|
||||
getAllStatisticsDateAggregationTypes: () => getLocalizedChartDateAggregationTypeAndDisplayName(true),
|
||||
getAllStatisticsDateAggregationTypesWithShortName: () => getLocalizedChartDateAggregationTypeAndDisplayName(false),
|
||||
getAllStatisticsDateAggregationTypes: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, true),
|
||||
getAllStatisticsDateAggregationTypesWithShortName: (analysisType: StatisticsAnalysisType) => getLocalizedChartDateAggregationTypeAndDisplayName(analysisType, false),
|
||||
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
|
||||
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
|
||||
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Impossibile recuperare le statistiche delle transazioni",
|
||||
"Categorical Analysis": "Analisi per categoria",
|
||||
"Trend Analysis": "Analisi dell'andamento",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Importo totale",
|
||||
"Total Assets": "Patrimonio totale",
|
||||
"Total Liabilities": "Passività totali",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Impostazioni comuni",
|
||||
"Categorical Analysis Settings": "Impostazioni analisi per categoria",
|
||||
"Trend Analysis Settings": "Impostazioni analisi dell'andamento",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Tipo di grafico",
|
||||
"Default Chart Type": "Tipo di grafico predefinito",
|
||||
"Chart Data Type": "Tipo di dati grafico",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Ordina per ordine di visualizzazione",
|
||||
"Sort by Name": "Ordina per nome",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Aggrega per mese",
|
||||
"Aggregate by Quarter": "Aggrega per trimestre",
|
||||
"Aggregate by Year": "Aggrega per anno",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "取引統計を取得できません",
|
||||
"Categorical Analysis": "カテゴリ分析",
|
||||
"Trend Analysis": "傾向分析",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "合計金額",
|
||||
"Total Assets": "総資産",
|
||||
"Total Liabilities": "総負債",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "共通設定",
|
||||
"Categorical Analysis Settings": "カテゴリ分析設定",
|
||||
"Trend Analysis Settings": "傾向分析設定",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "グラフの種類",
|
||||
"Default Chart Type": "デフォルトのグラフの種類",
|
||||
"Chart Data Type": "グラフデータの種類",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "表示で並べ替え",
|
||||
"Sort by Name": "名前で並べ替え",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "月ごとに集計",
|
||||
"Aggregate by Quarter": "四半期ごとに集計",
|
||||
"Aggregate by Year": "年ごとに集計",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "거래 통계를 검색할 수 없습니다",
|
||||
"Categorical Analysis": "범주 분석",
|
||||
"Trend Analysis": "추세 분석",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "총 금액",
|
||||
"Total Assets": "총 자산",
|
||||
"Total Liabilities": "총 부채",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "총 유출",
|
||||
"Total Inflows": "총 유입",
|
||||
"Net Income": "순수익",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "순현금흐름",
|
||||
"Total Transactions": "총 거래 수",
|
||||
"Opening Balance": "기초 잔액",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "일반 설정",
|
||||
"Categorical Analysis Settings": "범주 분석 설정",
|
||||
"Trend Analysis Settings": "추세 분석 설정",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "차트 유형",
|
||||
"Default Chart Type": "기본 차트 유형",
|
||||
"Chart Data Type": "차트 데이터 유형",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "표시 순서별 정렬",
|
||||
"Sort by Name": "이름별 정렬",
|
||||
"Time Granularity": "시간 세분화",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "월별 집계",
|
||||
"Aggregate by Quarter": "분기별 집계",
|
||||
"Aggregate by Year": "연도별 집계",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Kan transactiestatistieken niet ophalen",
|
||||
"Categorical Analysis": "Categorische analyse",
|
||||
"Trend Analysis": "Trendanalyse",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Totaalbedrag",
|
||||
"Total Assets": "Totaal activa",
|
||||
"Total Liabilities": "Totaal passiva",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Totaal uitgaand",
|
||||
"Total Inflows": "Totaal inkomend",
|
||||
"Net Income": "Netto-inkomen",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Netto-kasstroom",
|
||||
"Total Transactions": "Totaal transacties",
|
||||
"Opening Balance": "Openingssaldo",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Algemene instellingen",
|
||||
"Categorical Analysis Settings": "Instellingen categorische analyse",
|
||||
"Trend Analysis Settings": "Instellingen trendanalyse",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Diagramtype",
|
||||
"Default Chart Type": "Standaard diagramtype",
|
||||
"Chart Data Type": "Diagram-gegevenstype",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Sorteren op weergavevolgorde",
|
||||
"Sort by Name": "Sorteren op naam",
|
||||
"Time Granularity": "Tijdgranulariteit",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Groeperen per maand",
|
||||
"Aggregate by Quarter": "Groeperen per kwartaal",
|
||||
"Aggregate by Year": "Groeperen per jaar",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Não é possível recuperar estatísticas da transação",
|
||||
"Categorical Analysis": "Análise Categórica",
|
||||
"Trend Analysis": "Análise de Tendência",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Montante Total",
|
||||
"Total Assets": "Total de Ativos",
|
||||
"Total Liabilities": "Total de Passivos",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Configurações Comuns",
|
||||
"Categorical Analysis Settings": "Configurações de Análise Categórica",
|
||||
"Trend Analysis Settings": "Configurações de Análise de Tendência",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Tipo de Gráfico",
|
||||
"Default Chart Type": "Tipo de Gráfico Padrão",
|
||||
"Chart Data Type": "Tipo de Dados do Gráfico",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Classificar por Ordem de Exibição",
|
||||
"Sort by Name": "Classificar por Nome",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Agregado por Mês",
|
||||
"Aggregate by Quarter": "Agregado por Trimestre",
|
||||
"Aggregate by Year": "Agregado por Ano",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Не удалось получить статистику транзакций",
|
||||
"Categorical Analysis": "Категориальный анализ",
|
||||
"Trend Analysis": "Анализ тенденций",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Общая сумма",
|
||||
"Total Assets": "Общие активы",
|
||||
"Total Liabilities": "Общие обязательства",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Общие настройки",
|
||||
"Categorical Analysis Settings": "Настройки категориального анализа",
|
||||
"Trend Analysis Settings": "Настройки анализа тенденций",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Тип диаграммы",
|
||||
"Default Chart Type": "Тип диаграммы по умолчанию",
|
||||
"Chart Data Type": "Тип данных диаграммы",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Сортировать по порядку отображения",
|
||||
"Sort by Name": "Сортировать по имени",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Агрегировать по месяцам",
|
||||
"Aggregate by Quarter": "Агрегировать по кварталам",
|
||||
"Aggregate by Year": "Агрегировать по годам",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "ไม่สามารถดึงสถิติรายการได้",
|
||||
"Categorical Analysis": "วิเคราะห์ตามหมวดหมู่",
|
||||
"Trend Analysis": "วิเคราะห์แนวโน้ม",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "จำนวนรวม",
|
||||
"Total Assets": "สินทรัพย์รวม",
|
||||
"Total Liabilities": "หนี้สินรวม",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "เงินไหลออกรวม",
|
||||
"Total Inflows": "เงินไหลเข้ารวม",
|
||||
"Net Income": "รายได้สุทธิ",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "กระแสเงินสดสุทธิ",
|
||||
"Total Transactions": "จำนวนรายการทั้งหมด",
|
||||
"Opening Balance": "ยอดเริ่มต้น",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "การตั้งค่าทั่วไป",
|
||||
"Categorical Analysis Settings": "การตั้งค่าวิเคราะห์ตามหมวดหมู่",
|
||||
"Trend Analysis Settings": "การตั้งค่าวิเคราะห์แนวโน้ม",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "ประเภทกราฟ",
|
||||
"Default Chart Type": "ประเภทกราฟเริ่มต้น",
|
||||
"Chart Data Type": "ประเภทข้อมูลกราฟ",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "เรียงตามลำดับการแสดง",
|
||||
"Sort by Name": "เรียงตามชื่อ",
|
||||
"Time Granularity": "ความละเอียดเวลา",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "รวมตามเดือน",
|
||||
"Aggregate by Quarter": "รวมตามไตรมาส",
|
||||
"Aggregate by Year": "รวมตามปี",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Не вдалося отримати статистику транзакцій",
|
||||
"Categorical Analysis": "Аналіз за категоріями",
|
||||
"Trend Analysis": "Аналіз трендів",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Загальна сума",
|
||||
"Total Assets": "Загальні активи",
|
||||
"Total Liabilities": "Загальні зобов’язання",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Загальні налаштування",
|
||||
"Categorical Analysis Settings": "Налаштування аналізу за категоріями",
|
||||
"Trend Analysis Settings": "Налаштування аналізу трендів",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Тип діаграми",
|
||||
"Default Chart Type": "Тип діаграми за замовчуванням",
|
||||
"Chart Data Type": "Тип даних діаграми",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Сортувати за порядком відображення",
|
||||
"Sort by Name": "Сортувати за назвою",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Агрегувати за місяцями",
|
||||
"Aggregate by Quarter": "Агрегувати за кварталами",
|
||||
"Aggregate by Year": "Агрегувати за роками",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "Không thể lấy thống kê giao dịch",
|
||||
"Categorical Analysis": "Phân tích theo danh mục",
|
||||
"Trend Analysis": "Phân tích xu hướng",
|
||||
"Asset Trends": "Asset Trends",
|
||||
"Total Amount": "Tổng số tiền",
|
||||
"Total Assets": "Tổng tài sản",
|
||||
"Total Liabilities": "Tổng nợ phải trả",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "Total Outflows",
|
||||
"Total Inflows": "Total Inflows",
|
||||
"Net Income": "Net Income",
|
||||
"Net Worth": "Net Worth",
|
||||
"Net Cash Flow": "Net Cash Flow",
|
||||
"Total Transactions": "Total Transactions",
|
||||
"Opening Balance": "Opening Balance",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "Cài đặt chung",
|
||||
"Categorical Analysis Settings": "Cài đặt phân tích theo danh mục",
|
||||
"Trend Analysis Settings": "Cài đặt phân tích xu hướng",
|
||||
"Asset Trends Settings": "Asset Trends Settings",
|
||||
"Chart Type": "Loại biểu đồ",
|
||||
"Default Chart Type": "Loại biểu đồ mặc định",
|
||||
"Chart Data Type": "Loại dữ liệu biểu đồ",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "Sắp xếp theo thứ tự hiển thị",
|
||||
"Sort by Name": "Sắp xếp theo tên",
|
||||
"Time Granularity": "Time Granularity",
|
||||
"Aggregate by Day": "Aggregate by Day",
|
||||
"Aggregate by Month": "Tổng hợp theo tháng",
|
||||
"Aggregate by Quarter": "Tổng hợp theo quý",
|
||||
"Aggregate by Year": "Tổng hợp theo năm",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "无法获取交易统计数据",
|
||||
"Categorical Analysis": "分类分析",
|
||||
"Trend Analysis": "趋势分析",
|
||||
"Asset Trends": "资产趋势",
|
||||
"Total Amount": "总金额",
|
||||
"Total Assets": "总资产",
|
||||
"Total Liabilities": "总负债",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "总流出",
|
||||
"Total Inflows": "总流入",
|
||||
"Net Income": "净收入",
|
||||
"Net Worth": "净资产",
|
||||
"Net Cash Flow": "净现金流",
|
||||
"Total Transactions": "总交易数",
|
||||
"Opening Balance": "期初余额",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "通用设置",
|
||||
"Categorical Analysis Settings": "分类分析设置",
|
||||
"Trend Analysis Settings": "趋势分析设置",
|
||||
"Asset Trends Settings": "资产趋势设置",
|
||||
"Chart Type": "图表类型",
|
||||
"Default Chart Type": "默认图表类型",
|
||||
"Chart Data Type": "图表数据类型",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "按显示顺序排序",
|
||||
"Sort by Name": "按名称排序",
|
||||
"Time Granularity": "时间粒度",
|
||||
"Aggregate by Day": "按日聚合",
|
||||
"Aggregate by Month": "按月聚合",
|
||||
"Aggregate by Quarter": "按季度聚合",
|
||||
"Aggregate by Year": "按年聚合",
|
||||
|
||||
@@ -1982,6 +1982,7 @@
|
||||
"Unable to retrieve transaction statistics": "無法取得交易統計資料",
|
||||
"Categorical Analysis": "分類分析",
|
||||
"Trend Analysis": "趨勢分析",
|
||||
"Asset Trends": "資產趨勢",
|
||||
"Total Amount": "總金額",
|
||||
"Total Assets": "總資產",
|
||||
"Total Liabilities": "總負債",
|
||||
@@ -1990,6 +1991,7 @@
|
||||
"Total Outflows": "總流出",
|
||||
"Total Inflows": "總流入",
|
||||
"Net Income": "淨收入",
|
||||
"Net Worth": "淨資產",
|
||||
"Net Cash Flow": "淨現金流量",
|
||||
"Total Transactions": "總交易數",
|
||||
"Opening Balance": "期初餘額",
|
||||
@@ -2012,6 +2014,7 @@
|
||||
"Common Settings": "一般設定",
|
||||
"Categorical Analysis Settings": "分類分析設定",
|
||||
"Trend Analysis Settings": "趨勢分析設定",
|
||||
"Asset Trends Settings": "資產趨勢設定",
|
||||
"Chart Type": "圖表類型",
|
||||
"Default Chart Type": "預設圖表類型",
|
||||
"Chart Data Type": "圖表資料類型",
|
||||
@@ -2033,6 +2036,7 @@
|
||||
"Sort by Display Order": "依顯示順序排序",
|
||||
"Sort by Name": "依名稱排序",
|
||||
"Time Granularity": "時間粒度",
|
||||
"Aggregate by Day": "依日彙整",
|
||||
"Aggregate by Month": "依月份彙整",
|
||||
"Aggregate by Quarter": "依季度彙整",
|
||||
"Aggregate by Year": "依年份彙整",
|
||||
|
||||
@@ -56,7 +56,7 @@ import TransactionCalendar from '@/components/common/TransactionCalendar.vue';
|
||||
import ItemIcon from '@/components/mobile/ItemIcon.vue';
|
||||
import LanguageSelectButton from '@/components/mobile/LanguageSelectButton.vue';
|
||||
import PieChart from '@/components/mobile/PieChart.vue';
|
||||
import MonthlyTrendsBarChart from '@/components/mobile/MonthlyTrendsBarChart.vue';
|
||||
import TrendsBarChart from '@/components/mobile/TrendsBarChart.vue';
|
||||
import PinCodeInputSheet from '@/components/mobile/PinCodeInputSheet.vue';
|
||||
import PasswordInputSheet from '@/components/mobile/PasswordInputSheet.vue';
|
||||
import PasscodeInputSheet from '@/components/mobile/PasscodeInputSheet.vue';
|
||||
@@ -150,7 +150,7 @@ app.component('TransactionCalendar', TransactionCalendar);
|
||||
app.component('ItemIcon', ItemIcon);
|
||||
app.component('LanguageSelectButton', LanguageSelectButton);
|
||||
app.component('PieChart', PieChart);
|
||||
app.component('MonthlyTrendsBarChart', MonthlyTrendsBarChart);
|
||||
app.component('TrendsBarChart', TrendsBarChart);
|
||||
app.component('PinCodeInputSheet', PinCodeInputSheet);
|
||||
app.component('PasswordInputSheet', PasswordInputSheet);
|
||||
app.component('PasscodeInputSheet', PasscodeInputSheet);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type PartialRecord, itemAndIndex } from '@/core/base.ts';
|
||||
import type { Year1BasedMonth, TextualYearMonthDay, StartEndTime, WeekDay } from '@/core/datetime.ts';
|
||||
import type { TextualYearMonthDay, Year1BasedMonth, YearMonthDay, StartEndTime, WeekDay } from '@/core/datetime.ts';
|
||||
import { type Coordinate, getNormalizedCoordinate } from '@/core/coordinate.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
|
||||
@@ -581,6 +581,11 @@ export interface TransactionStatisticTrendsRequest extends YearMonthRangeRequest
|
||||
readonly useTransactionTimezone: boolean;
|
||||
}
|
||||
|
||||
export interface TransactionStatisticAssetTrendsRequest {
|
||||
readonly startTime: number;
|
||||
readonly endTime: number;
|
||||
}
|
||||
|
||||
export const ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE = [
|
||||
'today',
|
||||
'thisWeek',
|
||||
@@ -710,12 +715,31 @@ export interface TransactionStatisticTrendsResponseItem {
|
||||
readonly items: TransactionStatisticResponseItem[];
|
||||
}
|
||||
|
||||
export interface TransactionStatisticAssetTrendsResponseItem extends YearMonthDay {
|
||||
readonly year: number;
|
||||
readonly month: number; // 1-based (1 = January, 12 = December)
|
||||
readonly day: number;
|
||||
readonly items: TransactionStatisticAssetTrendsResponseDataItem[];
|
||||
}
|
||||
|
||||
export interface TransactionStatisticAssetTrendsResponseDataItem {
|
||||
readonly accountId: string;
|
||||
readonly accountOpeningBalance: number;
|
||||
readonly accountClosingBalance: number;
|
||||
}
|
||||
|
||||
export interface YearMonthDataItem extends Year1BasedMonth, Record<string, unknown> {}
|
||||
|
||||
export interface YearMonthDayDataItem extends YearMonthDay, Record<string, unknown> {}
|
||||
|
||||
export interface YearMonthItems<T extends Year1BasedMonth> extends Record<string, unknown> {
|
||||
readonly items: T[];
|
||||
}
|
||||
|
||||
export interface YearMonthDayItems<T extends YearMonthDay> extends Record<string, unknown> {
|
||||
readonly items: T[];
|
||||
}
|
||||
|
||||
export interface SortableTransactionStatisticDataItem {
|
||||
readonly name: string;
|
||||
readonly displayOrders: number[];
|
||||
@@ -749,6 +773,13 @@ export interface TransactionStatisticTrendsResponseItemWithInfo {
|
||||
readonly items: TransactionStatisticResponseItemWithInfo[];
|
||||
}
|
||||
|
||||
export interface TransactionStatisticAssetTrendsResponseItemWithInfo {
|
||||
readonly year: number;
|
||||
readonly month: number; // 1-based (1 = January, 12 = December)
|
||||
readonly day: number;
|
||||
readonly items: TransactionStatisticResponseItemWithInfo[];
|
||||
}
|
||||
|
||||
export type TransactionStatisticDataItemType = 'category' | 'account' | 'total';
|
||||
|
||||
export interface TransactionStatisticDataItemBase extends SortableTransactionStatisticDataItem {
|
||||
@@ -820,6 +851,21 @@ export interface TransactionTrendsAnalysisDataAmount extends Record<string, unkn
|
||||
readonly totalAmount: number;
|
||||
}
|
||||
|
||||
export interface TransactionAssetTrendsAnalysisData {
|
||||
readonly items: TransactionAssetTrendsAnalysisDataItem[];
|
||||
}
|
||||
|
||||
export interface TransactionAssetTrendsAnalysisDataItem extends Record<string, unknown>, TransactionStatisticDataItemBase {
|
||||
readonly items: TransactionAssetTrendsAnalysisDataAmount[];
|
||||
}
|
||||
|
||||
export interface TransactionAssetTrendsAnalysisDataAmount extends Record<string, unknown>, YearMonthDay {
|
||||
readonly year: number;
|
||||
readonly month: number;
|
||||
readonly day: number;
|
||||
readonly totalAmount: number;
|
||||
}
|
||||
|
||||
export type TransactionAmountsResponse = PartialRecord<TransactionAmountsRequestType, TransactionAmountsResponseItem>;
|
||||
|
||||
export interface TransactionAmountsResponseItem {
|
||||
|
||||
@@ -134,7 +134,8 @@ const router = createRouter({
|
||||
initTagFilterType: route.query['tagFilterType'],
|
||||
initKeyword: route.query['keyword'],
|
||||
initSortingType: route.query['sortingType'],
|
||||
initTrendDateAggregationType: route.query['trendDateAggregationType']
|
||||
initTrendDateAggregationType: route.query['trendDateAggregationType'],
|
||||
initAssetTrendsDateAggregationType: route.query['assetTrendsDateAggregationType']
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -314,6 +314,18 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
updateUserApplicationCloudSettingValue('statistics.defaultTrendChartDataRangeType', value);
|
||||
}
|
||||
|
||||
function setStatisticsDefaultAssetTrendsChartType(value: number): void {
|
||||
updateApplicationSettingsSubValue('statistics', 'defaultAssetTrendsChartType', value);
|
||||
appSettings.value.statistics.defaultAssetTrendsChartType = value;
|
||||
updateUserApplicationCloudSettingValue('statistics.defaultAssetTrendsChartType', value);
|
||||
}
|
||||
|
||||
function setStatisticsDefaultAssetTrendsChartDateRange(value: number): void {
|
||||
updateApplicationSettingsSubValue('statistics', 'defaultAssetTrendsChartDataRangeType', value);
|
||||
appSettings.value.statistics.defaultAssetTrendsChartDataRangeType = value;
|
||||
updateUserApplicationCloudSettingValue('statistics.defaultAssetTrendsChartDataRangeType', value);
|
||||
}
|
||||
|
||||
function clearAppSettings(): void {
|
||||
clearSettings();
|
||||
appSettings.value = getApplicationSettings();
|
||||
@@ -469,6 +481,8 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
setStatisticsDefaultCategoricalChartDateRange,
|
||||
setStatisticsDefaultTrendChartType,
|
||||
setStatisticsDefaultTrendChartDateRange,
|
||||
setStatisticsDefaultAssetTrendsChartType,
|
||||
setStatisticsDefaultAssetTrendsChartDateRange,
|
||||
clearAppSettings,
|
||||
createApplicationCloudSettings,
|
||||
setApplicationSettingsFromCloudSettings,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
||||
import { useExchangeRatesStore } from './exchangeRates.ts';
|
||||
|
||||
import { entries, values } from '@/core/base.ts';
|
||||
import { type TextualYearMonth, type TimeRangeAndDateType, DateRangeScene, DateRange } from '@/core/datetime.ts';
|
||||
import { type DateTime, type TextualYearMonth, type TimeRangeAndDateType, DateRangeScene, DateRange } from '@/core/datetime.ts';
|
||||
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
||||
import { CategoryType } from '@/core/category.ts';
|
||||
import {
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
ChartSortingType,
|
||||
ChartDateAggregationType,
|
||||
DEFAULT_CATEGORICAL_CHART_DATA_RANGE,
|
||||
DEFAULT_TREND_CHART_DATA_RANGE
|
||||
DEFAULT_TREND_CHART_DATA_RANGE,
|
||||
DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE
|
||||
} from '@/core/statistics.ts';
|
||||
import { DEFAULT_ACCOUNT_ICON, DEFAULT_CATEGORY_ICON } from '@/consts/icon.ts';
|
||||
import { DEFAULT_ACCOUNT_COLOR, DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
||||
@@ -32,9 +33,12 @@ import {
|
||||
type TransactionStatisticResponse,
|
||||
type TransactionStatisticResponseItem,
|
||||
type TransactionStatisticTrendsResponseItem,
|
||||
type TransactionStatisticAssetTrendsResponseItem,
|
||||
type TransactionStatisticAssetTrendsResponseDataItem,
|
||||
type TransactionStatisticResponseItemWithInfo,
|
||||
type TransactionStatisticResponseWithInfo,
|
||||
type TransactionStatisticTrendsResponseItemWithInfo,
|
||||
type TransactionStatisticAssetTrendsResponseItemWithInfo,
|
||||
type TransactionStatisticDataItemType,
|
||||
type TransactionStatisticDataItemBase,
|
||||
type TransactionCategoricalOverviewAnalysisData,
|
||||
@@ -44,6 +48,9 @@ import {
|
||||
type TransactionTrendsAnalysisData,
|
||||
type TransactionTrendsAnalysisDataItem,
|
||||
type TransactionTrendsAnalysisDataAmount,
|
||||
type TransactionAssetTrendsAnalysisData,
|
||||
type TransactionAssetTrendsAnalysisDataItem,
|
||||
type TransactionAssetTrendsAnalysisDataAmount,
|
||||
TransactionCategoricalOverviewAnalysisDataItemType
|
||||
} from '@/models/transaction.ts';
|
||||
|
||||
@@ -58,7 +65,12 @@ import {
|
||||
isObjectEmpty,
|
||||
objectFieldToArrayItem
|
||||
} from '@/lib/common.ts';
|
||||
import { getGregorianCalendarYearAndMonthFromUnixTime, getDateRangeByDateType } from '@/lib/datetime.ts';
|
||||
import {
|
||||
getYearMonthDayDateTime,
|
||||
getGregorianCalendarYearAndMonthFromUnixTime,
|
||||
getDayDifference,
|
||||
getDateRangeByDateType
|
||||
} from '@/lib/datetime.ts';
|
||||
import { getFinalAccountIdsByFilteredAccountIds } from '@/lib/account.ts';
|
||||
import { getFinalCategoryIdsByFilteredCategoryIds } from '@/lib/category.ts';
|
||||
import { sortStatisticsItems } from '@/lib/statistics.ts';
|
||||
@@ -83,7 +95,7 @@ interface WritableTransactionCategoricalAnalysisDataItem extends Record<string,
|
||||
percent?: number;
|
||||
}
|
||||
|
||||
interface WritableTransactionTrendsAnalysisDataItem extends Record<string, unknown> {
|
||||
interface WritableTransactionTrendsAnalysisDataItem extends Record<string, unknown>, TransactionTrendsAnalysisDataItem {
|
||||
name: string;
|
||||
type: TransactionStatisticDataItemType;
|
||||
id: string;
|
||||
@@ -95,6 +107,18 @@ interface WritableTransactionTrendsAnalysisDataItem extends Record<string, unkno
|
||||
items: TransactionTrendsAnalysisDataAmount[];
|
||||
}
|
||||
|
||||
interface WritableTransactionAssetTrendsAnalysisDataItem extends Record<string, unknown>, TransactionAssetTrendsAnalysisDataItem {
|
||||
name: string;
|
||||
type: TransactionStatisticDataItemType;
|
||||
id: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
hidden: boolean;
|
||||
displayOrders: number[];
|
||||
totalAmount: number;
|
||||
items: TransactionAssetTrendsAnalysisDataAmount[];
|
||||
}
|
||||
|
||||
export interface TransactionStatisticsPartialFilter {
|
||||
chartDataType?: number;
|
||||
categoricalChartType?: number;
|
||||
@@ -105,6 +129,10 @@ export interface TransactionStatisticsPartialFilter {
|
||||
trendChartDateType?: number;
|
||||
trendChartStartYearMonth?: TextualYearMonth | '';
|
||||
trendChartEndYearMonth?: TextualYearMonth | '';
|
||||
assetTrendsChartType?: number;
|
||||
assetTrendsChartDateType?: number;
|
||||
assetTrendsChartStartTime?: number;
|
||||
assetTrendsChartEndTime?: number;
|
||||
filterAccountIds?: Record<string, boolean>;
|
||||
filterCategoryIds?: Record<string, boolean>;
|
||||
tagIds?: string;
|
||||
@@ -123,6 +151,10 @@ export interface TransactionStatisticsFilter extends TransactionStatisticsPartia
|
||||
trendChartDateType: number;
|
||||
trendChartStartYearMonth: TextualYearMonth | '';
|
||||
trendChartEndYearMonth: TextualYearMonth | '';
|
||||
assetTrendsChartType: number;
|
||||
assetTrendsChartDateType: number;
|
||||
assetTrendsChartStartTime: number;
|
||||
assetTrendsChartEndTime: number;
|
||||
filterAccountIds: Record<string, boolean>;
|
||||
filterCategoryIds: Record<string, boolean>;
|
||||
tagIds: string;
|
||||
@@ -148,6 +180,10 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
trendChartDateType: DEFAULT_TREND_CHART_DATA_RANGE.type,
|
||||
trendChartStartYearMonth: '',
|
||||
trendChartEndYearMonth: '',
|
||||
assetTrendsChartType: TrendChartType.Default.type,
|
||||
assetTrendsChartDateType: DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE.type,
|
||||
assetTrendsChartStartTime: 0,
|
||||
assetTrendsChartEndTime: 0,
|
||||
filterAccountIds: {},
|
||||
filterCategoryIds: {},
|
||||
tagIds: '',
|
||||
@@ -158,6 +194,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
|
||||
const transactionCategoryStatisticsData = ref<TransactionStatisticResponse | null>(null);
|
||||
const transactionCategoryTrendsData = ref<TransactionStatisticTrendsResponseItem[]>([]);
|
||||
const transactionAssetTrendsData = ref<TransactionStatisticAssetTrendsResponseItem[]>([]);
|
||||
const transactionStatisticsStateInvalid = ref<boolean>(true);
|
||||
|
||||
const categoricalAnalysisChartDataCategory = computed<string>(() => {
|
||||
@@ -709,6 +746,227 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
return trendsData;
|
||||
});
|
||||
|
||||
const assetTrendsDataWithAccountInfo = computed<TransactionStatisticAssetTrendsResponseItemWithInfo[]>(() => {
|
||||
const assetTrendsData = transactionAssetTrendsData.value;
|
||||
const finalAssetTrendsData: TransactionStatisticAssetTrendsResponseItemWithInfo[] = [];
|
||||
|
||||
if (!assetTrendsData || !assetTrendsData.length) {
|
||||
return finalAssetTrendsData;
|
||||
}
|
||||
|
||||
const firstAssetTrendItem: TransactionStatisticAssetTrendsResponseItem | undefined = assetTrendsData[0];
|
||||
|
||||
if (!firstAssetTrendItem) {
|
||||
return finalAssetTrendsData;
|
||||
}
|
||||
|
||||
const lastAssetTrendItemMap: Record<string, TransactionStatisticAssetTrendsResponseDataItem> = {};
|
||||
let lastAssetTrendItem: TransactionStatisticAssetTrendsResponseItem = firstAssetTrendItem;
|
||||
|
||||
for (const item of firstAssetTrendItem.items) {
|
||||
lastAssetTrendItemMap[item.accountId] = item;
|
||||
}
|
||||
|
||||
for (const assetTrendItem of assetTrendsData) {
|
||||
const statisticResponseItems: TransactionStatisticResponseItem[] = [];
|
||||
const existedAccountIds: Record<string, boolean> = {};
|
||||
const missingDays: number = getDayDifference(lastAssetTrendItem, assetTrendItem) - 1;
|
||||
const lastAssetTrendItemDate: DateTime = getYearMonthDayDateTime(lastAssetTrendItem.year, lastAssetTrendItem.month, lastAssetTrendItem.day);
|
||||
|
||||
// fill in missing days with last known balance
|
||||
for (let i = 1; i <= missingDays; i++) {
|
||||
const missingStatisticResponseItems: TransactionStatisticResponseItem[] = [];
|
||||
const dateTime: DateTime = lastAssetTrendItemDate.getDateTimeAfterDays(i);
|
||||
|
||||
for (const item of values(lastAssetTrendItemMap)) {
|
||||
const statisticResponseItem: TransactionStatisticResponseItem = {
|
||||
categoryId: '',
|
||||
accountId: item.accountId,
|
||||
amount: item.accountClosingBalance
|
||||
};
|
||||
|
||||
missingStatisticResponseItems.push(statisticResponseItem);
|
||||
}
|
||||
|
||||
const finalAssetTrendItem: TransactionStatisticAssetTrendsResponseItemWithInfo = {
|
||||
year: dateTime.getGregorianCalendarYear(),
|
||||
month: dateTime.getGregorianCalendarMonth(),
|
||||
day: dateTime.getGregorianCalendarDay(),
|
||||
items: assembleAccountAndCategoryInfo(missingStatisticResponseItems)
|
||||
};
|
||||
|
||||
lastAssetTrendItem = assetTrendItem;
|
||||
finalAssetTrendsData.push(finalAssetTrendItem);
|
||||
}
|
||||
|
||||
// fill in current day data
|
||||
for (const item of assetTrendItem.items) {
|
||||
const statisticResponseItem: TransactionStatisticResponseItem = {
|
||||
categoryId: '',
|
||||
accountId: item.accountId,
|
||||
amount: item.accountClosingBalance
|
||||
};
|
||||
|
||||
lastAssetTrendItemMap[item.accountId] = item;
|
||||
existedAccountIds[item.accountId] = true;
|
||||
statisticResponseItems.push(statisticResponseItem);
|
||||
}
|
||||
|
||||
// fill in missing accounts with last known balance
|
||||
for (const item of values(lastAssetTrendItemMap)) {
|
||||
if (existedAccountIds[item.accountId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const statisticResponseItem: TransactionStatisticResponseItem = {
|
||||
categoryId: '',
|
||||
accountId: item.accountId,
|
||||
amount: item.accountClosingBalance
|
||||
};
|
||||
|
||||
existedAccountIds[item.accountId] = true;
|
||||
statisticResponseItems.push(statisticResponseItem);
|
||||
}
|
||||
|
||||
const finalAssetTrendItem: TransactionStatisticAssetTrendsResponseItemWithInfo = {
|
||||
year: assetTrendItem.year,
|
||||
month: assetTrendItem.month,
|
||||
day: assetTrendItem.day,
|
||||
items: assembleAccountAndCategoryInfo(statisticResponseItems)
|
||||
};
|
||||
|
||||
lastAssetTrendItem = assetTrendItem;
|
||||
finalAssetTrendsData.push(finalAssetTrendItem);
|
||||
}
|
||||
|
||||
return finalAssetTrendsData;
|
||||
});
|
||||
|
||||
const assetTrendsData = computed<TransactionAssetTrendsAnalysisData | null>(() => {
|
||||
if (!assetTrendsDataWithAccountInfo.value || !assetTrendsDataWithAccountInfo.value.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const combinedDataMap: Record<string, WritableTransactionAssetTrendsAnalysisDataItem> = {};
|
||||
|
||||
for (const dailyData of assetTrendsDataWithAccountInfo.value) {
|
||||
let dailyTotalAmount: number = 0;
|
||||
|
||||
for (const item of dailyData.items) {
|
||||
if (!item.primaryAccount || !item.account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.filterAccountIds && transactionStatisticsFilter.value.filterAccountIds[item.account.id]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isNumber(item.amountInDefaultCurrency)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type) {
|
||||
if (!item.account.isAsset) {
|
||||
continue;
|
||||
}
|
||||
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
|
||||
if (!item.account.isLiability) {
|
||||
continue;
|
||||
}
|
||||
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.NetWorth.type) {
|
||||
// Do Nothing
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let amount = item.amountInDefaultCurrency;
|
||||
|
||||
if (item.account.isLiability) {
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
|
||||
let data = combinedDataMap[item.account.id];
|
||||
|
||||
if (data) {
|
||||
data.totalAmount += amount;
|
||||
} else {
|
||||
data = {
|
||||
name: item.account.name,
|
||||
type: 'account',
|
||||
id: item.account.id,
|
||||
icon: item.account.icon || DEFAULT_ACCOUNT_ICON.icon,
|
||||
color: item.account.color || DEFAULT_ACCOUNT_COLOR,
|
||||
hidden: item.primaryAccount.hidden || item.account.hidden,
|
||||
displayOrders: [item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
||||
totalAmount: amount,
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
const amountItem: TransactionAssetTrendsAnalysisDataAmount = {
|
||||
year: dailyData.year,
|
||||
month: dailyData.month,
|
||||
day: dailyData.day,
|
||||
totalAmount: amount
|
||||
};
|
||||
data.items.push(amountItem);
|
||||
combinedDataMap[item.account.id] = data;
|
||||
}
|
||||
|
||||
if (item.account.isAsset) {
|
||||
dailyTotalAmount += amount;
|
||||
} else if (item.account.isLiability) {
|
||||
dailyTotalAmount -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.NetWorth.type) {
|
||||
let data = combinedDataMap['total'];
|
||||
|
||||
if (data) {
|
||||
data.totalAmount += dailyTotalAmount;
|
||||
} else {
|
||||
data = {
|
||||
name: ChartDataType.NetWorth.name,
|
||||
type: 'total',
|
||||
id: 'total',
|
||||
icon: '',
|
||||
color: '',
|
||||
hidden: false,
|
||||
displayOrders: [1],
|
||||
totalAmount: dailyTotalAmount,
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
const amountItem: TransactionAssetTrendsAnalysisDataAmount = {
|
||||
year: dailyData.year,
|
||||
month: dailyData.month,
|
||||
day: dailyData.day,
|
||||
totalAmount: dailyTotalAmount
|
||||
};
|
||||
data.items.push(amountItem);
|
||||
combinedDataMap['total'] = data;
|
||||
}
|
||||
}
|
||||
|
||||
const allAssetTrendsDataItems: TransactionAssetTrendsAnalysisDataItem[] = [];
|
||||
|
||||
for (const assetTrendsDataItem of values(combinedDataMap)) {
|
||||
allAssetTrendsDataItems.push(assetTrendsDataItem);
|
||||
}
|
||||
|
||||
sortCategoryTotalAmountItems(allAssetTrendsDataItems, transactionStatisticsFilter.value);
|
||||
|
||||
const assetTrendsData: TransactionAssetTrendsAnalysisData = {
|
||||
items: allAssetTrendsDataItems
|
||||
};
|
||||
|
||||
return assetTrendsData;
|
||||
});
|
||||
|
||||
function createNewTransactionCategoricalOverviewAnalysisDataItem(id: string, name: string, type: TransactionCategoricalOverviewAnalysisDataItemType, displayOrders: number[], hidden: boolean): TransactionCategoricalOverviewAnalysisDataItem {
|
||||
const dataItem: TransactionCategoricalOverviewAnalysisDataItem = {
|
||||
id: id,
|
||||
@@ -1062,6 +1320,10 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
transactionStatisticsFilter.value.trendChartDateType = DEFAULT_TREND_CHART_DATA_RANGE.type;
|
||||
transactionStatisticsFilter.value.trendChartStartYearMonth = '';
|
||||
transactionStatisticsFilter.value.trendChartEndYearMonth = '';
|
||||
transactionStatisticsFilter.value.assetTrendsChartType = TrendChartType.Default.type;
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE.type;
|
||||
transactionStatisticsFilter.value.assetTrendsChartStartTime = 0;
|
||||
transactionStatisticsFilter.value.assetTrendsChartEndTime = 0;
|
||||
transactionStatisticsFilter.value.filterAccountIds = {};
|
||||
transactionStatisticsFilter.value.filterCategoryIds = {};
|
||||
transactionStatisticsFilter.value.tagIds = '';
|
||||
@@ -1083,8 +1345,13 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
if (!ChartDataType.isAvailableForAnalysisType(transactionStatisticsFilter.value.chartDataType, analysisType)) {
|
||||
transactionStatisticsFilter.value.chartDataType = ChartDataType.Default.type;
|
||||
}
|
||||
} else if (analysisType === StatisticsAnalysisType.AssetTrends) {
|
||||
if (!ChartDataType.isAvailableForAnalysisType(transactionStatisticsFilter.value.chartDataType, analysisType)) {
|
||||
transactionStatisticsFilter.value.chartDataType = ChartDataType.DefaultForAssetTrends.type;
|
||||
}
|
||||
}
|
||||
|
||||
// Categorical Analysis filter initialization
|
||||
if (filter && isInteger(filter.categoricalChartType)) {
|
||||
transactionStatisticsFilter.value.categoricalChartType = filter.categoricalChartType;
|
||||
} else {
|
||||
@@ -1130,6 +1397,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Trend Analysis filter initialization
|
||||
if (filter && isInteger(filter.trendChartType)) {
|
||||
transactionStatisticsFilter.value.trendChartType = filter.trendChartType;
|
||||
} else {
|
||||
@@ -1175,6 +1443,53 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Asset Trends filter initialization
|
||||
if (filter && isInteger(filter.assetTrendsChartType)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartType = filter.assetTrendsChartType;
|
||||
} else {
|
||||
transactionStatisticsFilter.value.assetTrendsChartType = settingsStore.appSettings.statistics.defaultAssetTrendsChartType;
|
||||
}
|
||||
|
||||
if (!TrendChartType.isValidType(transactionStatisticsFilter.value.assetTrendsChartType)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartType = TrendChartType.Default.type;
|
||||
}
|
||||
|
||||
if (filter && isInteger(filter.assetTrendsChartDateType)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = filter.assetTrendsChartDateType;
|
||||
} else {
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = settingsStore.appSettings.statistics.defaultAssetTrendsChartDataRangeType;
|
||||
}
|
||||
|
||||
let assetTrendsChartDateTypeValid = true;
|
||||
|
||||
if (!DateRange.isAvailableForScene(transactionStatisticsFilter.value.assetTrendsChartDateType, DateRangeScene.AssetTrends)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = DEFAULT_ASSET_TRENDS_CHART_DATA_RANGE.type;
|
||||
assetTrendsChartDateTypeValid = false;
|
||||
}
|
||||
|
||||
if (assetTrendsChartDateTypeValid && transactionStatisticsFilter.value.assetTrendsChartDateType === DateRange.Custom.type) {
|
||||
if (filter && isInteger(filter.assetTrendsChartStartTime)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartStartTime = filter.assetTrendsChartStartTime;
|
||||
} else {
|
||||
transactionStatisticsFilter.value.assetTrendsChartStartTime = 0;
|
||||
}
|
||||
|
||||
if (filter && isInteger(filter.assetTrendsChartEndTime)) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartEndTime = filter.assetTrendsChartEndTime;
|
||||
} else {
|
||||
transactionStatisticsFilter.value.assetTrendsChartEndTime = 0;
|
||||
}
|
||||
} else {
|
||||
const assetTrendsChartDateRange = getDateRangeByDateType(transactionStatisticsFilter.value.assetTrendsChartDateType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart);
|
||||
|
||||
if (assetTrendsChartDateRange) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = assetTrendsChartDateRange.dateType;
|
||||
transactionStatisticsFilter.value.assetTrendsChartStartTime = assetTrendsChartDateRange.minTime;
|
||||
transactionStatisticsFilter.value.assetTrendsChartEndTime = assetTrendsChartDateRange.maxTime;
|
||||
}
|
||||
}
|
||||
|
||||
// Other filter initialization
|
||||
if (filter && isObject(filter.filterAccountIds)) {
|
||||
transactionStatisticsFilter.value.filterAccountIds = filter.filterAccountIds;
|
||||
} else {
|
||||
@@ -1224,6 +1539,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Categorical Analysis filter update
|
||||
if (filter && isInteger(filter.categoricalChartType) && transactionStatisticsFilter.value.categoricalChartType !== filter.categoricalChartType) {
|
||||
transactionStatisticsFilter.value.categoricalChartType = filter.categoricalChartType;
|
||||
changed = true;
|
||||
@@ -1244,6 +1560,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Trend Analysis filter update
|
||||
if (filter && isInteger(filter.trendChartType) && transactionStatisticsFilter.value.trendChartType !== filter.trendChartType) {
|
||||
transactionStatisticsFilter.value.trendChartType = filter.trendChartType;
|
||||
changed = true;
|
||||
@@ -1264,6 +1581,28 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Asset Trends filter update
|
||||
if (filter && isInteger(filter.assetTrendsChartType) && transactionStatisticsFilter.value.assetTrendsChartType !== filter.assetTrendsChartType) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartType = filter.assetTrendsChartType;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isInteger(filter.assetTrendsChartDateType) && transactionStatisticsFilter.value.assetTrendsChartDateType !== filter.assetTrendsChartDateType) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartDateType = filter.assetTrendsChartDateType;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isInteger(filter.assetTrendsChartStartTime) && transactionStatisticsFilter.value.assetTrendsChartStartTime !== filter.assetTrendsChartStartTime) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartStartTime = filter.assetTrendsChartStartTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isInteger(filter.assetTrendsChartEndTime) && transactionStatisticsFilter.value.assetTrendsChartEndTime !== filter.assetTrendsChartEndTime) {
|
||||
transactionStatisticsFilter.value.assetTrendsChartEndTime = filter.assetTrendsChartEndTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Other filter update
|
||||
if (filter && isObject(filter.filterAccountIds) && !isEquals(transactionStatisticsFilter.value.filterAccountIds, filter.filterAccountIds)) {
|
||||
transactionStatisticsFilter.value.filterAccountIds = filter.filterAccountIds;
|
||||
changed = true;
|
||||
@@ -1297,7 +1636,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
return changed;
|
||||
}
|
||||
|
||||
function getTransactionStatisticsPageParams(analysisType: StatisticsAnalysisType, trendDateAggregationType: number): string {
|
||||
function getTransactionStatisticsPageParams(analysisType: StatisticsAnalysisType, trendDateAggregationType: number, assetTrendsDateAggregationType: number): string {
|
||||
const querys: string[] = [];
|
||||
|
||||
querys.push('analysisType=' + analysisType);
|
||||
@@ -1320,9 +1659,21 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
querys.push('endTime=' + transactionStatisticsFilter.value.trendChartEndYearMonth);
|
||||
}
|
||||
|
||||
if (trendDateAggregationType !== ChartDateAggregationType.Month.type) {
|
||||
if (trendDateAggregationType !== ChartDateAggregationType.Default.type) {
|
||||
querys.push('trendDateAggregationType=' + trendDateAggregationType);
|
||||
}
|
||||
} else if (analysisType === StatisticsAnalysisType.AssetTrends) {
|
||||
querys.push('chartType=' + transactionStatisticsFilter.value.assetTrendsChartType);
|
||||
querys.push('chartDateType=' + transactionStatisticsFilter.value.assetTrendsChartDateType);
|
||||
|
||||
if (transactionStatisticsFilter.value.assetTrendsChartDateType === DateRange.Custom.type) {
|
||||
querys.push('startTime=' + transactionStatisticsFilter.value.assetTrendsChartStartTime);
|
||||
querys.push('endTime=' + transactionStatisticsFilter.value.assetTrendsChartEndTime);
|
||||
}
|
||||
|
||||
if (assetTrendsDateAggregationType !== ChartDateAggregationType.Default.type) {
|
||||
querys.push('assetTrendsDateAggregationType=' + assetTrendsDateAggregationType);
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.filterAccountIds) {
|
||||
@@ -1414,21 +1765,23 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
} else {
|
||||
querys.push('categoryIds=' + getFinalCategoryIdsByFilteredCategoryIds(transactionCategoriesStore.allTransactionCategoriesMap, transactionStatisticsFilter.value.filterCategoryIds));
|
||||
}
|
||||
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)) {
|
||||
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)
|
||||
) {
|
||||
querys.push('accountIds=' + itemId);
|
||||
|
||||
if (!isObjectEmpty(transactionStatisticsFilter.value.filterCategoryIds)) {
|
||||
if ((analysisType === StatisticsAnalysisType.CategoricalAnalysis || analysisType === StatisticsAnalysisType.TrendAnalysis) && !isObjectEmpty(transactionStatisticsFilter.value.filterCategoryIds)) {
|
||||
querys.push('categoryIds=' + getFinalCategoryIdsByFilteredCategoryIds(transactionCategoriesStore.allTransactionCategoriesMap, transactionStatisticsFilter.value.filterCategoryIds));
|
||||
}
|
||||
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type
|
||||
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type)) {
|
||||
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type ||
|
||||
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type)
|
||||
) {
|
||||
querys.push('categoryIds=' + itemId);
|
||||
|
||||
if (!isObjectEmpty(transactionStatisticsFilter.value.filterAccountIds)) {
|
||||
@@ -1444,16 +1797,18 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.tagIds) {
|
||||
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds);
|
||||
}
|
||||
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis || analysisType === StatisticsAnalysisType.TrendAnalysis) {
|
||||
if (transactionStatisticsFilter.value.tagIds) {
|
||||
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds);
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.tagFilterType) {
|
||||
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
|
||||
}
|
||||
if (transactionStatisticsFilter.value.tagFilterType) {
|
||||
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
|
||||
}
|
||||
|
||||
if (transactionStatisticsFilter.value.keyword) {
|
||||
querys.push('keyword=' + encodeURIComponent(transactionStatisticsFilter.value.keyword));
|
||||
if (transactionStatisticsFilter.value.keyword) {
|
||||
querys.push('keyword=' + encodeURIComponent(transactionStatisticsFilter.value.keyword));
|
||||
}
|
||||
}
|
||||
|
||||
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis
|
||||
@@ -1465,7 +1820,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
querys.push('minTime=' + transactionStatisticsFilter.value.categoricalChartStartTime);
|
||||
querys.push('maxTime=' + transactionStatisticsFilter.value.categoricalChartEndTime);
|
||||
}
|
||||
} else if (analysisType === StatisticsAnalysisType.TrendAnalysis && dateRange) {
|
||||
} else if ((analysisType === StatisticsAnalysisType.TrendAnalysis || analysisType === StatisticsAnalysisType.AssetTrends) && dateRange) {
|
||||
querys.push('dateType=' + dateRange.dateType);
|
||||
querys.push('minTime=' + dateRange.minTime);
|
||||
querys.push('maxTime=' + dateRange.maxTime);
|
||||
@@ -1560,6 +1915,45 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function loadAssetTrends({ force }: { force: boolean }): Promise<TransactionStatisticAssetTrendsResponseItem[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getTransactionStatisticsAssetTrends({
|
||||
startTime: transactionStatisticsFilter.value.assetTrendsChartStartTime,
|
||||
endTime: transactionStatisticsFilter.value.assetTrendsChartEndTime
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve transaction statistics' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionStatisticsStateInvalid.value) {
|
||||
updateTransactionStatisticsInvalidState(false);
|
||||
}
|
||||
|
||||
if (force && data.result && isEquals(transactionAssetTrendsData.value, data.result)) {
|
||||
reject({ message: 'Data is up to date', isUpToDate: true });
|
||||
return;
|
||||
}
|
||||
|
||||
transactionAssetTrendsData.value = data.result;
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to retrieve transaction statistics', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to retrieve transaction statistics' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// states
|
||||
transactionStatisticsFilter,
|
||||
@@ -1571,6 +1965,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
categoricalOverviewAnalysisData,
|
||||
categoricalAnalysisData,
|
||||
trendsAnalysisData,
|
||||
assetTrendsData,
|
||||
// functions
|
||||
updateTransactionStatisticsInvalidState,
|
||||
resetTransactionStatistics,
|
||||
@@ -1579,6 +1974,7 @@ export const useStatisticsStore = defineStore('statistics', () => {
|
||||
getTransactionStatisticsPageParams,
|
||||
getTransactionListPageParams,
|
||||
loadCategoricalAnalysis,
|
||||
loadTrendAnalysis
|
||||
loadTrendAnalysis,
|
||||
loadAssetTrends
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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({});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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