add transaction tag
This commit is contained in:
@@ -91,5 +91,13 @@ func updateAllDatabaseTablesStructure() error {
|
|||||||
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction category table maintained successfully")
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction category table maintained successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTag))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag table maintained successfully")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,15 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler))
|
apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler))
|
||||||
apiV1Route.POST("/transaction/categories/delete.json", bindApi(api.TransactionCategories.CategoryDeleteHandler))
|
apiV1Route.POST("/transaction/categories/delete.json", bindApi(api.TransactionCategories.CategoryDeleteHandler))
|
||||||
|
|
||||||
|
// Transaction Tags
|
||||||
|
apiV1Route.GET("/transaction/tags/list.json", bindApi(api.TransactionTags.TagListHandler))
|
||||||
|
apiV1Route.GET("/transaction/tags/get.json", bindApi(api.TransactionTags.TagGetHandler))
|
||||||
|
apiV1Route.POST("/transaction/tags/add.json", bindApi(api.TransactionTags.TagCreateHandler))
|
||||||
|
apiV1Route.POST("/transaction/tags/modify.json", bindApi(api.TransactionTags.TagModifyHandler))
|
||||||
|
apiV1Route.POST("/transaction/tags/hide.json", bindApi(api.TransactionTags.TagHideHandler))
|
||||||
|
apiV1Route.POST("/transaction/tags/move.json", bindApi(api.TransactionTags.TagMoveHandler))
|
||||||
|
apiV1Route.POST("/transaction/tags/delete.json", bindApi(api.TransactionTags.TagDeleteHandler))
|
||||||
|
|
||||||
// Exchange Rates
|
// Exchange Rates
|
||||||
apiV1Route.GET("/exchange_rates/latest.json", bindApi(api.ExchangeRates.LatestExchangeRateHandler))
|
apiV1Route.GET("/exchange_rates/latest.json", bindApi(api.ExchangeRates.LatestExchangeRateHandler))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/lab/pkg/core"
|
||||||
|
"github.com/mayswind/lab/pkg/errs"
|
||||||
|
"github.com/mayswind/lab/pkg/log"
|
||||||
|
"github.com/mayswind/lab/pkg/models"
|
||||||
|
"github.com/mayswind/lab/pkg/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionTagsApi struct {
|
||||||
|
tags *services.TransactionTagService
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
TransactionTags = &TransactionTagsApi{
|
||||||
|
tags: services.TransactionTags,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagListHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
tags, err := a.tags.GetAllTagsByUid(uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagListHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tagResps := make([]*models.TransactionTagInfoResponse, len(tags))
|
||||||
|
|
||||||
|
for i := 0; i < len(tags); i++ {
|
||||||
|
tagResps[i] = tags[i].ToTransactionTagInfoResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagResps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagGetReq models.TransactionTagGetRequest
|
||||||
|
err := c.ShouldBindQuery(&tagGetReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.TagGetHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
tag, err := a.tags.GetTagByTagId(uid, tagGetReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagGetHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagGetReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tagResp := tag.ToTransactionTagInfoResponse()
|
||||||
|
|
||||||
|
return tagResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagCreateReq models.TransactionTagCreateRequest
|
||||||
|
err := c.ShouldBindJSON(&tagCreateReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.TagCreateHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
|
maxOrderId, err := a.tags.GetMaxDisplayOrder(uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := a.createNewTagModel(uid, &tagCreateReq, maxOrderId+1)
|
||||||
|
|
||||||
|
err = a.tags.CreateTag(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to create tag \"id:%d\" for user \"uid:%d\", because %s", tag.TagId, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_tags.TagCreateHandler] user \"uid:%d\" has created a new tag \"id:%d\" successfully", uid, tag.TagId)
|
||||||
|
|
||||||
|
tagResp := tag.ToTransactionTagInfoResponse()
|
||||||
|
|
||||||
|
return tagResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagModifyReq models.TransactionTagModifyRequest
|
||||||
|
err := c.ShouldBindJSON(&tagModifyReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.TagModifyHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
tag, err := a.tags.GetTagByTagId(uid, tagModifyReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
newTag := &models.TransactionTag{
|
||||||
|
TagId: tag.TagId,
|
||||||
|
Uid: uid,
|
||||||
|
Name: tagModifyReq.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if newTag.Name == tag.Name {
|
||||||
|
return nil, errs.ErrNothingWillBeUpdated
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.tags.ModifyTag(newTag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to update tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_tags.TagModifyHandler] user \"uid:%d\" has updated tag \"id:%d\" successfully", uid, tagModifyReq.Id)
|
||||||
|
|
||||||
|
tag.Name = newTag.Name
|
||||||
|
tagResp := tag.ToTransactionTagInfoResponse()
|
||||||
|
|
||||||
|
return tagResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagHideReq models.TransactionTagHideRequest
|
||||||
|
err := c.ShouldBindJSON(&tagHideReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.CategoryHideHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
err = a.tags.HideTag(uid, []int64{tagHideReq.Id}, tagHideReq.Hidden)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.CategoryHideHandler] failed to hide tag \"id:%d\" for user \"uid:%d\", because %s", tagHideReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_tags.CategoryHideHandler] user \"uid:%d\" has hidden category \"id:%d\"", uid, tagHideReq.Id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagMoveReq models.TransactionTagMoveRequest
|
||||||
|
err := c.ShouldBindJSON(&tagMoveReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
tags := make([]*models.TransactionTag, len(tagMoveReq.NewDisplayOrders))
|
||||||
|
|
||||||
|
for i := 0; i < len(tagMoveReq.NewDisplayOrders); i++ {
|
||||||
|
newDisplayOrder := tagMoveReq.NewDisplayOrders[i]
|
||||||
|
tag := &models.TransactionTag{
|
||||||
|
Uid: uid,
|
||||||
|
TagId: newDisplayOrder.Id,
|
||||||
|
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
tags[i] = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.tags.ModifyTagDisplayOrders(uid, tags)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] failed to move tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_tags.CategoryMoveHandler] user \"uid:%d\" has moved categories", uid)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var tagDeleteReq models.TransactionTagDeleteRequest
|
||||||
|
err := c.ShouldBindJSON(&tagDeleteReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[transaction_tags.TagDeleteHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
err = a.tags.DeleteTags(uid, []int64{tagDeleteReq.Id})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[transaction_tags.TagDeleteHandler] failed to delete tag \"id:%d\" for user \"uid:%d\", because %s", tagDeleteReq.Id, uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.InfofWithRequestId(c, "[transaction_tags.TagDeleteHandler] user \"uid:%d\" has deleted tag \"id:%d\"", uid, tagDeleteReq.Id)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.TransactionTagCreateRequest, order int) *models.TransactionTag {
|
||||||
|
return &models.TransactionTag{
|
||||||
|
Uid: uid,
|
||||||
|
Name: tagCreateReq.Name,
|
||||||
|
DisplayOrder: order,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTransactionTagIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_TAG, 0, http.StatusBadRequest, "transaction tag id is invalid")
|
||||||
|
ErrTransactionTagNotFound = NewNormalError(NORMAL_SUBCATEGORY_TAG, 1, http.StatusBadRequest, "transaction tag not found")
|
||||||
|
ErrTransactionTagNameIsEmpty = NewNormalError(NORMAL_SUBCATEGORY_TAG, 2, http.StatusBadRequest, "transaction tag name is empty")
|
||||||
|
ErrTransactionTagNameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_TAG, 3, http.StatusBadRequest, "transaction tag name already exists")
|
||||||
|
)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type TransactionTag struct {
|
||||||
|
TagId int64 `xorm:"PK"`
|
||||||
|
Uid int64 `xorm:"UNIQUE(IDX_tag_uid_name) NOT NULL"`
|
||||||
|
Name string `xorm:"UNIQUE(IDX_tag_uid_name) VARCHAR(32) NOT NULL"`
|
||||||
|
DisplayOrder int `xorm:"NOT NULL"`
|
||||||
|
Hidden bool `xorm:"NOT NULL"`
|
||||||
|
CreatedUnixTime int64
|
||||||
|
UpdatedUnixTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagCreateRequest struct {
|
||||||
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagGetRequest struct {
|
||||||
|
Id int64 `form:"id,string" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagModifyRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
Name string `json:"name" binding:"required,notBlank,max=32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagHideRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagMoveRequest struct {
|
||||||
|
NewDisplayOrders []*TransactionTagNewDisplayOrderRequest `json:"newDisplayOrders"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagNewDisplayOrderRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
DisplayOrder int `json:"displayOrder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagDeleteRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagInfoResponse struct {
|
||||||
|
Id int64 `json:"id,string"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisplayOrder int `json:"displayOrder"`
|
||||||
|
Hidden bool `json:"hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TransactionTag) ToTransactionTagInfoResponse() *TransactionTagInfoResponse {
|
||||||
|
return &TransactionTagInfoResponse{
|
||||||
|
Id: c.TagId,
|
||||||
|
Name: c.Name,
|
||||||
|
DisplayOrder: c.DisplayOrder,
|
||||||
|
Hidden: c.Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionTagInfoResponseSlice []*TransactionTagInfoResponse
|
||||||
|
|
||||||
|
func (c TransactionTagInfoResponseSlice) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TransactionTagInfoResponseSlice) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TransactionTagInfoResponseSlice) Less(i, j int) bool {
|
||||||
|
return c[i].DisplayOrder < c[j].DisplayOrder
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/lab/pkg/datastore"
|
||||||
|
"github.com/mayswind/lab/pkg/errs"
|
||||||
|
"github.com/mayswind/lab/pkg/models"
|
||||||
|
"github.com/mayswind/lab/pkg/uuid"
|
||||||
|
"time"
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionTagService struct {
|
||||||
|
ServiceUsingDB
|
||||||
|
ServiceUsingUuid
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
TransactionTags = &TransactionTagService{
|
||||||
|
ServiceUsingDB: ServiceUsingDB{
|
||||||
|
container: datastore.Container,
|
||||||
|
},
|
||||||
|
ServiceUsingUuid: ServiceUsingUuid{
|
||||||
|
container: uuid.Container,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *TransactionTagService) GetAllTagsByUid(uid int64) ([]*models.TransactionTag, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []*models.TransactionTag
|
||||||
|
err := s.UserDataDB(uid).Where("uid=?", uid).OrderBy("display_order asc").Find(&tags)
|
||||||
|
|
||||||
|
return tags, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.TransactionTag, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return nil, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagId <= 0 {
|
||||||
|
return nil, errs.ErrTransactionTagIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := &models.TransactionTag{}
|
||||||
|
has, err := s.UserDataDB(uid).Where("uid=? AND tag_id=?", uid, tagId).Get(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if !has {
|
||||||
|
return nil, errs.ErrTransactionTagNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return 0, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := &models.TransactionTag{}
|
||||||
|
has, err := s.UserDataDB(uid).Cols("uid", "display_order").Where("uid=?", uid).OrderBy("display_order desc").Limit(1).Get(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if has {
|
||||||
|
return tag.DisplayOrder, nil
|
||||||
|
} else {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error {
|
||||||
|
if tag.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.ExistsTagName(tag.Uid, tag.Name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if exists {
|
||||||
|
return errs.ErrTransactionTagNameAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.TagId = s.GenerateUuid(uuid.UUID_TYPE_TAG)
|
||||||
|
|
||||||
|
tag.CreatedUnixTime = time.Now().Unix()
|
||||||
|
tag.UpdatedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||||
|
_, err := sess.Insert(tag)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) ModifyTag(tag *models.TransactionTag) error {
|
||||||
|
if tag.Uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.ExistsTagName(tag.Uid, tag.Name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if exists {
|
||||||
|
return errs.ErrTransactionTagNameAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.UpdatedUnixTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.Cols("name", "updated_unix_time").Where("tag_id=? AND uid=?", tag.TagId, tag.Uid).Update(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTagNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) HideTag(uid int64, ids []int64, hidden bool) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.TransactionTag{
|
||||||
|
Hidden: hidden,
|
||||||
|
UpdatedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||||
|
updatedRows, err := sess.Cols("hidden", "updated_unix_time").In("tag_id", ids).Where("uid=?", uid).Update(updateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTagNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models.TransactionTag) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(tags); i++ {
|
||||||
|
tags[i].UpdatedUnixTime = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||||
|
for i := 0; i < len(tags); i++ {
|
||||||
|
tag := tags[i]
|
||||||
|
updatedRows, err := sess.Cols("display_order", "updated_unix_time").Where("tag_id=? AND uid=?", tag.TagId, uid).Update(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if updatedRows < 1 {
|
||||||
|
return errs.ErrTransactionTagNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) DeleteTags(uid int64, ids []int64) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := &models.TransactionTag{}
|
||||||
|
|
||||||
|
return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error {
|
||||||
|
deletedRows, err := sess.In("tag_id", ids).Where("uid=?", uid).Delete(tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if deletedRows < 1 {
|
||||||
|
return errs.ErrTransactionTagNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TransactionTagService) ExistsTagName(uid int64, name string) (bool, error) {
|
||||||
|
if name == "" {
|
||||||
|
return false, errs.ErrTransactionTagNameIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDB().Cols("name").Where("uid=? AND name=?", uid, name).Exist(&models.TransactionTag{})
|
||||||
|
}
|
||||||
@@ -129,6 +129,15 @@ i.icon.la, i.icon.las, i.icon.lab {
|
|||||||
align-self: normal !important;
|
align-self: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list .item-content .input.list-title-input {
|
||||||
|
margin-top: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||||
|
margin-bottom: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .item-content .list-item-valign-middle {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
.list .item-content .list-item-checked {
|
.list .item-content .list-item-checked {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: var(--f7-radio-active-color, var(--f7-theme-color));
|
color: var(--f7-radio-active-color, var(--f7-theme-color));
|
||||||
|
|||||||
@@ -259,6 +259,39 @@ export default {
|
|||||||
id
|
id
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getAllTransactionTags: () => {
|
||||||
|
return axios.get('v1/transaction/tags/list.json');
|
||||||
|
},
|
||||||
|
getTransactionTag: ({ id }) => {
|
||||||
|
return axios.get('v1/transaction/tags/get.json?id=' + id);
|
||||||
|
},
|
||||||
|
addTransactionTag: ({ name }) => {
|
||||||
|
return axios.post('v1/transaction/tags/add.json', {
|
||||||
|
name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
modifyTransactionTag: ({ id, name }) => {
|
||||||
|
return axios.post('v1/transaction/tags/modify.json', {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hideTransactionTag: ({ id, hidden }) => {
|
||||||
|
return axios.post('v1/transaction/tags/hide.json', {
|
||||||
|
id,
|
||||||
|
hidden
|
||||||
|
});
|
||||||
|
},
|
||||||
|
moveTransactionTag: ({ newDisplayOrders }) => {
|
||||||
|
return axios.post('v1/transaction/tags/move.json', {
|
||||||
|
newDisplayOrders,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteTransactionTag: ({ id }) => {
|
||||||
|
return axios.post('v1/transaction/tags/delete.json', {
|
||||||
|
id
|
||||||
|
});
|
||||||
|
},
|
||||||
getLatestExchangeRates: () => {
|
getLatestExchangeRates: () => {
|
||||||
return axios.get('v1/exchange_rates/latest.json');
|
return axios.get('v1/exchange_rates/latest.json');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -307,6 +307,10 @@ export default {
|
|||||||
'transaction category type is invalid': 'Transaction category type is invalid',
|
'transaction category type is invalid': 'Transaction category type is invalid',
|
||||||
'parent transaction category not found': 'Parent transaction category is not found',
|
'parent transaction category not found': 'Parent transaction category is not found',
|
||||||
'cannot add to secondary transaction category': 'Cannot add transaction category to secondary transaction category',
|
'cannot add to secondary transaction category': 'Cannot add transaction category to secondary transaction category',
|
||||||
|
'transaction tag id is invalid': 'Transaction tag ID is invalid',
|
||||||
|
'transaction tag not found': 'Transaction tag is not found',
|
||||||
|
'transaction tag name is empty': 'Transaction tag title is empty',
|
||||||
|
'transaction tag name already exists': 'Transaction tag title already exists',
|
||||||
},
|
},
|
||||||
'parameter': {
|
'parameter': {
|
||||||
'id': 'ID',
|
'id': 'ID',
|
||||||
@@ -520,6 +524,15 @@ export default {
|
|||||||
'You have added a new category': 'You have added a new category',
|
'You have added a new category': 'You have added a new category',
|
||||||
'You have added default categories': 'You have added default categories',
|
'You have added default categories': 'You have added default categories',
|
||||||
'You have saved this category': 'You have saved this category',
|
'You have saved this category': 'You have saved this category',
|
||||||
|
'Transaction Tags': 'Transaction Tags',
|
||||||
|
'Tag Title': 'Tag Title',
|
||||||
|
'No available tag': 'No available tag',
|
||||||
|
'Unable to get tag list': 'Unable to get tag list',
|
||||||
|
'Unable to move tag': 'Unable to move tag',
|
||||||
|
'Unable to hide this tag': 'Unable to hide this tag',
|
||||||
|
'Unable to unhide this tag': 'Unable to unhide this tag',
|
||||||
|
'Are you sure you want to delete this tag?': 'Are you sure you want to delete this tag?',
|
||||||
|
'Unable to delete this tag': 'Unable to delete this tag',
|
||||||
'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?',
|
'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?',
|
||||||
'Unable to logout from this session': 'Unable to logout from this session',
|
'Unable to logout from this session': 'Unable to logout from this session',
|
||||||
'Are you sure you want to logout all other sessions?': 'Are you sure you want to logout all other sessions?',
|
'Are you sure you want to logout all other sessions?': 'Are you sure you want to logout all other sessions?',
|
||||||
|
|||||||
@@ -307,6 +307,10 @@ export default {
|
|||||||
'transaction category type is invalid': '交易分类类型无效',
|
'transaction category type is invalid': '交易分类类型无效',
|
||||||
'parent transaction category not found': '父级交易分类不存在',
|
'parent transaction category not found': '父级交易分类不存在',
|
||||||
'cannot add to secondary transaction category': '不能在二级交易分类中添加',
|
'cannot add to secondary transaction category': '不能在二级交易分类中添加',
|
||||||
|
'transaction tag id is invalid': '交易标签ID无效',
|
||||||
|
'transaction tag not found': '交易标签不存在',
|
||||||
|
'transaction tag name is empty': '交易标签标题不能为空',
|
||||||
|
'transaction tag name already exists': '交易标签标题已经存在',
|
||||||
},
|
},
|
||||||
'parameter': {
|
'parameter': {
|
||||||
'id': 'ID',
|
'id': 'ID',
|
||||||
@@ -520,6 +524,15 @@ export default {
|
|||||||
'You have added a new category': '您已经添加新分类',
|
'You have added a new category': '您已经添加新分类',
|
||||||
'You have added default categories': '您已经添加默认分类',
|
'You have added default categories': '您已经添加默认分类',
|
||||||
'You have saved this category': '您已经保存该分类',
|
'You have saved this category': '您已经保存该分类',
|
||||||
|
'Transaction Tags': '交易标签',
|
||||||
|
'Tag Title': '标签标题',
|
||||||
|
'No available tag': '没有可用的标签',
|
||||||
|
'Unable to get tag list': '无法获取标签列表',
|
||||||
|
'Unable to move tag': '无法移动标签',
|
||||||
|
'Unable to hide this tag': '无法隐藏该标签',
|
||||||
|
'Unable to unhide this tag': '无法取消隐藏该标签',
|
||||||
|
'Are you sure you want to delete this tag?': '您确定要删除该标签?',
|
||||||
|
'Unable to delete this tag': '无法删除该标签',
|
||||||
'Are you sure you want to logout from this session?': '您确定要退出该会话?',
|
'Are you sure you want to logout from this session?': '您确定要退出该会话?',
|
||||||
'Unable to logout from this session': '无法退出该会话',
|
'Unable to logout from this session': '无法退出该会话',
|
||||||
'Are you sure you want to logout all other sessions?': '您确定要退出其他所有会话?',
|
'Are you sure you want to logout all other sessions?': '您确定要退出其他所有会话?',
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import CategoryListPage from "../views/mobile/categories/List.vue";
|
|||||||
import CategoryEditPage from "../views/mobile/categories/Edit.vue";
|
import CategoryEditPage from "../views/mobile/categories/Edit.vue";
|
||||||
import CategoryDefaultPage from "../views/mobile/categories/Default.vue";
|
import CategoryDefaultPage from "../views/mobile/categories/Default.vue";
|
||||||
|
|
||||||
|
import TagListPage from "../views/mobile/tags/List.vue";
|
||||||
|
|
||||||
function checkLogin(to, from, resolve, reject) {
|
function checkLogin(to, from, resolve, reject) {
|
||||||
const router = this;
|
const router = this;
|
||||||
|
|
||||||
@@ -210,6 +212,11 @@ const routes = [
|
|||||||
component: CategoryDefaultPage,
|
component: CategoryDefaultPage,
|
||||||
beforeEnter: checkLogin
|
beforeEnter: checkLogin
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tag/list',
|
||||||
|
component: TagListPage,
|
||||||
|
beforeEnter: checkLogin
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '(.*)',
|
path: '(.*)',
|
||||||
redirect: '/'
|
redirect: '/'
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<f7-list>
|
<f7-list>
|
||||||
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
|
<f7-list-item :title="$t('User Profile')" link="/user/profile"></f7-list-item>
|
||||||
<f7-list-item :title="$t('Transaction Categories')" link="/category/all"></f7-list-item>
|
<f7-list-item :title="$t('Transaction Categories')" link="/category/all"></f7-list-item>
|
||||||
|
<f7-list-item :title="$t('Transaction Tags')" link="/tag/list"></f7-list-item>
|
||||||
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa"></f7-list-item>
|
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa"></f7-list-item>
|
||||||
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
|
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
|
||||||
<f7-list-button :class="{ 'disabled': logouting }" @click="logout">{{ $t('Log Out') }}</f7-list-button>
|
<f7-list-button :class="{ 'disabled': logouting }" @click="logout">{{ $t('Log Out') }}</f7-list-button>
|
||||||
|
|||||||
@@ -0,0 +1,500 @@
|
|||||||
|
<template>
|
||||||
|
<f7-page :ptr="!sortable && !hasEditingTag" @ptr:refresh="reload">
|
||||||
|
<f7-navbar>
|
||||||
|
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
|
||||||
|
<f7-nav-title :title="$t('Transaction Tags')"></f7-nav-title>
|
||||||
|
<f7-nav-right class="navbar-compact-icons">
|
||||||
|
<f7-link icon-f7="ellipsis" v-if="!sortable && !hasEditingTag && this.tags.length" @click="showMoreActionSheet = true"></f7-link>
|
||||||
|
<f7-link icon-f7="plus" v-if="!sortable && !hasEditingTag" @click="add"></f7-link>
|
||||||
|
<f7-link :text="$t('Done')" :class="{ 'disabled': displayOrderSaving || hasEditingTag }" v-else-if="sortable" @click="saveSortResult"></f7-link>
|
||||||
|
</f7-nav-right>
|
||||||
|
</f7-navbar>
|
||||||
|
|
||||||
|
<f7-card class="skeleton-text" v-if="loading">
|
||||||
|
<f7-card-content class="no-safe-areas" :padding="false">
|
||||||
|
<f7-list>
|
||||||
|
<f7-list-item>
|
||||||
|
<f7-block slot="title" class="no-padding">
|
||||||
|
<div class="display-flex">
|
||||||
|
<f7-icon slot="media" f7="number"></f7-icon>
|
||||||
|
<div class="list-item-valign-middle padding-left-half">Tag Name</div>
|
||||||
|
</div>
|
||||||
|
</f7-block>
|
||||||
|
</f7-list-item>
|
||||||
|
<f7-list-item>
|
||||||
|
<f7-block slot="title" class="no-padding">
|
||||||
|
<div class="display-flex">
|
||||||
|
<f7-icon slot="media" f7="number"></f7-icon>
|
||||||
|
<div class="list-item-valign-middle padding-left-half">Tag Name 2</div>
|
||||||
|
</div>
|
||||||
|
</f7-block>
|
||||||
|
</f7-list-item>
|
||||||
|
<f7-list-item>
|
||||||
|
<f7-block slot="title" class="no-padding">
|
||||||
|
<div class="display-flex">
|
||||||
|
<f7-icon slot="media" f7="number"></f7-icon>
|
||||||
|
<div class="list-item-valign-middle padding-left-half">Tag Name 3</div>
|
||||||
|
</div>
|
||||||
|
</f7-block>
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
</f7-card-content>
|
||||||
|
</f7-card>
|
||||||
|
|
||||||
|
<f7-card v-else-if="!loading">
|
||||||
|
<f7-card-content class="no-safe-areas" :padding="false">
|
||||||
|
<f7-list v-if="noAvailableTag">
|
||||||
|
<f7-list-item :title="$t('No available tag')"></f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
|
||||||
|
<f7-list sortable :sortable-enabled="sortable" @sortable:sort="onSort">
|
||||||
|
<f7-list-item v-for="tag in tags"
|
||||||
|
:key="tag.id"
|
||||||
|
:id="tag | tagDomId"
|
||||||
|
v-show="showHidden || !tag.hidden"
|
||||||
|
swipeout @taphold.native="setSortable()">
|
||||||
|
<f7-block slot="title" class="no-padding">
|
||||||
|
<div class="display-flex">
|
||||||
|
<f7-icon slot="media" f7="number">
|
||||||
|
<f7-badge color="gray" class="right-bottom-icon" v-if="tag.hidden">
|
||||||
|
<f7-icon f7="eye_slash_fill"></f7-icon>
|
||||||
|
</f7-badge>
|
||||||
|
</f7-icon>
|
||||||
|
|
||||||
|
<div class="list-item-valign-middle padding-left-half"
|
||||||
|
v-if="!tag.editing">
|
||||||
|
{{ tag.name }}
|
||||||
|
</div>
|
||||||
|
<f7-input class="list-title-input padding-left-half"
|
||||||
|
type="text"
|
||||||
|
:placeholder="$t('Tag Title')"
|
||||||
|
:value="tag.newName"
|
||||||
|
v-else-if="tag.editing"
|
||||||
|
@input="tag.newName = $event.target.value"
|
||||||
|
@keyup.enter.native="save(tag)">
|
||||||
|
</f7-input>
|
||||||
|
</div>
|
||||||
|
</f7-block>
|
||||||
|
<f7-button slot="after"
|
||||||
|
:class="{ 'no-padding': true, 'disabled': !isTagModified(tag) }"
|
||||||
|
raised fill
|
||||||
|
icon-f7="checkmark_alt"
|
||||||
|
color="blue"
|
||||||
|
v-if="tag.editing"
|
||||||
|
@click="save(tag)">
|
||||||
|
</f7-button>
|
||||||
|
<f7-button slot="after"
|
||||||
|
class="no-padding margin-left-half"
|
||||||
|
raised fill
|
||||||
|
icon-f7="xmark"
|
||||||
|
color="gray"
|
||||||
|
v-if="tag.editing"
|
||||||
|
@click="cancelSave(tag)">
|
||||||
|
</f7-button>
|
||||||
|
<f7-swipeout-actions left v-if="sortable && !tag.editing">
|
||||||
|
<f7-swipeout-button :color="tag.hidden ? 'blue' : 'gray'" class="padding-left padding-right"
|
||||||
|
overswipe close @click="hide(tag, !tag.hidden)">
|
||||||
|
<f7-icon :f7="tag.hidden ? 'eye' : 'eye_slash'"></f7-icon>
|
||||||
|
</f7-swipeout-button>
|
||||||
|
</f7-swipeout-actions>
|
||||||
|
<f7-swipeout-actions right v-if="!sortable && !tag.editing">
|
||||||
|
<f7-swipeout-button color="orange" close :text="$t('Edit')" @click="edit(tag)"></f7-swipeout-button>
|
||||||
|
<f7-swipeout-button color="red" class="padding-left padding-right" @click="remove(tag)">
|
||||||
|
<f7-icon f7="trash"></f7-icon>
|
||||||
|
</f7-swipeout-button>
|
||||||
|
</f7-swipeout-actions>
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
</f7-card-content>
|
||||||
|
</f7-card>
|
||||||
|
|
||||||
|
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
|
||||||
|
<f7-actions-group>
|
||||||
|
<f7-actions-button @click="setSortable()">{{ $t('Sort') }}</f7-actions-button>
|
||||||
|
</f7-actions-group>
|
||||||
|
<f7-actions-group>
|
||||||
|
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
||||||
|
</f7-actions-group>
|
||||||
|
</f7-actions>
|
||||||
|
|
||||||
|
<f7-actions close-by-outside-click close-on-escape :opened="showDeleteActionSheet" @actions:closed="showDeleteActionSheet = false">
|
||||||
|
<f7-actions-group>
|
||||||
|
<f7-actions-label>{{ $t('Are you sure you want to delete this tag?') }}</f7-actions-label>
|
||||||
|
<f7-actions-button color="red" @click="remove(tagToDelete)">{{ $t('Delete') }}</f7-actions-button>
|
||||||
|
</f7-actions-group>
|
||||||
|
<f7-actions-group>
|
||||||
|
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
||||||
|
</f7-actions-group>
|
||||||
|
</f7-actions>
|
||||||
|
</f7-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tags: [],
|
||||||
|
loading: true,
|
||||||
|
showHidden: false,
|
||||||
|
sortable: false,
|
||||||
|
tagToDelete: null,
|
||||||
|
showMoreActionSheet: false,
|
||||||
|
showDeleteActionSheet: false,
|
||||||
|
editSaving: false,
|
||||||
|
displayOrderModified: false,
|
||||||
|
displayOrderSaving: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
noAvailableTag() {
|
||||||
|
for (let i = 0; i < this.tags.length; i++) {
|
||||||
|
if (this.showHidden || !this.tags[i].hidden) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
hasEditingTag() {
|
||||||
|
for (let i = 0; i < this.tags.length; i++) {
|
||||||
|
if (this.tags[i].editing) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const self = this;
|
||||||
|
const router = self.$f7router;
|
||||||
|
|
||||||
|
self.loading = true;
|
||||||
|
|
||||||
|
self.$services.getAllTransactionTags().then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$toast('Unable to get tag list');
|
||||||
|
router.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < data.result.length; i++) {
|
||||||
|
data.result[i].editing = false;
|
||||||
|
data.result[i].newName = data.result[i].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tags = data.result;
|
||||||
|
self.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to load tag list', error);
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
router.back();
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast('Unable to get tag list');
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
reload(done) {
|
||||||
|
if (this.sortable || this.hasEditingTag) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$services.getAllTransactionTags().then(response => {
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$toast('Unable to get tag list');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < data.result.length; i++) {
|
||||||
|
data.result[i].editing = false;
|
||||||
|
data.result[i].newName = data.result[i].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tags = data.result;
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to reload tag list', error);
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast('Unable to get category list');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setSortable() {
|
||||||
|
if (this.sortable || this.hasEditingTag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showHidden = true;
|
||||||
|
this.sortable = true;
|
||||||
|
this.displayOrderModified = false;
|
||||||
|
},
|
||||||
|
onSort(event) {
|
||||||
|
if (!event || !event.el || !event.el.id || event.el.id.indexOf('tag_') !== 0) {
|
||||||
|
this.$toast('Unable to move tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = event.el.id.substr(4); // tag_
|
||||||
|
let tag = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.tags.length; i++) {
|
||||||
|
if (this.tags[i].id === id) {
|
||||||
|
tag = this.tags[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tag || !this.tags[event.to]) {
|
||||||
|
this.$toast('Unable to move tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tags.splice(event.to, 0, this.tags.splice(event.from, 1)[0]);
|
||||||
|
|
||||||
|
this.displayOrderModified = true;
|
||||||
|
},
|
||||||
|
saveSortResult() {
|
||||||
|
const self = this;
|
||||||
|
const newDisplayOrders = [];
|
||||||
|
|
||||||
|
if (!self.displayOrderModified) {
|
||||||
|
self.showHidden = false;
|
||||||
|
self.sortable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.displayOrderSaving = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < self.tags.length; i++) {
|
||||||
|
newDisplayOrders.push({
|
||||||
|
id: self.tags[i].id,
|
||||||
|
displayOrder: i + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$showLoading();
|
||||||
|
|
||||||
|
self.$services.moveTransactionTag({
|
||||||
|
newDisplayOrders: newDisplayOrders
|
||||||
|
}).then(response => {
|
||||||
|
self.displayOrderSaving = false;
|
||||||
|
self.$hideLoading();
|
||||||
|
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$toast('Unable to move tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.showHidden = false;
|
||||||
|
self.sortable = false;
|
||||||
|
self.displayOrderModified = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to save tags display order', error);
|
||||||
|
|
||||||
|
self.displayOrderSaving = false;
|
||||||
|
self.$hideLoading();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast('Unable to move tag');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
add() {
|
||||||
|
this.tags.push({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
newName: '',
|
||||||
|
hidden: false,
|
||||||
|
editing: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
edit(tag) {
|
||||||
|
tag.newName = tag.name;
|
||||||
|
tag.editing = true;
|
||||||
|
},
|
||||||
|
save(tag) {
|
||||||
|
if (tag.newName === tag.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$showLoading();
|
||||||
|
|
||||||
|
let promise = null;
|
||||||
|
|
||||||
|
if (tag.id) {
|
||||||
|
promise = self.$services.modifyTransactionTag({
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.newName
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = self.$services.addTransactionTag({
|
||||||
|
name: tag.newName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(response => {
|
||||||
|
self.$hideLoading();
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$toast('Unable to save this tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.id = data.result.id;
|
||||||
|
tag.name = data.result.name;
|
||||||
|
tag.hidden = data.result.hidden;
|
||||||
|
tag.editing = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to save tag', error);
|
||||||
|
|
||||||
|
self.$hideLoading();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast('Unable to save this tag');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancelSave(tag) {
|
||||||
|
if (tag.id) {
|
||||||
|
tag.newName = '';
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < this.tags.length; i++) {
|
||||||
|
if (this.tags[i] === tag) {
|
||||||
|
this.tags.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.editing = false;
|
||||||
|
},
|
||||||
|
isTagModified(tag) {
|
||||||
|
return tag.newName !== tag.name;
|
||||||
|
},
|
||||||
|
hide(tag, hidden) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$showLoading();
|
||||||
|
|
||||||
|
self.$services.hideTransactionTag({
|
||||||
|
id: tag.id,
|
||||||
|
hidden: hidden
|
||||||
|
}).then(response => {
|
||||||
|
self.$hideLoading();
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
if (hidden) {
|
||||||
|
self.$toast('Unable to hide this tag');
|
||||||
|
} else {
|
||||||
|
self.$toast('Unable to unhide this tag');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag.hidden = hidden;
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to change tag visibility', error);
|
||||||
|
|
||||||
|
self.$hideLoading();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
if (hidden) {
|
||||||
|
self.$toast('Unable to hide this tag');
|
||||||
|
} else {
|
||||||
|
self.$toast('Unable to unhide this tag');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove(tag) {
|
||||||
|
const self = this;
|
||||||
|
const app = self.$f7;
|
||||||
|
const $$ = app.$;
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
self.$alert('An error has occurred');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.showDeleteActionSheet) {
|
||||||
|
self.tagToDelete = tag;
|
||||||
|
self.showDeleteActionSheet = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.showDeleteActionSheet = false;
|
||||||
|
self.tagToDelete = null;
|
||||||
|
self.$showLoading();
|
||||||
|
|
||||||
|
self.$services.deleteTransactionTag({
|
||||||
|
id: tag.id
|
||||||
|
}).then(response => {
|
||||||
|
self.$hideLoading();
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
self.$toast('Unable to delete this tag');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.swipeout.delete($$(`#${self.$options.filters.tagDomId(tag)}`), () => {
|
||||||
|
for (let i = 0; i < self.tags.length; i++) {
|
||||||
|
if (self.tags[i].id === tag.id) {
|
||||||
|
self.tags.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
self.$logger.error('failed to delete tag', error);
|
||||||
|
|
||||||
|
self.$hideLoading();
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
self.$toast({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast('Unable to delete this tag');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
tagDomId(category) {
|
||||||
|
return 'tag_' + category.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user