support filtering transaction amount
This commit is contained in:
@@ -89,6 +89,7 @@ func startWebServer(c *cli.Context) error {
|
||||
_ = v.RegisterValidation("validEmail", validators.ValidEmail)
|
||||
_ = v.RegisterValidation("validCurrency", validators.ValidCurrency)
|
||||
_ = v.RegisterValidation("validHexRGBColor", validators.ValidHexRGBColor)
|
||||
_ = v.RegisterValidation("validAmountFilter", validators.ValidAmountFilter)
|
||||
}
|
||||
|
||||
router.NoRoute(bindApi(api.Default.ApiNotFound))
|
||||
|
||||
@@ -59,7 +59,7 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (any, *errs.E
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
|
||||
totalCount, err := a.transactions.GetTransactionCount(c, uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.AmountFilter, transactionCountReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -118,7 +118,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (any, *errs.Er
|
||||
var totalCount int64
|
||||
|
||||
if transactionListReq.WithCount {
|
||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
||||
totalCount, err = a.transactions.GetTransactionCount(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
@@ -126,7 +126,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.Context) (any, *errs.Er
|
||||
}
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||
transactions, err := a.transactions.GetTransactionsByMaxTime(c, uid, transactionListReq.MaxTime, transactionListReq.MinTime, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.AmountFilter, transactionListReq.Keyword, transactionListReq.Page, transactionListReq.Count, true, true)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionListHandler] failed to get transactions earlier than \"%d\" for user \"uid:%d\", because %s", transactionListReq.MaxTime, uid, err.Error())
|
||||
@@ -206,7 +206,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.Context) (any, *er
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.Keyword)
|
||||
transactions, err := a.transactions.GetTransactionsInMonthByPage(c, uid, transactionListReq.Year, transactionListReq.Month, transactionListReq.Type, allCategoryIds, allAccountIds, transactionListReq.AmountFilter, transactionListReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthListHandler] failed to get transactions in month \"%d-%d\" for user \"uid:%d\", because %s", transactionListReq.Year, transactionListReq.Month, uid, err.Error())
|
||||
|
||||
@@ -82,3 +82,8 @@ func GetParameterInvalidCurrencyMessage(field string) string {
|
||||
func GetParameterInvalidHexRGBColorMessage(field string) string {
|
||||
return fmt.Sprintf("parameter \"%s\" is invalid color", field)
|
||||
}
|
||||
|
||||
// GetParameterInvalidAmountFilterMessage returns specific error message for invalid amount filter parameter error
|
||||
func GetParameterInvalidAmountFilterMessage(field string) string {
|
||||
return fmt.Sprintf("parameter \"%s\" is invalid amount filter", field)
|
||||
}
|
||||
|
||||
@@ -94,12 +94,13 @@ type TransactionModifyRequest struct {
|
||||
|
||||
// TransactionCountRequest represents transaction count request
|
||||
type TransactionCountRequest struct {
|
||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
||||
Keyword string `form:"keyword"`
|
||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
||||
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||
Keyword string `form:"keyword"`
|
||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||
}
|
||||
|
||||
// TransactionListByMaxTimeRequest represents all parameters of transaction listing by max time request
|
||||
@@ -107,6 +108,7 @@ type TransactionListByMaxTimeRequest struct {
|
||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
||||
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||
Keyword string `form:"keyword"`
|
||||
MaxTime int64 `form:"max_time" binding:"min=0"`
|
||||
MinTime int64 `form:"min_time" binding:"min=0"`
|
||||
@@ -125,6 +127,7 @@ type TransactionListInMonthByPageRequest struct {
|
||||
Type TransactionDbType `form:"type" binding:"min=0,max=4"`
|
||||
CategoryId int64 `form:"category_id" binding:"min=0"`
|
||||
AccountId int64 `form:"account_id" binding:"min=0"`
|
||||
AmountFilter string `form:"amount_filter" binding:"validAmountFilter"`
|
||||
Keyword string `form:"keyword"`
|
||||
TrimAccount bool `form:"trim_account"`
|
||||
TrimCategory bool `form:"trim_category"`
|
||||
|
||||
@@ -73,11 +73,11 @@ func (s *TransactionService) GetAllTransactions(c *core.Context, uid int64, page
|
||||
|
||||
// GetAllTransactionsByMaxTime returns all transactions before given time
|
||||
func (s *TransactionService) GetAllTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, count int32, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, "", 1, count, false, noDuplicated)
|
||||
return s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, nil, "", "", 1, count, false, noDuplicated)
|
||||
}
|
||||
|
||||
// GetTransactionsByMaxTime returns transactions before given time
|
||||
func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, amountFilter string, keyword string, page int32, count int32, needOneMoreItem bool, noDuplicated bool) ([]*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -101,14 +101,14 @@ func (s *TransactionService) GetTransactionsByMaxTime(c *core.Context, uid int64
|
||||
actualCount++
|
||||
}
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, noDuplicated)
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, amountFilter, keyword, noDuplicated)
|
||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Limit(int(actualCount), int(count*(page-1))).OrderBy("transaction_time desc").Find(&transactions)
|
||||
|
||||
return transactions, err
|
||||
}
|
||||
|
||||
// GetTransactionsInMonthByPage returns all transactions in given year and month
|
||||
func (s *TransactionService) GetTransactionsInMonthByPage(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string) ([]*models.Transaction, error) {
|
||||
func (s *TransactionService) GetTransactionsInMonthByPage(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, amountFilter string, keyword string) ([]*models.Transaction, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (s *TransactionService) GetTransactionsInMonthByPage(c *core.Context, uid i
|
||||
|
||||
var transactions []*models.Transaction
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, amountFilter, keyword, true)
|
||||
err = s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).OrderBy("transaction_time desc").Find(&transactions)
|
||||
|
||||
transactionsInMonth := make([]*models.Transaction, 0, len(transactions))
|
||||
@@ -163,11 +163,11 @@ func (s *TransactionService) GetTransactionByTransactionId(c *core.Context, uid
|
||||
|
||||
// GetAllTransactionCount returns total count of transactions
|
||||
func (s *TransactionService) GetAllTransactionCount(c *core.Context, uid int64) (int64, error) {
|
||||
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, "")
|
||||
return s.GetTransactionCount(c, uid, 0, 0, 0, nil, nil, "", "")
|
||||
}
|
||||
|
||||
// GetMonthTransactionCount returns total count of transactions in given year and month
|
||||
func (s *TransactionService) GetMonthTransactionCount(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, utcOffset int16) (int64, error) {
|
||||
func (s *TransactionService) GetMonthTransactionCount(c *core.Context, uid int64, year int32, month int32, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, amountFilter string, keyword string, utcOffset int16) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -183,16 +183,16 @@ func (s *TransactionService) GetMonthTransactionCount(c *core.Context, uid int64
|
||||
minTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startTime.Unix())
|
||||
maxTransactionTime := utils.GetMinTransactionTimeFromUnixTime(endTime.Unix()) - 1
|
||||
|
||||
return s.GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword)
|
||||
return s.GetTransactionCount(c, uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, amountFilter, keyword)
|
||||
}
|
||||
|
||||
// GetTransactionCount returns count of transactions
|
||||
func (s *TransactionService) GetTransactionCount(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string) (int64, error) {
|
||||
func (s *TransactionService) GetTransactionCount(c *core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, amountFilter string, keyword string) (int64, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, keyword, true)
|
||||
condition, conditionParams := s.getTransactionQueryCondition(uid, maxTransactionTime, minTransactionTime, transactionType, categoryIds, accountIds, amountFilter, keyword, true)
|
||||
return s.UserDataDB(uid).NewSession(c).Where(condition, conditionParams...).Count(&models.Transaction{})
|
||||
}
|
||||
|
||||
@@ -1331,7 +1331,7 @@ func (s *TransactionService) GetTransactionMapByList(transactions []*models.Tran
|
||||
return transactionMap
|
||||
}
|
||||
|
||||
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, keyword string, noDuplicated bool) (string, []any) {
|
||||
func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransactionTime int64, minTransactionTime int64, transactionType models.TransactionDbType, categoryIds []int64, accountIds []int64, amountFilter string, keyword string, noDuplicated bool) (string, []any) {
|
||||
condition := "uid=? AND deleted=?"
|
||||
conditionParams := make([]any, 0, 16)
|
||||
conditionParams = append(conditionParams, uid)
|
||||
@@ -1399,6 +1399,58 @@ func (s *TransactionService) getTransactionQueryCondition(uid int64, maxTransact
|
||||
condition = condition + " AND account_id IN (" + conditions.String() + ")"
|
||||
}
|
||||
|
||||
if amountFilter != "" {
|
||||
amountFilterItems := strings.Split(amountFilter, ":")
|
||||
|
||||
if len(amountFilterItems) == 2 && amountFilterItems[0] == "gt" {
|
||||
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND amount > ?"
|
||||
conditionParams = append(conditionParams, value)
|
||||
}
|
||||
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "lt" {
|
||||
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND amount < ?"
|
||||
conditionParams = append(conditionParams, value)
|
||||
}
|
||||
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "eq" {
|
||||
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND amount = ?"
|
||||
conditionParams = append(conditionParams, value)
|
||||
}
|
||||
} else if len(amountFilterItems) == 2 && amountFilterItems[0] == "ne" {
|
||||
value, err := utils.StringToInt64(amountFilterItems[1])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND amount <> ?"
|
||||
conditionParams = append(conditionParams, value)
|
||||
}
|
||||
} else if len(amountFilterItems) == 3 && amountFilterItems[0] == "bt" {
|
||||
value1, err := utils.StringToInt64(amountFilterItems[1])
|
||||
value2, err := utils.StringToInt64(amountFilterItems[2])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND amount >= ? AND amount <= ?"
|
||||
conditionParams = append(conditionParams, value1)
|
||||
conditionParams = append(conditionParams, value2)
|
||||
}
|
||||
} else if len(amountFilterItems) == 3 && amountFilterItems[0] == "nb" {
|
||||
value1, err := utils.StringToInt64(amountFilterItems[1])
|
||||
value2, err := utils.StringToInt64(amountFilterItems[2])
|
||||
|
||||
if err == nil {
|
||||
condition = condition + " AND (amount < ? OR amount > ?)"
|
||||
conditionParams = append(conditionParams, value1)
|
||||
conditionParams = append(conditionParams, value2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if keyword != "" {
|
||||
condition = condition + " AND comment LIKE ?"
|
||||
conditionParams = append(conditionParams, "%%"+keyword+"%%")
|
||||
|
||||
@@ -110,6 +110,8 @@ func getValidationErrorText(err validator.FieldError) string {
|
||||
return errs.GetParameterInvalidCurrencyMessage(fieldName)
|
||||
case "validHexRGBColor":
|
||||
return errs.GetParameterInvalidHexRGBColorMessage(fieldName)
|
||||
case "validAmountFilter":
|
||||
return errs.GetParameterInvalidAmountFilterMessage(fieldName)
|
||||
}
|
||||
|
||||
return errs.GetParameterInvalidMessage(fieldName)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// ValidAmountFilter returns whether the given amount filter is valid
|
||||
func ValidAmountFilter(fl validator.FieldLevel) bool {
|
||||
if value, ok := fl.Field().Interface().(string); ok {
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
amountFilterItems := strings.Split(value, ":")
|
||||
|
||||
if len(amountFilterItems) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
amount1, err := utils.StringToInt64(amountFilterItems[1])
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if amountFilterItems[0] == "gt" || amountFilterItems[0] == "lt" || amountFilterItems[0] == "eq" || amountFilterItems[0] == "ne" {
|
||||
if len(amountFilterItems) != 2 {
|
||||
return false
|
||||
}
|
||||
} else if amountFilterItems[0] == "bt" || amountFilterItems[0] == "nb" {
|
||||
if len(amountFilterItems) != 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
amount2, err := utils.StringToInt64(amountFilterItems[2])
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if amount2 < amount1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -86,6 +86,57 @@ const allDigitGroupingTypeMap = {
|
||||
[allDigitGroupingType.ThousandsSeparator.type]: allDigitGroupingType.ThousandsSeparator
|
||||
};
|
||||
|
||||
const allAmountFilterType = {
|
||||
GreaterThan: {
|
||||
type: 'gt',
|
||||
name: 'Greater than',
|
||||
paramCount: 1
|
||||
},
|
||||
LessThan:{
|
||||
type: 'lt',
|
||||
name: 'Less than',
|
||||
paramCount: 1
|
||||
},
|
||||
EqualTo:{
|
||||
type: 'eq',
|
||||
name: 'Equal to',
|
||||
paramCount: 1
|
||||
},
|
||||
NotEqualTo:{
|
||||
type: 'ne',
|
||||
name: 'Not equal to',
|
||||
paramCount: 1
|
||||
},
|
||||
Between:{
|
||||
type: 'bt',
|
||||
name: 'Between',
|
||||
paramCount: 2
|
||||
},
|
||||
NotBetween:{
|
||||
type: 'nb',
|
||||
name: 'Not between',
|
||||
paramCount: 2
|
||||
}
|
||||
};
|
||||
|
||||
const allAmountFilterTypeArray = [
|
||||
allAmountFilterType.GreaterThan,
|
||||
allAmountFilterType.LessThan,
|
||||
allAmountFilterType.EqualTo,
|
||||
allAmountFilterType.NotEqualTo,
|
||||
allAmountFilterType.Between,
|
||||
allAmountFilterType.NotBetween,
|
||||
];
|
||||
|
||||
const allAmountFilterTypeMap = {
|
||||
[allAmountFilterType.GreaterThan.type]: allAmountFilterType.GreaterThan,
|
||||
[allAmountFilterType.LessThan.type]: allAmountFilterType.LessThan,
|
||||
[allAmountFilterType.EqualTo.type]: allAmountFilterType.EqualTo,
|
||||
[allAmountFilterType.NotEqualTo.type]: allAmountFilterType.NotEqualTo,
|
||||
[allAmountFilterType.Between.type]: allAmountFilterType.Between,
|
||||
[allAmountFilterType.NotBetween.type]: allAmountFilterType.NotBetween
|
||||
};
|
||||
|
||||
const defaultDecimalSeparator = allDecimalSeparator.Dot;
|
||||
const defaultDigitGroupingSymbol = allDigitGroupingSymbol.Comma;
|
||||
const defaultDigitGroupingType = allDigitGroupingType.ThousandsSeparator;
|
||||
@@ -101,6 +152,9 @@ export default {
|
||||
allDigitGroupingType: allDigitGroupingType,
|
||||
allDigitGroupingTypeArray: allDigitGroupingTypeArray,
|
||||
allDigitGroupingTypeMap: allDigitGroupingTypeMap,
|
||||
allAmountFilterType: allAmountFilterType,
|
||||
allAmountFilterTypeArray: allAmountFilterTypeArray,
|
||||
allAmountFilterTypeMap: allAmountFilterTypeMap,
|
||||
defaultDecimalSeparator: defaultDecimalSeparator,
|
||||
defaultDigitGroupingSymbol: defaultDigitGroupingSymbol,
|
||||
defaultDigitGroupingType: defaultDigitGroupingType,
|
||||
|
||||
@@ -175,6 +175,14 @@ const parameterizedErrors = [
|
||||
field: 'parameter',
|
||||
localized: true
|
||||
}]
|
||||
},
|
||||
{
|
||||
localeKey: 'parameter invalid amount filter',
|
||||
regex: /^parameter "(\w+)" is invalid amount filter$/,
|
||||
parameters: [{
|
||||
field: 'parameter',
|
||||
localized: true
|
||||
}]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
+6
-4
@@ -279,13 +279,15 @@ export default {
|
||||
id
|
||||
});
|
||||
},
|
||||
getTransactions: ({ maxTime, minTime, count, page, withCount, type, categoryId, accountId, keyword }) => {
|
||||
getTransactions: ({ maxTime, minTime, count, page, withCount, type, categoryId, accountId, amountFilter, keyword }) => {
|
||||
amountFilter = encodeURIComponent(amountFilter);
|
||||
keyword = encodeURIComponent(keyword);
|
||||
return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&count=${count}&page=${page}&with_count=${withCount}&trim_account=true&trim_category=true&trim_tag=true`);
|
||||
return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_id=${categoryId}&account_id=${accountId}&amount_filter=${amountFilter}&keyword=${keyword}&count=${count}&page=${page}&with_count=${withCount}&trim_account=true&trim_category=true&trim_tag=true`);
|
||||
},
|
||||
getAllTransactionsByMonth: ({ year, month, type, categoryId, accountId, keyword }) => {
|
||||
getAllTransactionsByMonth: ({ year, month, type, categoryId, accountId, amountFilter, keyword }) => {
|
||||
amountFilter = encodeURIComponent(amountFilter);
|
||||
keyword = encodeURIComponent(keyword);
|
||||
return axios.get(`v1/transactions/list/by_month.json?year=${year}&month=${month}&type=${type}&category_id=${categoryId}&account_id=${accountId}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
|
||||
return axios.get(`v1/transactions/list/by_month.json?year=${year}&month=${month}&type=${type}&category_id=${categoryId}&account_id=${accountId}&amount_filter=${amountFilter}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
|
||||
},
|
||||
getTransactionStatistics: ({ startTime, endTime, useTransactionTimezone }) => {
|
||||
const queryParams = [];
|
||||
|
||||
@@ -707,6 +707,7 @@ export default {
|
||||
'time': 'Time',
|
||||
'startTime': 'Start Time',
|
||||
'endTime': 'End Time',
|
||||
'amountFilter': 'Amount Filter',
|
||||
'sourceAccountId': 'Source Account ID',
|
||||
'destinationAccountId': 'Destination Account ID',
|
||||
'sourceAmount': 'Source Amount',
|
||||
@@ -731,6 +732,7 @@ export default {
|
||||
'parameter invalid email format': '{parameter} is invalid format',
|
||||
'parameter invalid currency': '{parameter} is invalid format',
|
||||
'parameter invalid color': '{parameter} is invalid format',
|
||||
'parameter invalid amount filter': '{parameter} is invalid format',
|
||||
},
|
||||
'OK': 'OK',
|
||||
'Cancel': 'Cancel',
|
||||
@@ -799,6 +801,12 @@ export default {
|
||||
'Select Time': 'Select Time',
|
||||
'Now': 'Now',
|
||||
'Custom': 'Custom',
|
||||
'Greater than': 'Greater than',
|
||||
'Less than': 'Less than',
|
||||
'Equal to': 'Equal to',
|
||||
'Not equal to': 'Not equal to',
|
||||
'Between': 'Between',
|
||||
'Not between': 'Not between',
|
||||
'Pie Chart': 'Pie Chart',
|
||||
'Bar Chart': 'Bar Chart',
|
||||
'Area Chart': 'Area Chart',
|
||||
@@ -921,7 +929,9 @@ export default {
|
||||
'This Month': 'This Month',
|
||||
'This Year': 'This Year',
|
||||
'Monthly income': 'Monthly income',
|
||||
'Filter Amount': 'Filter Amount',
|
||||
'Unable to retrieve transaction overview': 'Unable to retrieve transaction overview',
|
||||
'Incorrect amount range': 'Incorrect amount range',
|
||||
'Data is up to date': 'Data is up to date',
|
||||
'Data has been updated': 'Data has been updated',
|
||||
'Net assets': 'Net assets',
|
||||
@@ -1066,6 +1076,8 @@ export default {
|
||||
'Default Sort Order': 'Default Sort Order',
|
||||
'Timezone Used for Date Range': 'Timezone Used for Date Range',
|
||||
'Amount': 'Amount',
|
||||
'Min Amount': 'Min Amount',
|
||||
'Max Amount': 'Max Amount',
|
||||
'Display Order': 'Display Order',
|
||||
'Name': 'Name',
|
||||
'Sort by Amount': 'Sort by Amount',
|
||||
|
||||
@@ -707,6 +707,7 @@ export default {
|
||||
'time': '时间',
|
||||
'startTime': '开始时间',
|
||||
'endTime': '结束时间',
|
||||
'amountFilter': '金额过滤',
|
||||
'sourceAccountId': '来源账户ID',
|
||||
'destinationAccountId': '目标账户ID',
|
||||
'sourceAmount': '源金额',
|
||||
@@ -731,6 +732,7 @@ export default {
|
||||
'parameter invalid email format': '{parameter}格式错误',
|
||||
'parameter invalid currency': '{parameter}格式错误',
|
||||
'parameter invalid color': '{parameter}格式错误',
|
||||
'parameter invalid amount filter': '{parameter}格式错误',
|
||||
},
|
||||
'OK': '确定',
|
||||
'Cancel': '取消',
|
||||
@@ -799,6 +801,12 @@ export default {
|
||||
'Select Time': '选择时间',
|
||||
'Now': '现在',
|
||||
'Custom': '自定义',
|
||||
'Greater than': '大于',
|
||||
'Less than': '小于',
|
||||
'Equal to': '等于',
|
||||
'Not equal to': '不等于',
|
||||
'Between': '介于',
|
||||
'Not between': '不介于',
|
||||
'Pie Chart': '饼图',
|
||||
'Bar Chart': '条形图',
|
||||
'Area Chart': '面积图',
|
||||
@@ -921,7 +929,9 @@ export default {
|
||||
'This Month': '本月',
|
||||
'This Year': '今年',
|
||||
'Monthly income': '当月收入',
|
||||
'Filter Amount': '过滤金额',
|
||||
'Unable to retrieve transaction overview': '无法获取交易概要',
|
||||
'Incorrect amount range': '金额范围错误',
|
||||
'Data is up to date': '数据已是最新',
|
||||
'Data has been updated': '数据已更新',
|
||||
'Net assets': '净资产',
|
||||
@@ -1066,6 +1076,8 @@ export default {
|
||||
'Default Sort Order': '默认排序方式',
|
||||
'Timezone Used for Date Range': '时间范围使用的时区',
|
||||
'Amount': '金额',
|
||||
'Min Amount': '最小金额',
|
||||
'Max Amount': '最大金额',
|
||||
'Display Order': '显示顺序',
|
||||
'Name': '名称',
|
||||
'Sort by Amount': '按金额排序',
|
||||
|
||||
@@ -100,6 +100,7 @@ const router = createRouter({
|
||||
initType: route.query.type,
|
||||
initCategoryId: route.query.categoryId,
|
||||
initAccountId: route.query.accountId,
|
||||
initAmountFilter: route.query.amountFilter,
|
||||
initKeyword: route.query.keyword
|
||||
})
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import UnlockPage from '@/views/mobile/UnlockPage.vue';
|
||||
|
||||
import TransactionListPage from '@/views/mobile/transactions/ListPage.vue';
|
||||
import TransactionEditPage from '@/views/mobile/transactions/EditPage.vue';
|
||||
import TransactionAmountFilterPage from '@/views/mobile/transactions/AmountFilterPage.vue';
|
||||
|
||||
import AccountListPage from '@/views/mobile/accounts/ListPage.vue';
|
||||
import AccountEditPage from '@/views/mobile/accounts/EditPage.vue';
|
||||
@@ -148,6 +149,11 @@ const routes = [
|
||||
async: asyncResolve(TransactionListPage),
|
||||
beforeEnter: [checkLogin]
|
||||
},
|
||||
{
|
||||
path: '/transaction/filter/amount',
|
||||
async: asyncResolve(TransactionAmountFilterPage),
|
||||
beforeEnter: [checkLogin]
|
||||
},
|
||||
{
|
||||
path: '/transaction/add',
|
||||
async: asyncResolve(TransactionEditPage),
|
||||
|
||||
@@ -270,6 +270,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
type: 0,
|
||||
categoryId: '0',
|
||||
accountId: '0',
|
||||
amountFilter: '',
|
||||
keyword: ''
|
||||
},
|
||||
transactions: [],
|
||||
@@ -365,6 +366,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
this.transactionsFilter.type = 0;
|
||||
this.transactionsFilter.categoryId = '0';
|
||||
this.transactionsFilter.accountId = '0';
|
||||
this.transactionsFilter.amountFilter = '';
|
||||
this.transactionsFilter.keyword = '';
|
||||
this.transactions = [];
|
||||
this.transactionsNextTimeId = 0;
|
||||
@@ -412,6 +414,12 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
this.transactionsFilter.accountId = '0';
|
||||
}
|
||||
|
||||
if (filter && isString(filter.amountFilter)) {
|
||||
this.transactionsFilter.amountFilter = filter.amountFilter;
|
||||
} else {
|
||||
this.transactionsFilter.amountFilter = '';
|
||||
}
|
||||
|
||||
if (filter && isString(filter.keyword)) {
|
||||
this.transactionsFilter.keyword = filter.keyword;
|
||||
} else {
|
||||
@@ -443,6 +451,10 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
this.transactionsFilter.accountId = filter.accountId;
|
||||
}
|
||||
|
||||
if (filter && isString(filter.amountFilter)) {
|
||||
this.transactionsFilter.amountFilter = filter.amountFilter;
|
||||
}
|
||||
|
||||
if (filter && isString(filter.keyword)) {
|
||||
this.transactionsFilter.keyword = filter.keyword;
|
||||
}
|
||||
@@ -469,6 +481,10 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
querys.push('minTime=' + this.transactionsFilter.minTime);
|
||||
}
|
||||
|
||||
if (this.transactionsFilter.amountFilter) {
|
||||
querys.push('amountFilter=' + encodeURIComponent(this.transactionsFilter.amountFilter));
|
||||
}
|
||||
|
||||
if (this.transactionsFilter.keyword) {
|
||||
querys.push('keyword=' + encodeURIComponent(this.transactionsFilter.keyword));
|
||||
}
|
||||
@@ -497,6 +513,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
type: self.transactionsFilter.type,
|
||||
categoryId: self.transactionsFilter.categoryId,
|
||||
accountId: self.transactionsFilter.accountId,
|
||||
amountFilter: self.transactionsFilter.amountFilter,
|
||||
keyword: self.transactionsFilter.keyword
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
@@ -571,6 +588,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
||||
type: self.transactionsFilter.type,
|
||||
categoryId: self.transactionsFilter.categoryId,
|
||||
accountId: self.transactionsFilter.accountId,
|
||||
amountFilter: self.transactionsFilter.amountFilter,
|
||||
keyword: self.transactionsFilter.keyword
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
@@ -192,7 +192,56 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</th>
|
||||
<th class="transaction-table-column-amount">{{ $t('Amount') }}</th>
|
||||
<th class="transaction-table-column-amount">
|
||||
<v-menu ref="amountFilterMenu" class="transaction-amount-menu"
|
||||
eager location="bottom" max-height="500"
|
||||
:close-on-content-click="false"
|
||||
v-model="amountMenuState"
|
||||
@update:model-value="scrollAmountMenuToSelectedItem">
|
||||
<template #activator="{ props }">
|
||||
<div class="d-flex align-center cursor-pointer"
|
||||
:class="{ 'readonly': loading, 'text-primary': query.amountFilter }" v-bind="props">
|
||||
<span>{{ $t('Amount') }}</span>
|
||||
<v-icon :icon="icons.dropdownMenu" />
|
||||
</div>
|
||||
</template>
|
||||
<v-list :selected="[query.amountFilter.split(':')[0]]">
|
||||
<v-list-item key="0" value="0" class="text-sm" density="compact"
|
||||
:class="{ 'list-item-selected': !query.amountFilter }"
|
||||
:append-icon="(!query.amountFilter && !currentAmountFilterType ? icons.check : null)">
|
||||
<v-list-item-title class="cursor-pointer"
|
||||
@click="changeAmountFilter('')">
|
||||
<div class="d-flex align-center">
|
||||
<span class="text-sm ml-3">{{ $t('All') }}</span>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<template :key="filterType.type"
|
||||
v-for="filterType in allAmountFilterTypes">
|
||||
<v-list-item class="text-sm" density="compact"
|
||||
:value="filterType.type"
|
||||
:class="{ 'list-item-selected': query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`) }"
|
||||
:append-icon="(query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`) && currentAmountFilterType !== filterType.type ? icons.check : null)">
|
||||
<v-list-item-title class="cursor-pointer"
|
||||
@click="currentAmountFilterType = filterType.type">
|
||||
<div class="d-flex align-center">
|
||||
<span class="text-sm ml-3">{{ $t(filterType.name) }}</span>
|
||||
<span class="text-sm ml-4" v-if="query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`) && currentAmountFilterType !== filterType.type">{{ queryAmount }}</span>
|
||||
<amount-input class="transaction-amount-filter-value ml-4" density="compact" v-model="currentAmountFilterValue1"
|
||||
v-if="currentAmountFilterType === filterType.type"/>
|
||||
<span class="ml-2 mr-2" v-if="currentAmountFilterType === filterType.type && filterType.paramCount === 2">~</span>
|
||||
<amount-input class="transaction-amount-filter-value" density="compact" v-model="currentAmountFilterValue2"
|
||||
v-if="currentAmountFilterType === filterType.type && filterType.paramCount === 2"/>
|
||||
<v-btn class="ml-2" density="compact" color="primary" variant="tonal"
|
||||
@click="changeAmountFilter(filterType.type)"
|
||||
v-if="currentAmountFilterType === filterType.type">{{ $t('Apply') }}</v-btn>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</th>
|
||||
<th class="transaction-table-column-account">
|
||||
<v-menu ref="accountFilterMenu" class="transaction-account-menu"
|
||||
eager location="bottom" max-height="500"
|
||||
@@ -353,11 +402,13 @@ import { useAccountsStore } from '@/stores/account.js';
|
||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
|
||||
import numeralConstants from '@/consts/numeral.js';
|
||||
import datetimeConstants from '@/consts/datetime.js';
|
||||
import currencyConstants from '@/consts/currency.js';
|
||||
import accountConstants from '@/consts/account.js';
|
||||
import transactionConstants from '@/consts/transaction.js';
|
||||
import { getNameByKeyValue } from '@/lib/common.js';
|
||||
import { isString, getNameByKeyValue } from '@/lib/common.js';
|
||||
import logger from '@/lib/logger.js';
|
||||
import {
|
||||
getCurrentUnixTime,
|
||||
parseDateFromUnixTime,
|
||||
@@ -406,6 +457,7 @@ export default {
|
||||
'initType',
|
||||
'initCategoryId',
|
||||
'initAccountId',
|
||||
'initAmountFilter',
|
||||
'initKeyword'
|
||||
],
|
||||
data() {
|
||||
@@ -421,8 +473,12 @@ export default {
|
||||
searchKeyword: '',
|
||||
customMinDatetime: 0,
|
||||
customMaxDatetime: 0,
|
||||
currentAmountFilterType: '',
|
||||
currentAmountFilterValue1: '0',
|
||||
currentAmountFilterValue2: '0',
|
||||
currentPageTransactions: [],
|
||||
categoryMenuState: false,
|
||||
amountMenuState: false,
|
||||
alwaysShowNav: mdAndUp.value,
|
||||
showNav: mdAndUp.value,
|
||||
showCustomDateRangeDialog: false,
|
||||
@@ -497,6 +553,25 @@ export default {
|
||||
queryAccountName() {
|
||||
return getNameByKeyValue(this.allAccounts, this.query.accountId, null, 'name', this.$t('Account'));
|
||||
},
|
||||
queryAmount() {
|
||||
if (!this.query.amountFilter) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const amountFilterItems = this.query.amountFilter.split(':');
|
||||
|
||||
if (amountFilterItems.length < 2) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const displayAmount = [];
|
||||
|
||||
for (let i = 1; i < amountFilterItems.length; i++) {
|
||||
displayAmount.push(this.getDisplayCurrency(amountFilterItems[i], false));
|
||||
}
|
||||
|
||||
return displayAmount.join(' ~ ');
|
||||
},
|
||||
queryMonthlyData() {
|
||||
return isDateRangeMatchOneMonth(this.query.minTime, this.query.maxTime);
|
||||
},
|
||||
@@ -597,6 +672,9 @@ export default {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
allAmountFilterTypes() {
|
||||
return numeralConstants.allAmountFilterTypeArray;
|
||||
},
|
||||
allTransactionTypes() {
|
||||
return transactionConstants.allTransactionTypes;
|
||||
},
|
||||
@@ -638,6 +716,7 @@ export default {
|
||||
type: this.initType,
|
||||
categoryId: this.initCategoryId,
|
||||
accountId: this.initAccountId,
|
||||
amountFilter: this.initAmountFilter,
|
||||
keyword: this.initKeyword
|
||||
});
|
||||
},
|
||||
@@ -666,6 +745,7 @@ export default {
|
||||
type: to.query.type,
|
||||
categoryId: to.query.categoryId,
|
||||
accountId: to.query.accountId,
|
||||
amountFilter: to.query.amountFilter,
|
||||
keyword: to.query.keyword
|
||||
});
|
||||
}
|
||||
@@ -691,10 +771,12 @@ export default {
|
||||
type: parseInt(query.type) > 0 ? parseInt(query.type) : undefined,
|
||||
categoryId: query.categoryId,
|
||||
accountId: query.accountId,
|
||||
amountFilter: query.amountFilter || '',
|
||||
keyword: query.keyword || ''
|
||||
});
|
||||
|
||||
this.searchKeyword = query.keyword || '';
|
||||
this.currentAmountFilterType = '';
|
||||
|
||||
this.currentPage = 1;
|
||||
this.reload(false);
|
||||
@@ -855,6 +937,50 @@ export default {
|
||||
this.transactionsStore.clearTransactions();
|
||||
this.$router.push(this.getFilterLinkUrl());
|
||||
},
|
||||
changeAmountFilter(filterType) {
|
||||
this.currentAmountFilterType = '';
|
||||
this.amountMenuState = false;
|
||||
|
||||
if (this.query.amountFilter === filterType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let amountFilter = filterType;
|
||||
|
||||
if (filterType) {
|
||||
const amountCount = this.getAmountFilterParameterCount(filterType);
|
||||
|
||||
if (!amountCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (amountCount === 1) {
|
||||
amountFilter += ':' + this.currentAmountFilterValue1;
|
||||
} else if (amountCount === 2) {
|
||||
if (this.currentAmountFilterValue2 < this.currentAmountFilterValue1) {
|
||||
this.$refs.snackbar.showMessage('Incorrect amount range');
|
||||
return;
|
||||
}
|
||||
|
||||
amountFilter += ':' + this.currentAmountFilterValue1 + ':' + this.currentAmountFilterValue2;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.query.amountFilter === amountFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transactionsStore.updateTransactionListFilter({
|
||||
amountFilter: amountFilter
|
||||
});
|
||||
|
||||
this.loading = true;
|
||||
this.currentPageTransactions = [];
|
||||
this.transactionsStore.clearTransactions();
|
||||
this.$router.push(this.getFilterLinkUrl());
|
||||
},
|
||||
changeAccountFilter(accountId) {
|
||||
if (this.query.accountId === accountId) {
|
||||
return;
|
||||
@@ -929,6 +1055,34 @@ export default {
|
||||
this.scrollMenuToSelectedItem(this.$refs.categoryFilterMenu);
|
||||
}
|
||||
},
|
||||
scrollAmountMenuToSelectedItem(opened) {
|
||||
if (opened) {
|
||||
this.currentAmountFilterType = '';
|
||||
|
||||
let amount1 = 0, amount2 = 0;
|
||||
|
||||
if (isString(this.query.amountFilter)) {
|
||||
try {
|
||||
const filterItems = this.query.amountFilter.split(':');
|
||||
const amountCount = this.getAmountFilterParameterCount(filterItems[0]);
|
||||
|
||||
if (filterItems.length === 2 && amountCount === 1) {
|
||||
amount1 = parseInt(filterItems[1]);
|
||||
} else if (filterItems.length === 3 && amountCount === 2) {
|
||||
amount1 = parseInt(filterItems[1]);
|
||||
amount2 = parseInt(filterItems[2]);
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.warn('cannot parse amount from filter value, original value is ' + this.query.amountFilter);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentAmountFilterValue1 = amount1;
|
||||
this.currentAmountFilterValue2 = amount2;
|
||||
|
||||
this.scrollMenuToSelectedItem(this.$refs.amountFilterMenu);
|
||||
}
|
||||
},
|
||||
scrollAccountMenuToSelectedItem(opened) {
|
||||
if (opened) {
|
||||
this.scrollMenuToSelectedItem(this.$refs.accountFilterMenu);
|
||||
@@ -1005,6 +1159,10 @@ export default {
|
||||
|
||||
return [];
|
||||
},
|
||||
getAmountFilterParameterCount(filterType) {
|
||||
const amountFilterType = numeralConstants.allAmountFilterTypeMap[filterType];
|
||||
return amountFilterType ? amountFilterType.paramCount : 0;
|
||||
},
|
||||
getFilterLinkUrl() {
|
||||
return `/transaction/list?${this.transactionsStore.getTransactionListPageParams()}`;
|
||||
}
|
||||
@@ -1071,11 +1229,21 @@ export default {
|
||||
}
|
||||
|
||||
.transaction-category-menu .item-icon,
|
||||
.transaction-amount-menu .item-icon,
|
||||
.transaction-account-menu .item-icon,
|
||||
.transaction-table .item-icon {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.transaction-amount-filter-value {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.transaction-amount-filter-value input.v-field__input {
|
||||
min-height: 32px !important;
|
||||
padding: 0 8px 0 8px;
|
||||
}
|
||||
|
||||
.transaction-category-menu .has-children-item-selected span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<f7-page @page:afterin="onPageAfterIn">
|
||||
<f7-navbar>
|
||||
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
|
||||
<f7-nav-title :title="$t('Filter Amount')"></f7-nav-title>
|
||||
<f7-nav-right>
|
||||
<f7-link :text="$t('Apply')" @click="confirm"></f7-link>
|
||||
</f7-nav-right>
|
||||
</f7-navbar>
|
||||
|
||||
<f7-list form strong inset dividers class="margin-vertical">
|
||||
<f7-list-item
|
||||
class="ebk-small-amount"
|
||||
link="#" no-chevron
|
||||
:header="amount1Header"
|
||||
:title="getDisplayAmount(amount1)"
|
||||
@click="showAmount1Sheet = true"
|
||||
>
|
||||
<number-pad-sheet :min-value="allowedMinAmount"
|
||||
:max-value="allowedMaxAmount"
|
||||
v-model:show="showAmount1Sheet"
|
||||
v-model="amount1"
|
||||
></number-pad-sheet>
|
||||
</f7-list-item>
|
||||
|
||||
<f7-list-item
|
||||
class="ebk-small-amount"
|
||||
link="#" no-chevron
|
||||
:header="amount2Header"
|
||||
:title="getDisplayAmount(amount2)"
|
||||
@click="showAmount2Sheet = true"
|
||||
v-if="amountCount === 2"
|
||||
>
|
||||
<number-pad-sheet :min-value="allowedMinAmount"
|
||||
:max-value="allowedMaxAmount"
|
||||
v-model:show="showAmount2Sheet"
|
||||
v-model="amount2"
|
||||
></number-pad-sheet>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
|
||||
<f7-list form strong inset dividers class="margin-vertical">
|
||||
<f7-list-item :key="filterType.type" :title="$t(filterType.name)"
|
||||
v-for="filterType in allAmountFilterTypes"
|
||||
@click="type = filterType.type">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="type === filterType.type"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapStores } from 'pinia';
|
||||
import { useSettingsStore } from '@/stores/setting.js';
|
||||
import { useUserStore } from '@/stores/user.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
|
||||
import numeralConstants from '@/consts/numeral.js';
|
||||
import transactionConstants from '@/consts/transaction.js';
|
||||
import { isString } from '@/lib/common.js';
|
||||
import logger from '@/lib/logger.js';
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'f7route',
|
||||
'f7router'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
type: '',
|
||||
amount1: 0,
|
||||
amount2: 0,
|
||||
showAmount1Sheet: false,
|
||||
showAmount2Sheet: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSettingsStore, useUserStore, useTransactionsStore),
|
||||
allAmountFilterTypes() {
|
||||
return numeralConstants.allAmountFilterTypeArray;
|
||||
},
|
||||
allowedMinAmount() {
|
||||
return transactionConstants.minAmountNumber;
|
||||
},
|
||||
allowedMaxAmount() {
|
||||
return transactionConstants.maxAmountNumber;
|
||||
},
|
||||
amountCount() {
|
||||
return this.getAmountFilterParameterCount(this.type);
|
||||
},
|
||||
title() {
|
||||
const amountFilterType = numeralConstants.allAmountFilterTypeMap[this.type];
|
||||
return amountFilterType ? this.$t(amountFilterType.name) : this.$t('Amount');
|
||||
},
|
||||
amount1Header() {
|
||||
if (this.type === numeralConstants.allAmountFilterType.GreaterThan.type
|
||||
|| this.type === numeralConstants.allAmountFilterType.Between.type
|
||||
|| this.type === numeralConstants.allAmountFilterType.NotBetween.type) {
|
||||
return this.$t('Min Amount');
|
||||
} else if (this.type === numeralConstants.allAmountFilterType.LessThan.type) {
|
||||
return this.$t('Max Amount');
|
||||
} else {
|
||||
return this.$t('Amount');
|
||||
}
|
||||
},
|
||||
amount2Header() {
|
||||
if (this.type === numeralConstants.allAmountFilterType.Between.type) {
|
||||
return this.$t('Max Amount');
|
||||
} else if (this.type === numeralConstants.allAmountFilterType.NotBetween.type) {
|
||||
return this.$t('Max Amount');
|
||||
} else {
|
||||
return this.$t('Amount');
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const query = this.f7route.query;
|
||||
this.type = query.type;
|
||||
|
||||
let amount1 = 0, amount2 = 0;
|
||||
|
||||
if (isString(query.value)) {
|
||||
try {
|
||||
const filterItems = query.value.split(':');
|
||||
const amountCount = this.getAmountFilterParameterCount(filterItems[0]);
|
||||
|
||||
if (filterItems.length === 2 && amountCount === 1) {
|
||||
amount1 = parseInt(filterItems[1]);
|
||||
} else if (filterItems.length === 3 && amountCount === 2) {
|
||||
amount1 = parseInt(filterItems[1]);
|
||||
amount2 = parseInt(filterItems[2]);
|
||||
}
|
||||
} catch (ex) {
|
||||
logger.warn('cannot parse amount from filter value, original value is ' + query.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.amount1 = amount1;
|
||||
this.amount2 = amount2;
|
||||
},
|
||||
methods: {
|
||||
onPageAfterIn() {
|
||||
this.$routeBackOnError(this.f7router, 'loadingError');
|
||||
},
|
||||
confirm() {
|
||||
const router = this.f7router;
|
||||
let amountFilter = this.type;
|
||||
|
||||
if (this.amountCount === 1) {
|
||||
amountFilter += ':' + this.amount1;
|
||||
} else if (this.amountCount === 2) {
|
||||
if (this.amount2 < this.amount1) {
|
||||
this.$toast('Incorrect amount range');
|
||||
return;
|
||||
}
|
||||
|
||||
amountFilter += ':' + this.amount1 + ':' + this.amount2;
|
||||
} else {
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
|
||||
this.transactionsStore.updateTransactionListFilter({
|
||||
amountFilter: amountFilter
|
||||
});
|
||||
this.transactionsStore.updateTransactionListInvalidState(true);
|
||||
router.back();
|
||||
},
|
||||
getDisplayAmount(value) {
|
||||
return this.$locale.formatAmountWithCurrency(this.settingsStore, this.userStore, value, false);
|
||||
},
|
||||
getAmountFilterParameterCount(filterType) {
|
||||
const amountFilterType = numeralConstants.allAmountFilterTypeMap[filterType];
|
||||
return amountFilterType ? amountFilterType.paramCount : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -42,7 +42,7 @@
|
||||
<span :class="{ 'tabbar-item-changed': query.accountId > 0 }">{{ queryAccountName }}</span>
|
||||
</f7-link>
|
||||
<f7-link popover-open=".more-popover-menu">
|
||||
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 }"></f7-icon>
|
||||
<f7-icon f7="ellipsis_vertical" :class="{ 'tabbar-item-changed': query.type > 0 || query.amountFilter }"></f7-icon>
|
||||
</f7-link>
|
||||
</f7-toolbar>
|
||||
|
||||
@@ -403,6 +403,23 @@
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.type === 4"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
|
||||
<f7-list-item group-title :title="$t('Amount')" />
|
||||
<f7-list-item :class="{ 'list-item-selected': !query.amountFilter }" :title="$t('All')" @click="changeAmountFilter('')">
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="!query.amountFilter"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
<f7-list-item :key="filterType.type"
|
||||
:class="{ 'list-item-selected': query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`) }"
|
||||
:title="$t(filterType.name)"
|
||||
v-for="filterType in allAmountFilterTypes"
|
||||
@click="changeAmountFilter(filterType.type)">
|
||||
<template #after>
|
||||
<span class="margin-right-half" v-if="query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`)">{{ queryAmount }}</span>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="query.amountFilter && query.amountFilter.startsWith(`${filterType.type}:`)"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-popover>
|
||||
|
||||
@@ -426,6 +443,7 @@ import { useAccountsStore } from '@/stores/account.js';
|
||||
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.js';
|
||||
import { useTransactionsStore } from '@/stores/transaction.js';
|
||||
|
||||
import numeralConstants from '@/consts/numeral.js';
|
||||
import datetimeConstants from '@/consts/datetime.js';
|
||||
import currencyConstants from '@/consts/currency.js';
|
||||
import accountConstants from '@/consts/account.js';
|
||||
@@ -518,6 +536,25 @@ export default {
|
||||
queryAccountName() {
|
||||
return getNameByKeyValue(this.allAccounts, this.query.accountId, null, 'name', this.$t('Account'));
|
||||
},
|
||||
queryAmount() {
|
||||
if (!this.query.amountFilter) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const amountFilterItems = this.query.amountFilter.split(':');
|
||||
|
||||
if (amountFilterItems.length < 2) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const displayAmount = [];
|
||||
|
||||
for (let i = 1; i < amountFilterItems.length; i++) {
|
||||
displayAmount.push(this.getDisplayCurrency(amountFilterItems[i], false));
|
||||
}
|
||||
|
||||
return displayAmount.join(' ~ ');
|
||||
},
|
||||
transactions() {
|
||||
if (this.loading) {
|
||||
return [];
|
||||
@@ -531,6 +568,9 @@ export default {
|
||||
hasMoreTransaction() {
|
||||
return this.transactionsStore.hasMoreTransaction;
|
||||
},
|
||||
allAmountFilterTypes() {
|
||||
return numeralConstants.allAmountFilterTypeArray;
|
||||
},
|
||||
allTransactionTypes() {
|
||||
return transactionConstants.allTransactionTypes;
|
||||
},
|
||||
@@ -777,6 +817,24 @@ export default {
|
||||
this.showAccountPopover = false;
|
||||
this.reload(null);
|
||||
},
|
||||
changeAmountFilter(filterType) {
|
||||
if (this.query.amountFilter === filterType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (filterType) {
|
||||
this.showMorePopover = false;
|
||||
this.f7router.navigate(`/transaction/filter/amount?type=${filterType}&value=${this.query.amountFilter}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.transactionsStore.updateTransactionListFilter({
|
||||
amountFilter: filterType
|
||||
});
|
||||
|
||||
this.showMorePopover = false;
|
||||
this.reload(null);
|
||||
},
|
||||
changeKeywordFilter(keyword) {
|
||||
if (this.query.keyword === keyword) {
|
||||
return;
|
||||
@@ -1008,7 +1066,10 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.date-popover-menu .popover-inner, .category-popover-menu .popover-inner, .account-popover-menu .popover-inner {
|
||||
.date-popover-menu .popover-inner,
|
||||
.category-popover-menu .popover-inner,
|
||||
.account-popover-menu .popover-inner,
|
||||
.more-popover-menu .popover-inner{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user