save insights explorer to database
This commit is contained in:
@@ -157,5 +157,13 @@ func updateAllDatabaseTablesStructure(c *core.CliContext) error {
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] user external auth table maintained successfully")
|
||||
|
||||
err = datastore.Container.UserDataStore.SyncStructs(new(models.InsightsExplorer))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.BootInfof(c, "[database.updateAllDatabaseTablesStructure] insights explorer table maintained successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -437,6 +437,15 @@ func startWebServer(c *core.CliContext) error {
|
||||
apiV1Route.POST("/transaction/templates/move.json", bindApi(api.TransactionTemplates.TemplateMoveHandler))
|
||||
apiV1Route.POST("/transaction/templates/delete.json", bindApi(api.TransactionTemplates.TemplateDeleteHandler))
|
||||
|
||||
// Insights Explorers
|
||||
apiV1Route.GET("/insights/explorers/list.json", bindApi(api.InsightsExplorers.InsightsExplorerListHandler))
|
||||
apiV1Route.GET("/insights/explorers/get.json", bindApi(api.InsightsExplorers.InsightsExplorerGetHandler))
|
||||
apiV1Route.POST("/insights/explorers/add.json", bindApi(api.InsightsExplorers.InsightsExplorerCreateHandler))
|
||||
apiV1Route.POST("/insights/explorers/modify.json", bindApi(api.InsightsExplorers.InsightsExplorerModifyHandler))
|
||||
apiV1Route.POST("/insights/explorers/hide.json", bindApi(api.InsightsExplorers.InsightsExplorerHideHandler))
|
||||
apiV1Route.POST("/insights/explorers/move.json", bindApi(api.InsightsExplorers.InsightsExplorerMoveHandler))
|
||||
apiV1Route.POST("/insights/explorers/delete.json", bindApi(api.InsightsExplorers.InsightsExplorerDeleteHandler))
|
||||
|
||||
// Large Language Models
|
||||
if config.ReceiptImageRecognitionLLMConfig != nil && config.ReceiptImageRecognitionLLMConfig.LLMProvider != "" {
|
||||
if config.TransactionFromAIImageRecognition {
|
||||
|
||||
@@ -31,6 +31,7 @@ type DataManagementsApi struct {
|
||||
pictures *services.TransactionPictureService
|
||||
templates *services.TransactionTemplateService
|
||||
userCustomExchangeRates *services.UserCustomExchangeRatesService
|
||||
insightsExploreres *services.InsightsExplorerService
|
||||
}
|
||||
|
||||
// Initialize a data management api singleton instance
|
||||
@@ -48,6 +49,7 @@ var (
|
||||
pictures: services.TransactionPictures,
|
||||
templates: services.TransactionTemplates,
|
||||
userCustomExchangeRates: services.UserCustomExchangeRates,
|
||||
insightsExploreres: services.InsightsExplorers,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -190,6 +192,13 @@ func (a *DataManagementsApi) ClearAllDataHandler(c *core.WebContext) (any, *errs
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.DeleteAllInsightsExplorers(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[data_managements.ClearAllDataHandler] failed to delete all insights explorers, because %s", err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[data_managements.ClearAllDataHandler] user \"uid:%d\" has cleared all data", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/services"
|
||||
)
|
||||
|
||||
// InsightsExplorersApi represents insights explorers api
|
||||
type InsightsExplorersApi struct {
|
||||
insightsExploreres *services.InsightsExplorerService
|
||||
}
|
||||
|
||||
// Initialize a insights explorers api singleton instance
|
||||
var (
|
||||
InsightsExplorers = &InsightsExplorersApi{
|
||||
insightsExploreres: services.InsightsExplorers,
|
||||
}
|
||||
)
|
||||
|
||||
// InsightsExplorerListHandler returns insights explorer list of current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerListHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
uid := c.GetCurrentUid()
|
||||
explorers, err := a.insightsExploreres.GetAllInsightsExplorerNamesByUid(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerListHandler] failed to get insights explorers for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorerResps := make(models.InsightsExplorerInfoResponseSlice, len(explorers))
|
||||
|
||||
for i := 0; i < len(explorers); i++ {
|
||||
explorerResps[i], err = explorers[i].ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerListHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(explorerResps)
|
||||
|
||||
return explorerResps, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerGetHandler returns one specific insights explorer of current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerGetHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerGetReq models.InsightsExplorerGetRequest
|
||||
err := c.ShouldBindQuery(&explorerGetReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerGetHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorer, err := a.insightsExploreres.GetInsightsExplorerByExplorerId(c, uid, explorerGetReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerGetHandler] failed to get insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerGetReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerGetHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerCreateHandler saves a new insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerCreateHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerCreateReq models.InsightsExplorerCreateRequest
|
||||
err := c.ShouldBindJSON(&explorerCreateReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerCreateHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
|
||||
maxOrderId, err := a.insightsExploreres.GetMaxDisplayOrder(c, uid)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
explorer, err := a.createNewInsightsExplorerModel(uid, &explorerCreateReq, maxOrderId+1)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to parse insights explorer data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.CreateInsightsExplorer(c, explorer)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to create insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorer.ExplorerId, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerCreateHandler] user \"uid:%d\" has created a new insights explorer \"id:%d\" successfully", uid, explorer.ExplorerId)
|
||||
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerCreateHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerModifyHandler saves an existed insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerModifyHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerModifyReq models.InsightsExplorerModifyRequest
|
||||
err := c.ShouldBindJSON(&explorerModifyReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerModifyHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorer, err := a.insightsExploreres.GetInsightsExplorerByExplorerId(c, uid, explorerModifyReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to get insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
newData, err := json.Marshal(explorerModifyReq.Data)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to parse insights explorer data for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
newExplorer := &models.InsightsExplorer{
|
||||
ExplorerId: explorer.ExplorerId,
|
||||
Uid: uid,
|
||||
Name: explorerModifyReq.Name,
|
||||
Data: string(newData),
|
||||
}
|
||||
|
||||
if newExplorer.Name == explorer.Name && newExplorer.Data == explorer.Data {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.ModifyInsightsExplorer(c, newExplorer)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to update insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerModifyReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerModifyHandler] user \"uid:%d\" has updated insights explorer \"id:%d\" successfully", uid, explorerModifyReq.Id)
|
||||
|
||||
explorer.Name = newExplorer.Name
|
||||
explorer.Data = newExplorer.Data
|
||||
explorerResp, err := explorer.ToInsightsExplorerInfoResponse()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerModifyHandler] failed to get insights explorer response for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.ErrInsightsExplorerDataInvalid
|
||||
}
|
||||
|
||||
return explorerResp, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerHideHandler hides a insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerHideHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerHideReq models.InsightsExplorerHideRequest
|
||||
err := c.ShouldBindJSON(&explorerHideReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerHideHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.insightsExploreres.HideInsightsExplorer(c, uid, []int64{explorerHideReq.Id}, explorerHideReq.Hidden)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerHideHandler] failed to hide insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerHideReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerHideHandler] user \"uid:%d\" has hidden insights explorer \"id:%d\"", uid, explorerHideReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerMoveHandler moves display order of existed insights explorers by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerMoveHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerMoveReq models.InsightsExplorerMoveRequest
|
||||
err := c.ShouldBindJSON(&explorerMoveReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerMoveHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
explorers := make([]*models.InsightsExplorer, len(explorerMoveReq.NewDisplayOrders))
|
||||
|
||||
for i := 0; i < len(explorerMoveReq.NewDisplayOrders); i++ {
|
||||
newDisplayOrder := explorerMoveReq.NewDisplayOrders[i]
|
||||
explorer := &models.InsightsExplorer{
|
||||
Uid: uid,
|
||||
ExplorerId: newDisplayOrder.Id,
|
||||
DisplayOrder: newDisplayOrder.DisplayOrder,
|
||||
}
|
||||
|
||||
explorers[i] = explorer
|
||||
}
|
||||
|
||||
err = a.insightsExploreres.ModifyInsightsExplorerDisplayOrders(c, uid, explorers)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerMoveHandler] failed to move insights explorers for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerMoveHandler] user \"uid:%d\" has moved insights explorers", uid)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerDeleteHandler deletes an existed insights explorer by request parameters for current user
|
||||
func (a *InsightsExplorersApi) InsightsExplorerDeleteHandler(c *core.WebContext) (any, *errs.Error) {
|
||||
var explorerDeleteReq models.InsightsExplorerDeleteRequest
|
||||
err := c.ShouldBindJSON(&explorerDeleteReq)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf(c, "[explorers.InsightsExplorerDeleteHandler] parse request failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid := c.GetCurrentUid()
|
||||
err = a.insightsExploreres.DeleteInsightsExplorer(c, uid, explorerDeleteReq.Id)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(c, "[explorers.InsightsExplorerDeleteHandler] failed to delete insights explorer \"id:%d\" for user \"uid:%d\", because %s", explorerDeleteReq.Id, uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.Infof(c, "[explorers.InsightsExplorerDeleteHandler] user \"uid:%d\" has deleted insights explorer \"id:%d\"", uid, explorerDeleteReq.Id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *InsightsExplorersApi) createNewInsightsExplorerModel(uid int64, explorerCreateReq *models.InsightsExplorerCreateRequest, order int32) (*models.InsightsExplorer, error) {
|
||||
data, err := json.Marshal(explorerCreateReq.Data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.InsightsExplorer{
|
||||
Uid: uid,
|
||||
Name: explorerCreateReq.Name,
|
||||
Data: string(data),
|
||||
DisplayOrder: order,
|
||||
}, nil
|
||||
}
|
||||
@@ -43,6 +43,7 @@ const (
|
||||
NormalSubcategoryLargeLanguageModel = 15
|
||||
NormalSubcategoryUserExternalAuth = 16
|
||||
NormalSubcategoryOAuth2 = 17
|
||||
NormalSubcategoryInsightsExplorer = 18
|
||||
)
|
||||
|
||||
// Error represents the specific error returned to user
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Error codes related to insights explorers
|
||||
var (
|
||||
ErrInsightsExplorerIdInvalid = NewNormalError(NormalSubcategoryInsightsExplorer, 0, http.StatusBadRequest, "explorer id is invalid")
|
||||
ErrInsightsExplorerNotFound = NewNormalError(NormalSubcategoryInsightsExplorer, 1, http.StatusBadRequest, "explorer not found")
|
||||
ErrInsightsExplorerDataInvalid = NewNormalError(NormalSubcategoryInsightsExplorer, 2, http.StatusBadRequest, "explorer data is invalid")
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
package models
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// InsightsExplorer represents a saved insights explorer configuration
|
||||
type InsightsExplorer struct {
|
||||
ExplorerId int64 `xorm:"PK"`
|
||||
Uid int64 `xorm:"INDEX(IDX_insights_explorer_uid_deleted_order) NOT NULL"`
|
||||
Deleted bool `xorm:"INDEX(IDX_insights_explorer_uid_deleted_order) NOT NULL"`
|
||||
Name string `xorm:"VARCHAR(64) NOT NULL"`
|
||||
DisplayOrder int32 `xorm:"INDEX(IDX_insights_explorer_uid_deleted_order) NOT NULL"`
|
||||
Data string `xorm:"MEDIUMBLOB"`
|
||||
Hidden bool `xorm:"NOT NULL"`
|
||||
CreatedUnixTime int64
|
||||
UpdatedUnixTime int64
|
||||
DeletedUnixTime int64
|
||||
}
|
||||
|
||||
// InsightsExplorerCreateRequest represents all parameters of insights explorer creation request
|
||||
type InsightsExplorerCreateRequest struct {
|
||||
Name string `json:"name" binding:"required,notBlank,max=64"`
|
||||
Data map[string]any `json:"data" binding:"required"`
|
||||
ClientSessionId string `json:"clientSessionId"`
|
||||
}
|
||||
|
||||
// InsightsExplorerModifyRequest represents all parameters of insights explorer modification request
|
||||
type InsightsExplorerModifyRequest struct {
|
||||
Id int64 `json:"id,string" binding:"required,min=0"`
|
||||
Name string `json:"name" binding:"required,notBlank,max=64"`
|
||||
Data map[string]any `json:"data" binding:"required"`
|
||||
Hidden bool `json:"hidden"`
|
||||
ClientSessionId string `json:"clientSessionId"`
|
||||
}
|
||||
|
||||
// InsightsExplorerGetRequest represents all parameters of insights explorer getting request
|
||||
type InsightsExplorerGetRequest struct {
|
||||
Id int64 `form:"id,string" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// InsightsExplorerHideRequest represents all parameters of insights explorer hiding request
|
||||
type InsightsExplorerHideRequest struct {
|
||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
|
||||
// InsightsExplorerMoveRequest represents all parameters of insights explorer moving request
|
||||
type InsightsExplorerMoveRequest struct {
|
||||
NewDisplayOrders []*InsightsExplorerNewDisplayOrderRequest `json:"newDisplayOrders" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// InsightsExplorerNewDisplayOrderRequest represents a data pair of id and display order
|
||||
type InsightsExplorerNewDisplayOrderRequest struct {
|
||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||
DisplayOrder int32 `json:"displayOrder"`
|
||||
}
|
||||
|
||||
// InsightsExplorerDeleteRequest represents all parameters of insights explorer deleting request
|
||||
type InsightsExplorerDeleteRequest struct {
|
||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// InsightsExplorerInfoResponse represents a view-object of insights explorer info
|
||||
type InsightsExplorerInfoResponse struct {
|
||||
Id int64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
DisplayOrder int32 `json:"displayOrder"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ToInsightsExplorerInfoResponse returns a view-object according to database model
|
||||
func (a *InsightsExplorer) ToInsightsExplorerInfoResponse() (*InsightsExplorerInfoResponse, error) {
|
||||
var data map[string]any = nil
|
||||
|
||||
if a.Data != "" {
|
||||
err := json.Unmarshal([]byte(a.Data), &data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &InsightsExplorerInfoResponse{
|
||||
Id: a.ExplorerId,
|
||||
Name: a.Name,
|
||||
DisplayOrder: a.DisplayOrder,
|
||||
Hidden: a.Hidden,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InsightsExplorerInfoResponseSlice represents the slice data structure of InsightsExplorerInfoResponse
|
||||
type InsightsExplorerInfoResponseSlice []*InsightsExplorerInfoResponse
|
||||
|
||||
// Len returns the count of items
|
||||
func (s InsightsExplorerInfoResponseSlice) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps two items
|
||||
func (s InsightsExplorerInfoResponseSlice) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less reports whether the first item is less than the second one
|
||||
func (s InsightsExplorerInfoResponseSlice) Less(i, j int) bool {
|
||||
return s[i].DisplayOrder < s[j].DisplayOrder
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInsightsExplorerInfoResponseSliceLess(t *testing.T) {
|
||||
var insightsExplorerRespSlice InsightsExplorerInfoResponseSlice
|
||||
insightsExplorerRespSlice = append(insightsExplorerRespSlice, &InsightsExplorerInfoResponse{
|
||||
Id: 1,
|
||||
DisplayOrder: 3,
|
||||
})
|
||||
insightsExplorerRespSlice = append(insightsExplorerRespSlice, &InsightsExplorerInfoResponse{
|
||||
Id: 2,
|
||||
DisplayOrder: 1,
|
||||
})
|
||||
insightsExplorerRespSlice = append(insightsExplorerRespSlice, &InsightsExplorerInfoResponse{
|
||||
Id: 3,
|
||||
DisplayOrder: 2,
|
||||
})
|
||||
|
||||
sort.Sort(insightsExplorerRespSlice)
|
||||
|
||||
assert.Equal(t, int64(2), insightsExplorerRespSlice[0].Id)
|
||||
assert.Equal(t, int64(3), insightsExplorerRespSlice[1].Id)
|
||||
assert.Equal(t, int64(1), insightsExplorerRespSlice[2].Id)
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/datastore"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/uuid"
|
||||
)
|
||||
|
||||
// InsightsExplorerService represents insights explorer service
|
||||
type InsightsExplorerService struct {
|
||||
ServiceUsingDB
|
||||
ServiceUsingUuid
|
||||
}
|
||||
|
||||
// Initialize a insights explorer service singleton instance
|
||||
var (
|
||||
InsightsExplorers = &InsightsExplorerService{
|
||||
ServiceUsingDB: ServiceUsingDB{
|
||||
container: datastore.Container,
|
||||
},
|
||||
ServiceUsingUuid: ServiceUsingUuid{
|
||||
container: uuid.Container,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// GetAllInsightsExplorerNamesByUid returns all insights explorer models of user without data
|
||||
func (s *InsightsExplorerService) GetAllInsightsExplorerNamesByUid(c core.Context, uid int64) ([]*models.InsightsExplorer, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
var explorers []*models.InsightsExplorer
|
||||
err := s.UserDataDB(uid).NewSession(c).Select("explorer_id, uid, name, display_order, hidden").Where("uid=? AND deleted=?", uid, false).Find(&explorers)
|
||||
|
||||
return explorers, err
|
||||
}
|
||||
|
||||
// GetInsightsExplorerByExplorerId returns a insights explorer model according to insights explorer id
|
||||
func (s *InsightsExplorerService) GetInsightsExplorerByExplorerId(c core.Context, uid int64, explorerId int64) (*models.InsightsExplorer, error) {
|
||||
if uid <= 0 {
|
||||
return nil, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
if explorerId <= 0 {
|
||||
return nil, errs.ErrInsightsExplorerIdInvalid
|
||||
}
|
||||
|
||||
explorer := &models.InsightsExplorer{}
|
||||
has, err := s.UserDataDB(uid).NewSession(c).ID(explorerId).Where("uid=? AND deleted=?", uid, false).Get(explorer)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, errs.ErrInsightsExplorerNotFound
|
||||
}
|
||||
|
||||
return explorer, nil
|
||||
}
|
||||
|
||||
// GetMaxDisplayOrder returns the max display order
|
||||
func (s *InsightsExplorerService) GetMaxDisplayOrder(c core.Context, uid int64) (int32, error) {
|
||||
if uid <= 0 {
|
||||
return 0, errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
explorer := &models.InsightsExplorer{}
|
||||
has, err := s.UserDataDB(uid).NewSession(c).Cols("uid", "deleted", "display_order").Where("uid=? AND deleted=?", uid, false).OrderBy("display_order desc").Limit(1).Get(explorer)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if has {
|
||||
return explorer.DisplayOrder, nil
|
||||
} else {
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreateInsightsExplorer saves a new insights explorer model to database
|
||||
func (s *InsightsExplorerService) CreateInsightsExplorer(c core.Context, explorer *models.InsightsExplorer) error {
|
||||
if explorer.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
explorer.ExplorerId = s.GenerateUuid(uuid.UUID_TYPE_EXPLORER)
|
||||
|
||||
if explorer.ExplorerId < 1 {
|
||||
return errs.ErrSystemIsBusy
|
||||
}
|
||||
|
||||
explorer.Deleted = false
|
||||
explorer.CreatedUnixTime = time.Now().Unix()
|
||||
explorer.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(explorer.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Insert(explorer)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ModifyInsightsExplorer saves an existed insights explorer model to database
|
||||
func (s *InsightsExplorerService) ModifyInsightsExplorer(c core.Context, explorer *models.InsightsExplorer) error {
|
||||
if explorer.Uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
explorer.UpdatedUnixTime = time.Now().Unix()
|
||||
|
||||
return s.UserDataDB(explorer.Uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
updatedRows, err := sess.ID(explorer.ExplorerId).Cols("name", "data", "updated_unix_time").Where("uid=? AND deleted=?", explorer.Uid, false).Update(explorer)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrInsightsExplorerNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// HideInsightsExplorer updates hidden field of given insights explorer ids
|
||||
func (s *InsightsExplorerService) HideInsightsExplorer(c core.Context, uid int64, ids []int64, hidden bool) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.InsightsExplorer{
|
||||
Hidden: hidden,
|
||||
UpdatedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
updatedRows, err := sess.Cols("hidden", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).In("explorer_id", ids).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrInsightsExplorerNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// ModifyInsightsExplorerDisplayOrders updates display order of given insights explorers
|
||||
func (s *InsightsExplorerService) ModifyInsightsExplorerDisplayOrders(c core.Context, uid int64, explorers []*models.InsightsExplorer) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
for i := 0; i < len(explorers); i++ {
|
||||
explorers[i].UpdatedUnixTime = time.Now().Unix()
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
for i := 0; i < len(explorers); i++ {
|
||||
explorer := explorers[i]
|
||||
updatedRows, err := sess.ID(explorer.ExplorerId).Cols("display_order", "updated_unix_time").Where("uid=? AND deleted=?", uid, false).Update(explorer)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if updatedRows < 1 {
|
||||
return errs.ErrInsightsExplorerNotFound
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteInsightsExplorer deletes an existed insights explorer from database
|
||||
func (s *InsightsExplorerService) DeleteInsightsExplorer(c core.Context, uid int64, explorerId int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.InsightsExplorer{
|
||||
Deleted: true,
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
deletedRows, err := sess.ID(explorerId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if deletedRows < 1 {
|
||||
return errs.ErrInsightsExplorerNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAllInsightsExplorers deletes all existed insights explorers from database
|
||||
func (s *InsightsExplorerService) DeleteAllInsightsExplorers(c core.Context, uid int64) error {
|
||||
if uid <= 0 {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
updateModel := &models.InsightsExplorer{
|
||||
Deleted: true,
|
||||
DeletedUnixTime: now,
|
||||
}
|
||||
|
||||
return s.UserDataDB(uid).DoTransaction(c, func(sess *xorm.Session) error {
|
||||
_, err := sess.Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=?", uid, false).Update(updateModel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -14,4 +14,5 @@ const (
|
||||
UUID_TYPE_TAG_INDEX UuidType = 6
|
||||
UUID_TYPE_TEMPLATE UuidType = 7
|
||||
UUID_TYPE_PICTURE UuidType = 8
|
||||
UUID_TYPE_EXPLORER UuidType = 9
|
||||
)
|
||||
|
||||
@@ -117,6 +117,14 @@ import type {
|
||||
TransactionTemplateDeleteRequest,
|
||||
TransactionTemplateInfoResponse
|
||||
} from '@/models/transaction_template.ts';
|
||||
import type {
|
||||
InsightsExplorerCreateRequest,
|
||||
InsightsExplorerModifyRequest,
|
||||
InsightsExplorerHideRequest,
|
||||
InsightsExplorerMoveRequest,
|
||||
InsightsExplorerDeleteRequest,
|
||||
InsightsExplorerInfoResponse,
|
||||
} from '@/models/explorer.ts';
|
||||
import type {
|
||||
TokenGenerateAPIRequest,
|
||||
TokenGenerateMCPRequest,
|
||||
@@ -742,6 +750,27 @@ export default {
|
||||
deleteTransactionTemplate: (req: TransactionTemplateDeleteRequest): ApiResponsePromise<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/transaction/templates/delete.json', req);
|
||||
},
|
||||
getAllInsightsExplorers: (): ApiResponsePromise<InsightsExplorerInfoResponse[]> => {
|
||||
return axios.get<ApiResponse<InsightsExplorerInfoResponse[]>>('v1/insights/explorers/list.json');
|
||||
},
|
||||
getInsightsExplorer: ({ id }: { id: string }): ApiResponsePromise<InsightsExplorerInfoResponse> => {
|
||||
return axios.get<ApiResponse<InsightsExplorerInfoResponse>>('v1/insights/explorers/get.json?id=' + id);
|
||||
},
|
||||
addInsightsExplorer: (req: InsightsExplorerCreateRequest): ApiResponsePromise<InsightsExplorerInfoResponse> => {
|
||||
return axios.post<ApiResponse<InsightsExplorerInfoResponse>>('v1/insights/explorers/add.json', req);
|
||||
},
|
||||
modifyInsightsExplorer: (req: InsightsExplorerModifyRequest): ApiResponsePromise<InsightsExplorerInfoResponse> => {
|
||||
return axios.post<ApiResponse<InsightsExplorerInfoResponse>>('v1/insights/explorers/modify.json', req);
|
||||
},
|
||||
hideInsightsExplorer: (req: InsightsExplorerHideRequest): ApiResponsePromise<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/insights/explorers/hide.json', req);
|
||||
},
|
||||
moveInsightsExplorer: (req: InsightsExplorerMoveRequest): ApiResponsePromise<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/insights/explorers/move.json', req);
|
||||
},
|
||||
deleteInsightsExplorer: (req: InsightsExplorerDeleteRequest): ApiResponsePromise<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/insights/explorers/delete.json', req);
|
||||
},
|
||||
recognizeReceiptImage: ({ imageFile, cancelableUuid }: { imageFile: File, cancelableUuid?: string }): ApiResponsePromise<RecognizedReceiptImageResponse> => {
|
||||
return axios.postForm<ApiResponse<RecognizedReceiptImageResponse>>('v1/llm/transactions/recognize_receipt_image.json', {
|
||||
image: imageFile
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Abfrageelemente dürfen nicht leer sein",
|
||||
"query items too much": "Zu viele Abfrageelemente",
|
||||
"query items have invalid item": "Ungültiges Element in Abfrageelementen",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "There are no query items",
|
||||
"query items too much": "There are too many query items",
|
||||
"query items have invalid item": "There is invalid item in query items",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "There are no query items",
|
||||
"query items too much": "There are too many query items",
|
||||
"query items have invalid item": "Hay un elemento no válido en los elementos de consulta",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Il n'y a pas d'éléments de requête",
|
||||
"query items too much": "Il y a trop d'éléments de requête",
|
||||
"query items have invalid item": "Il y a un élément invalide dans les éléments de requête",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Non ci sono elementi di query",
|
||||
"query items too much": "Ci sono troppi elementi di query",
|
||||
"query items have invalid item": "C'è un elemento non valido negli elementi di query",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "クエリ項目がありません",
|
||||
"query items too much": "クエリ項目が多すぎます",
|
||||
"query items have invalid item": "クエリ項目に無効な項目があります",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "OAuth 2.0 ಟೋಕನ್ ಅಮಾನ್ಯವಾಗಿದೆ",
|
||||
"cannot retrieve user info from oauth2 provider": "OAuth 2.0 ಪೂರೈಕೆದಾರರಿಂದ ಬಳಕೆದಾರ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 ಬಳಕೆದಾರ ಈಗಾಗಲೇ ಇನ್ನೊಬ್ಬ ಬಳಕೆದಾರನಿಗೆ ಬೌಂಡ್ ಆಗಿದ್ದಾನೆ",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "ವಿಚರಣೆ ಐಟಂಗಳಿಲ್ಲ",
|
||||
"query items too much": "ವಿಚರಣೆ ಐಟಂಗಳ ಸಂಖ್ಯೆ ಹೆಚ್ಚು",
|
||||
"query items have invalid item": "ವಿಚರಣೆ ಐಟಂಗಳಲ್ಲಿ ಅಮಾನ್ಯ ಐಟಂ ಇದೆ",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "쿼리 항목이 비어 있을 수 없습니다.",
|
||||
"query items too much": "쿼리 항목이 너무 많습니다.",
|
||||
"query items have invalid item": "쿼리 항목에 유효하지 않은 항목이 있습니다.",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Geen zoekitems opgegeven",
|
||||
"query items too much": "Te veel zoekitems",
|
||||
"query items have invalid item": "Ongeldig item in zoekitems",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Não há itens de consulta",
|
||||
"query items too much": "Há muitos itens de consulta",
|
||||
"query items have invalid item": "Há item inválido nos itens de consulta",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Нет элементов запроса",
|
||||
"query items too much": "Слишком много элементов запроса",
|
||||
"query items have invalid item": "В элементах запроса присутствует недопустимый элемент",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Neveljaven žeton OAuth 2.0",
|
||||
"cannot retrieve user info from oauth2 provider": "Ni mogoče pridobiti podatkov o uporabniku od ponudnika OAuth 2.0",
|
||||
"oauth2 user already bound to another user": "Uporabnik OAuth 2.0 je že povezan z drugim uporabnikom",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Poizvedbeni elementi ne morejo biti prazni",
|
||||
"query items too much": "Preveč poizvedbenih elementov",
|
||||
"query items have invalid item": "Med poizvedbenimi elementi je neveljaven element",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Poizvedba",
|
||||
"Data Table": "Podatkovna tabela",
|
||||
"Chart": "Grafikon",
|
||||
"New Exploration": "Novo raziskovanje",
|
||||
"New Explorer": "Novo raziskovanje",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Dodaj poizvedbo",
|
||||
"Remove Query": "Odstrani poizvedbo",
|
||||
"Modify Query Name": "Spremeni ime poizvedbe",
|
||||
"Add Condition": "Dodaj pogoj",
|
||||
"Remove Condition": "Odstrani pogoj",
|
||||
"No conditions defined. All transactions will match.": "Ni določenih pogojev. Prikazane bodo vse transakcije.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Urednik",
|
||||
"Expression": "Izraz",
|
||||
"Failed to generate expression": "Izraza ni bilo mogoče ustvariti",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "ไม่มีรายการสำหรับค้นหา",
|
||||
"query items too much": "รายการค้นหามากเกินไป",
|
||||
"query items have invalid item": "มีรายการไม่ถูกต้องในรายการค้นหา",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Geçersiz OAuth 2.0 jetonu",
|
||||
"cannot retrieve user info from oauth2 provider": "OAuth 2.0 sağlayıcısından kullanıcı bilgisi alınamıyor",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 kullanıcısı zaten başka bir kullanıcıya bağlı",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Sorgu öğeleri boş olamaz",
|
||||
"query items too much": "Çok fazla sorgu öğesi var",
|
||||
"query items have invalid item": "Sorgu öğelerinde geçersiz öğe var",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Елементи запиту не можуть бути порожніми",
|
||||
"query items too much": "Занадто багато елементів запиту",
|
||||
"query items have invalid item": "Запит містить недійсний елемент",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
+24
-1
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "Invalid OAuth 2.0 token",
|
||||
"cannot retrieve user info from oauth2 provider": "Cannot retrieve user info from OAuth 2.0 provider",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 user is already bound to another user",
|
||||
"explorer id is invalid": "Explorer ID is invalid",
|
||||
"explorer not found": "Explorer is not found",
|
||||
"explorer data is invalid": "Explorer data is invalid",
|
||||
"query items cannot be blank": "Không có mục truy vấn",
|
||||
"query items too much": "Có quá nhiều mục truy vấn",
|
||||
"query items have invalid item": "Có mục không hợp lệ trong các mục truy vấn",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "Query",
|
||||
"Data Table": "Data Table",
|
||||
"Chart": "Chart",
|
||||
"New Exploration": "New Exploration",
|
||||
"New Explorer": "New Explorer",
|
||||
"Untitled Explorer": "Untitled Explorer",
|
||||
"More Explorer": "More Explorer",
|
||||
"Save Explorer": "Save Explorer",
|
||||
"Save As New Explorer": "Save As New Explorer",
|
||||
"Rename Explorer": "Rename Explorer",
|
||||
"Delete Explorer": "Delete Explorer",
|
||||
"Explorer Name": "Explorer Name",
|
||||
"Add Query": "Add Query",
|
||||
"Remove Query": "Remove Query",
|
||||
"Modify Query Name": "Modify Query Name",
|
||||
"Add Condition": "Add Condition",
|
||||
"Remove Condition": "Remove Condition",
|
||||
"No conditions defined. All transactions will match.": "No conditions defined. All transactions will match.",
|
||||
"Unable to retrieve explorer list": "Unable to retrieve explorer list",
|
||||
"Explorer list is up to date": "Explorer list is up to date",
|
||||
"Explorer list has been updated": "Explorer list has been updated",
|
||||
"Unable to retrieve explorer": "Unable to retrieve explorer",
|
||||
"Unable to add explorer": "Unable to add explorer",
|
||||
"Unable to save explorer": "Unable to save explorer",
|
||||
"Unable to move explorer": "Unable to move explorer",
|
||||
"Unable to hide this explorer": "Unable to hide this explorer",
|
||||
"Unable to unhide this explorer": "Unable to unhide this explorer",
|
||||
"Are you sure you want to delete this explorer?": "Are you sure you want to delete this explorer?",
|
||||
"Unable to delete this explorer": "Unable to delete this explorer",
|
||||
"Show Hidden Explorers": "Show Hidden Explorers",
|
||||
"Hide Hidden Explorers": "Hide Hidden Explorers",
|
||||
"Editor": "Editor",
|
||||
"Expression": "Expression",
|
||||
"Failed to generate expression": "Failed to generate expression",
|
||||
|
||||
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "无效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth2 provider": "无法从 OAuth 2.0 提供者获取用户信息",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 用户已经绑定到另一个用户",
|
||||
"explorer id is invalid": "探索ID无效",
|
||||
"explorer not found": "探索不存在",
|
||||
"explorer data is invalid": "探索数据无效",
|
||||
"query items cannot be blank": "请求项目不能为空",
|
||||
"query items too much": "请求项目过多",
|
||||
"query items have invalid item": "请求项目中有非法项目",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "查询",
|
||||
"Data Table": "数据表格",
|
||||
"Chart": "图表",
|
||||
"New Exploration": "新的探索",
|
||||
"New Explorer": "新的探索",
|
||||
"Untitled Explorer": "未命名的探索",
|
||||
"More Explorer": "更多探索",
|
||||
"Save Explorer": "保存探索",
|
||||
"Save As New Explorer": "另存为新的探索",
|
||||
"Rename Explorer": "重命名探索",
|
||||
"Delete Explorer": "删除探索",
|
||||
"Explorer Name": "探索名称",
|
||||
"Add Query": "添加查询",
|
||||
"Remove Query": "移除查询",
|
||||
"Modify Query Name": "修改查询名称",
|
||||
"Add Condition": "添加条件",
|
||||
"Remove Condition": "移除条件",
|
||||
"No conditions defined. All transactions will match.": "没有定义条件。所有交易都会匹配。",
|
||||
"Unable to retrieve explorer list": "无法获取探索列表",
|
||||
"Explorer list is up to date": "探索列表已是最新",
|
||||
"Explorer list has been updated": "探索列表已更新",
|
||||
"Unable to retrieve explorer": "无法获取探索",
|
||||
"Unable to add explorer": "无法添加探索",
|
||||
"Unable to save explorer": "无法保存探索",
|
||||
"Unable to move explorer": "无法移动探索",
|
||||
"Unable to hide this explorer": "无法隐藏该探索",
|
||||
"Unable to unhide this explorer": "无法取消隐藏该探索",
|
||||
"Are you sure you want to delete this explorer?": "您确定要删除该探索?",
|
||||
"Unable to delete this explorer": "无法删除该探索",
|
||||
"Show Hidden Explorers": "显示隐藏的探索",
|
||||
"Hide Hidden Explorers": "不显示隐藏的探索",
|
||||
"Editor": "编辑器",
|
||||
"Expression": "表达式",
|
||||
"Failed to generate expression": "生成表达式失败",
|
||||
|
||||
@@ -1270,6 +1270,9 @@
|
||||
"invalid oauth2 token": "無效的 OAuth 2.0 令牌",
|
||||
"cannot retrieve user info from oauth2 provider": "無法從 OAuth 2.0 提供者獲取使用者資訊",
|
||||
"oauth2 user already bound to another user": "OAuth 2.0 使用者已綁定到另一個使用者",
|
||||
"explorer id is invalid": "探索ID無效",
|
||||
"explorer not found": "探索不存在",
|
||||
"explorer data is invalid": "探索資料無效",
|
||||
"query items cannot be blank": "查詢項目不能為空",
|
||||
"query items too much": "查詢項目過多",
|
||||
"query items have invalid item": "查詢項目中有非法項目",
|
||||
@@ -1709,13 +1712,33 @@
|
||||
"Query": "查詢",
|
||||
"Data Table": "資料表格",
|
||||
"Chart": "圖表",
|
||||
"New Exploration": "新的探索",
|
||||
"New Explorer": "新的探索",
|
||||
"Untitled Explorer": "無標題的探索",
|
||||
"More Explorer": "更多探索",
|
||||
"Save Explorer": "儲存探索",
|
||||
"Save As New Explorer": "另存新探索",
|
||||
"Rename Explorer": "重新命名探索",
|
||||
"Delete Explorer": "刪除探索",
|
||||
"Explorer Name": "探索名稱",
|
||||
"Add Query": "新增查詢",
|
||||
"Remove Query": "移除查詢",
|
||||
"Modify Query Name": "修改查詢名稱",
|
||||
"Add Condition": "新增條件",
|
||||
"Remove Condition": "移除條件",
|
||||
"No conditions defined. All transactions will match.": "沒有定義條件。所有交易都符合。",
|
||||
"Unable to retrieve explorer list": "無法取得探索清單",
|
||||
"Explorer list is up to date": "探索清單已是最新",
|
||||
"Explorer list has been updated": "探索清單已更新",
|
||||
"Unable to retrieve explorer": "無法取得探索",
|
||||
"Unable to add explorer": "無法新增探索",
|
||||
"Unable to save explorer": "無法儲存探索",
|
||||
"Unable to move explorer": "無法移動探索",
|
||||
"Unable to hide this explorer": "無法隱藏此探索",
|
||||
"Unable to unhide this explorer": "無法取消隱藏此探索",
|
||||
"Are you sure you want to delete this explorer?": "您確定要刪除此探索?",
|
||||
"Unable to delete this explorer": "無法刪除此探索",
|
||||
"Show Hidden Explorers": "顯示隱藏的探索",
|
||||
"Hide Hidden Explorers": "不顯示隱藏的探索",
|
||||
"Editor": "編輯器",
|
||||
"Expression": "表達式",
|
||||
"Failed to generate expression": "產生表達式失敗",
|
||||
|
||||
+233
-1
@@ -1,13 +1,21 @@
|
||||
import { type PartialRecord, itemAndIndex, keysIfValueEquals } from '@/core/base.ts';
|
||||
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
||||
import { AccountType } from '@/core/account.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { ChartSortingType } from '@/core/statistics.ts';
|
||||
import {
|
||||
TransactionExplorerConditionRelation,
|
||||
TransactionExplorerConditionRelationPriority,
|
||||
TransactionExplorerConditionFieldType,
|
||||
TransactionExplorerConditionField,
|
||||
TransactionExplorerConditionOperatorType,
|
||||
TransactionExplorerConditionOperator
|
||||
TransactionExplorerConditionOperator,
|
||||
TransactionExplorerChartTypeValue,
|
||||
TransactionExplorerChartType,
|
||||
TransactionExplorerDataDimensionType,
|
||||
TransactionExplorerDataDimension,
|
||||
TransactionExplorerValueMetricType,
|
||||
TransactionExplorerValueMetric
|
||||
} from '@/core/explorer.ts';
|
||||
|
||||
import { Account } from '@/models/account.ts';
|
||||
@@ -15,6 +23,230 @@ import { TransactionCategory } from '@/models/transaction_category.ts';
|
||||
import { TransactionTag } from '@/models/transaction_tag.ts';
|
||||
import { type TransactionInsightDataItem } from '@/models/transaction.ts';
|
||||
|
||||
export class InsightsExplorerBasicInfo implements InsightsExplorerInfoResponse {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public displayOrder: number;
|
||||
public hidden: boolean;
|
||||
public data: Record<string, string | number | string[]> = {};
|
||||
|
||||
private constructor(id: string, name: string, displayOrder: number, hidden: boolean) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.displayOrder = displayOrder;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
|
||||
public static of(explorerResponse: InsightsExplorerInfoResponse): InsightsExplorerBasicInfo {
|
||||
return new InsightsExplorerBasicInfo(
|
||||
explorerResponse.id,
|
||||
explorerResponse.name,
|
||||
explorerResponse.displayOrder,
|
||||
explorerResponse.hidden
|
||||
);
|
||||
}
|
||||
|
||||
public static ofMulti(explorerResponses: InsightsExplorerInfoResponse[]): InsightsExplorerBasicInfo[] {
|
||||
const explorers: InsightsExplorerBasicInfo[] = [];
|
||||
|
||||
for (const explorerResponse of explorerResponses) {
|
||||
explorers.push(InsightsExplorerBasicInfo.of(explorerResponse));
|
||||
}
|
||||
|
||||
return explorers;
|
||||
}
|
||||
}
|
||||
|
||||
export class InsightsExplorer implements InsightsExplorerInfoResponse {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public displayOrder: number;
|
||||
public hidden: boolean;
|
||||
public queries: TransactionExplorerQuery[];
|
||||
public timezoneUsedForDateRange: number;
|
||||
public chartType: TransactionExplorerChartTypeValue;
|
||||
public categoryDimension: TransactionExplorerDataDimensionType;
|
||||
public seriesDimension: TransactionExplorerDataDimensionType;
|
||||
public valueMetric: TransactionExplorerValueMetricType;
|
||||
public chartSortingType: number;
|
||||
|
||||
public static readonly Default: InsightsExplorer = new InsightsExplorer(
|
||||
'',
|
||||
'',
|
||||
0,
|
||||
false,
|
||||
[],
|
||||
TimezoneTypeForStatistics.Default.type,
|
||||
TransactionExplorerChartType.Default.value,
|
||||
TransactionExplorerDataDimension.CategoryDimensionDefault.value,
|
||||
TransactionExplorerDataDimension.SeriesDimensionDefault.value,
|
||||
TransactionExplorerValueMetric.Default.value,
|
||||
ChartSortingType.Default.type
|
||||
);
|
||||
|
||||
private constructor(id: string, name: string, displayOrder: number, hidden: boolean, queries: TransactionExplorerQuery[], timezoneUsedForDateRange: number, chartType: TransactionExplorerChartTypeValue, categoryDimension: TransactionExplorerDataDimensionType, seriesDimension: TransactionExplorerDataDimensionType, valueMetric: TransactionExplorerValueMetricType, chartSortingType: number) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.displayOrder = displayOrder;
|
||||
this.hidden = hidden;
|
||||
this.queries = queries;
|
||||
this.timezoneUsedForDateRange = timezoneUsedForDateRange;
|
||||
this.chartType = chartType;
|
||||
this.categoryDimension = categoryDimension;
|
||||
this.seriesDimension = seriesDimension;
|
||||
this.valueMetric = valueMetric;
|
||||
this.chartSortingType = chartSortingType;
|
||||
}
|
||||
|
||||
public get data(): Record<string, string | number | string[]> {
|
||||
return {
|
||||
queries: this.queries.map(q => q.toJson()),
|
||||
timezoneUsedForDateRange: this.timezoneUsedForDateRange,
|
||||
chartType: this.chartType,
|
||||
categoryDimension: this.categoryDimension,
|
||||
seriesDimension: this.seriesDimension,
|
||||
valueMetric: this.valueMetric,
|
||||
chartSortingType: this.chartSortingType
|
||||
};
|
||||
}
|
||||
|
||||
public toCreateRequest(clientSessionId: string): InsightsExplorerCreateRequest {
|
||||
return {
|
||||
name: this.name,
|
||||
data: this.data,
|
||||
clientSessionId: clientSessionId
|
||||
};
|
||||
}
|
||||
|
||||
public toModifyRequest(): InsightsExplorerModifyRequest {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
data: this.data,
|
||||
hidden: this.hidden
|
||||
};
|
||||
}
|
||||
|
||||
public static of(explorerResponse: InsightsExplorerInfoResponse): InsightsExplorer {
|
||||
const data = explorerResponse.data;
|
||||
const queries: TransactionExplorerQuery[] = [];
|
||||
let timezoneUsedForDateRange = InsightsExplorer.Default.timezoneUsedForDateRange;
|
||||
let chartType = InsightsExplorer.Default.chartType;
|
||||
let categoryDimension = InsightsExplorer.Default.categoryDimension;
|
||||
let seriesDimension = InsightsExplorer.Default.seriesDimension;
|
||||
let valueMetric = InsightsExplorer.Default.valueMetric;
|
||||
let chartSortingType = InsightsExplorer.Default.chartSortingType;
|
||||
|
||||
if (data) {
|
||||
if (Array.isArray(data['queries'])) {
|
||||
const textualQueries = data['queries'] as string[];
|
||||
|
||||
for (const textualQuery of textualQueries) {
|
||||
const query = TransactionExplorerQuery.parse(textualQuery);
|
||||
|
||||
if (query) {
|
||||
queries.push(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data['timezoneUsedForDateRange'] === 'number') {
|
||||
timezoneUsedForDateRange = data['timezoneUsedForDateRange'] as number;
|
||||
}
|
||||
|
||||
if (typeof data['chartType'] === 'string') {
|
||||
chartType = data['chartType'] as TransactionExplorerChartTypeValue;
|
||||
}
|
||||
|
||||
if (typeof data['categoryDimension'] === 'string') {
|
||||
categoryDimension = data['categoryDimension'] as TransactionExplorerDataDimensionType;
|
||||
}
|
||||
|
||||
if (typeof data['seriesDimension'] === 'string') {
|
||||
seriesDimension = data['seriesDimension'] as TransactionExplorerDataDimensionType;
|
||||
}
|
||||
|
||||
if (typeof data['valueMetric'] === 'string') {
|
||||
valueMetric = data['valueMetric'] as TransactionExplorerValueMetricType;
|
||||
}
|
||||
|
||||
if (typeof data['chartSortingType'] === 'number') {
|
||||
chartSortingType = data['chartSortingType'] as number;
|
||||
}
|
||||
}
|
||||
|
||||
return new InsightsExplorer(
|
||||
explorerResponse.id,
|
||||
explorerResponse.name,
|
||||
explorerResponse.displayOrder,
|
||||
explorerResponse.hidden,
|
||||
queries,
|
||||
timezoneUsedForDateRange,
|
||||
chartType,
|
||||
categoryDimension,
|
||||
seriesDimension,
|
||||
valueMetric,
|
||||
chartSortingType
|
||||
);
|
||||
}
|
||||
|
||||
public static createNewExplorer(name?: string): InsightsExplorer {
|
||||
return new InsightsExplorer(
|
||||
'',
|
||||
name || '',
|
||||
0,
|
||||
false,
|
||||
[TransactionExplorerQuery.create()],
|
||||
InsightsExplorer.Default.timezoneUsedForDateRange,
|
||||
InsightsExplorer.Default.chartType,
|
||||
InsightsExplorer.Default.categoryDimension,
|
||||
InsightsExplorer.Default.seriesDimension,
|
||||
InsightsExplorer.Default.valueMetric,
|
||||
InsightsExplorer.Default.chartSortingType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface InsightsExplorerCreateRequest {
|
||||
readonly name: string;
|
||||
readonly data: Record<string, string | number | string[]>;
|
||||
readonly clientSessionId?: string;
|
||||
}
|
||||
|
||||
export interface InsightsExplorerModifyRequest {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly data: Record<string, string | number | string[]>;
|
||||
readonly hidden: boolean;
|
||||
readonly clientSessionId?: string;
|
||||
}
|
||||
|
||||
export interface InsightsExplorerHideRequest {
|
||||
readonly id: string;
|
||||
readonly hidden: boolean;
|
||||
}
|
||||
|
||||
export interface InsightsExplorerMoveRequest {
|
||||
readonly newDisplayOrders: InsightsExplorerNewDisplayOrderRequest[];
|
||||
}
|
||||
|
||||
export interface InsightsExplorerNewDisplayOrderRequest {
|
||||
readonly id: string;
|
||||
readonly displayOrder: number;
|
||||
}
|
||||
|
||||
export interface InsightsExplorerDeleteRequest {
|
||||
readonly id: string;
|
||||
}
|
||||
|
||||
export interface InsightsExplorerInfoResponse {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly displayOrder: number;
|
||||
readonly hidden: boolean;
|
||||
readonly data: Record<string, string | number | string[]>;
|
||||
}
|
||||
|
||||
interface ExpressionNode {
|
||||
textualExpression: string;
|
||||
operator?: TransactionExplorerConditionRelation;
|
||||
|
||||
+367
-97
@@ -8,19 +8,15 @@ import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
||||
import { useTransactionTagsStore } from './transactionTag.ts';
|
||||
import { useExchangeRatesStore } from './exchangeRates.ts';
|
||||
|
||||
import { itemAndIndex, keys, values } from '@/core/base.ts';
|
||||
import { type BeforeResolveFunction, itemAndIndex, keys, values } from '@/core/base.ts';
|
||||
import { AmountFilterType } from '@/core/numeral.ts';
|
||||
import { DateRangeScene, DateRange } from '@/core/datetime.ts';
|
||||
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
||||
import { AccountCategory } from '@/core/account.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { ChartSortingType } from '@/core/statistics.ts';
|
||||
import {
|
||||
TransactionExplorerChartTypeValue,
|
||||
TransactionExplorerChartType,
|
||||
TransactionExplorerDataDimensionType,
|
||||
TransactionExplorerDataDimension,
|
||||
TransactionExplorerValueMetricType,
|
||||
TransactionExplorerValueMetric,
|
||||
DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE
|
||||
} from '@/core/explorer.ts';
|
||||
@@ -34,7 +30,10 @@ import {
|
||||
type TransactionInsightDataItem
|
||||
} from '@/models/transaction.ts';
|
||||
import {
|
||||
TransactionExplorerQuery
|
||||
type InsightsExplorerNewDisplayOrderRequest,
|
||||
type InsightsExplorerInfoResponse,
|
||||
InsightsExplorer,
|
||||
InsightsExplorerBasicInfo
|
||||
} from '@/models/explorer.ts';
|
||||
|
||||
import {
|
||||
@@ -49,7 +48,7 @@ import {
|
||||
getDateRangeByDateType,
|
||||
getFiscalYearFromUnixTime
|
||||
} from '@/lib/datetime.ts';
|
||||
import services from '@/lib/services.ts';
|
||||
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
||||
import logger from '@/lib/logger.ts';
|
||||
|
||||
export enum TransactionExplorerDimensionType {
|
||||
@@ -69,26 +68,12 @@ export interface TransactionExplorerPartialFilter {
|
||||
dateRangeType?: number;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
queryId?: string;
|
||||
timezoneUsedForDateRange?: number;
|
||||
chartType?: TransactionExplorerChartTypeValue;
|
||||
categoryDimension?: TransactionExplorerDataDimensionType;
|
||||
seriesDimension?: TransactionExplorerDataDimensionType;
|
||||
valueMetric?: TransactionExplorerValueMetricType;
|
||||
chartSortingType?: number;
|
||||
}
|
||||
|
||||
export interface TransactionExplorerFilter extends TransactionExplorerPartialFilter {
|
||||
dateRangeType: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
query: TransactionExplorerQuery[];
|
||||
timezoneUsedForDateRange: number;
|
||||
chartType: TransactionExplorerChartTypeValue;
|
||||
categoryDimension: TransactionExplorerDataDimensionType;
|
||||
seriesDimension: TransactionExplorerDataDimensionType;
|
||||
valueMetric: TransactionExplorerValueMetricType;
|
||||
chartSortingType: number;
|
||||
}
|
||||
|
||||
export interface CategoriedInfo {
|
||||
@@ -152,7 +137,7 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
}
|
||||
|
||||
if (dimension === TransactionExplorerDataDimension.None) {
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(transactionExplorerFilter.value.valueMetric);
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(currentInsightsExplorer.value.valueMetric);
|
||||
return {
|
||||
categoryName: valueMetric?.name ?? 'Unknown',
|
||||
categoryNameNeedI18n: true,
|
||||
@@ -429,22 +414,68 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
seriesedData.trasactions.push(transaction);
|
||||
}
|
||||
|
||||
function loadInsightsExplorerList(explorers: InsightsExplorerBasicInfo[]): void {
|
||||
allInsightsExplorerBasicInfos.value = explorers;
|
||||
allInsightsExplorerBasicInfosMap.value = {};
|
||||
|
||||
for (const explorer of explorers) {
|
||||
allInsightsExplorerBasicInfosMap.value[explorer.id] = explorer;
|
||||
}
|
||||
}
|
||||
|
||||
function addExplorerToInsightsExplorerList(explorer: InsightsExplorerBasicInfo): void {
|
||||
allInsightsExplorerBasicInfos.value.push(explorer);
|
||||
allInsightsExplorerBasicInfosMap.value[explorer.id] = explorer;
|
||||
}
|
||||
|
||||
function updateExplorerInInsightsExplorerList(currentExplorer: InsightsExplorerBasicInfo): void {
|
||||
for (const [explorer, index] of itemAndIndex(allInsightsExplorerBasicInfos.value)) {
|
||||
if (explorer.id === currentExplorer.id) {
|
||||
allInsightsExplorerBasicInfos.value.splice(index, 1, currentExplorer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
allInsightsExplorerBasicInfosMap.value[currentExplorer.id] = currentExplorer;
|
||||
}
|
||||
|
||||
function updateExplorerDisplayOrderInInsightsExplorerList({ from, to }: { from: number, to: number }): void {
|
||||
allInsightsExplorerBasicInfos.value.splice(to, 0, allInsightsExplorerBasicInfos.value.splice(from, 1)[0] as InsightsExplorer);
|
||||
}
|
||||
|
||||
function updateExplorerVisibilityInInsightsExplorerList({ explorer, hidden }: { explorer: InsightsExplorerBasicInfo, hidden: boolean }): void {
|
||||
if (allInsightsExplorerBasicInfosMap.value[explorer.id]) {
|
||||
allInsightsExplorerBasicInfosMap.value[explorer.id]!.hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
function removeExplorerFromInsightsExplorerList(currentExplorer: InsightsExplorerBasicInfo): void {
|
||||
for (const [insightsExplorer, index] of itemAndIndex(allInsightsExplorerBasicInfos.value)) {
|
||||
if (insightsExplorer.id === currentExplorer.id) {
|
||||
allInsightsExplorerBasicInfos.value.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allInsightsExplorerBasicInfosMap.value[currentExplorer.id]) {
|
||||
delete allInsightsExplorerBasicInfosMap.value[currentExplorer.id];
|
||||
}
|
||||
}
|
||||
|
||||
const transactionExplorerFilter = ref<TransactionExplorerFilter>({
|
||||
dateRangeType: DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
query: [],
|
||||
timezoneUsedForDateRange: TimezoneTypeForStatistics.Default.type,
|
||||
chartType: TransactionExplorerChartType.Default.value,
|
||||
categoryDimension: TransactionExplorerDataDimension.CategoryDimensionDefault.value,
|
||||
seriesDimension: TransactionExplorerDataDimension.SeriesDimensionDefault.value,
|
||||
valueMetric: TransactionExplorerValueMetric.Default.value,
|
||||
chartSortingType: ChartSortingType.Default.type
|
||||
endTime: 0
|
||||
});
|
||||
|
||||
const transactionExplorerAllData = ref<TransactionInfoResponse[]>([]);
|
||||
const transactionExplorerStateInvalid = ref<boolean>(true);
|
||||
|
||||
const allInsightsExplorerBasicInfos = ref<InsightsExplorerBasicInfo[]>([]);
|
||||
const allInsightsExplorerBasicInfosMap = ref<Record<string, InsightsExplorerBasicInfo>>({});
|
||||
const currentInsightsExplorer = ref<InsightsExplorer>(InsightsExplorer.createNewExplorer());
|
||||
const insightsExplorerListStateInvalid = ref<boolean>(true);
|
||||
|
||||
const allTransactions = computed<TransactionInsightDataItem[]>(() => {
|
||||
if (!transactionExplorerAllData.value || transactionExplorerAllData.value.length < 1) {
|
||||
return [];
|
||||
@@ -524,14 +555,14 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!transactionExplorerFilter.value.query || transactionExplorerFilter.value.query.length < 1) {
|
||||
if (!currentInsightsExplorer.value.queries || currentInsightsExplorer.value.queries.length < 1) {
|
||||
return allTransactions.value;
|
||||
}
|
||||
|
||||
const result: TransactionInsightDataItem[] = [];
|
||||
|
||||
for (const transaction of allTransactions.value) {
|
||||
for (const query of transactionExplorerFilter.value.query) {
|
||||
for (const query of currentInsightsExplorer.value.queries) {
|
||||
if (query.match(transaction)) {
|
||||
result.push(transaction);
|
||||
break;
|
||||
@@ -547,9 +578,9 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
return {};
|
||||
}
|
||||
|
||||
const chartType = TransactionExplorerChartType.valueOf(transactionExplorerFilter.value.chartType);
|
||||
const categoryDimension = TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.categoryDimension);
|
||||
const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault;
|
||||
const chartType = TransactionExplorerChartType.valueOf(currentInsightsExplorer.value.chartType);
|
||||
const categoryDimension = TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.categoryDimension);
|
||||
const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault;
|
||||
|
||||
if (!chartType || !categoryDimension || !seriesDimension) {
|
||||
return {};
|
||||
@@ -558,14 +589,14 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
const categoriedDataMap: Record<string, CategoriedTransactions> = {};
|
||||
|
||||
for (const transaction of allTransactions.value) {
|
||||
if (!transactionExplorerFilter.value.query || transactionExplorerFilter.value.query.length < 1) {
|
||||
addTransactionToCategoriedDataMap(transactionExplorerFilter.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, '', 0, transaction);
|
||||
if (!currentInsightsExplorer.value.queries || currentInsightsExplorer.value.queries.length < 1) {
|
||||
addTransactionToCategoriedDataMap(currentInsightsExplorer.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, '', 0, transaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [query, index] of itemAndIndex(transactionExplorerFilter.value.query)) {
|
||||
for (const [query, index] of itemAndIndex(currentInsightsExplorer.value.queries)) {
|
||||
if (query.match(transaction)) {
|
||||
addTransactionToCategoriedDataMap(transactionExplorerFilter.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, query.name, index, transaction);
|
||||
addTransactionToCategoriedDataMap(currentInsightsExplorer.value.timezoneUsedForDateRange, categoriedDataMap, categoryDimension, seriesDimension, query.name, index, transaction);
|
||||
|
||||
if (categoryDimension !== TransactionExplorerDataDimension.Query) {
|
||||
break;
|
||||
@@ -582,10 +613,10 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
return [];
|
||||
}
|
||||
|
||||
const chartType = TransactionExplorerChartType.valueOf(transactionExplorerFilter.value.chartType);
|
||||
const categoryDimension = TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.categoryDimension);
|
||||
const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(transactionExplorerFilter.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault;
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(transactionExplorerFilter.value.valueMetric);
|
||||
const chartType = TransactionExplorerChartType.valueOf(currentInsightsExplorer.value.chartType);
|
||||
const categoryDimension = TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.categoryDimension);
|
||||
const seriesDimension = chartType?.seriesDimensionRequired ? TransactionExplorerDataDimension.valueOf(currentInsightsExplorer.value.seriesDimension) : TransactionExplorerDataDimension.SeriesDimensionDefault;
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(currentInsightsExplorer.value.valueMetric);
|
||||
|
||||
if (!chartType || !categoryDimension || !seriesDimension || !valueMetric) {
|
||||
return [];
|
||||
@@ -699,18 +730,20 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
transactionExplorerStateInvalid.value = invalidState;
|
||||
}
|
||||
|
||||
function updateInsightsExplorerListInvalidState(invalidState: boolean): void {
|
||||
insightsExplorerListStateInvalid.value = invalidState;
|
||||
}
|
||||
|
||||
function updateCurrentInsightsExplorer(explorer: InsightsExplorer): void {
|
||||
currentInsightsExplorer.value = explorer;
|
||||
}
|
||||
|
||||
function resetTransactionExplorers(): void {
|
||||
transactionExplorerFilter.value.dateRangeType = DEFAULT_TRANSACTION_EXPLORER_DATE_RANGE.type;
|
||||
transactionExplorerFilter.value.startTime = 0;
|
||||
transactionExplorerFilter.value.endTime = 0;
|
||||
transactionExplorerFilter.value.query = [];
|
||||
transactionExplorerFilter.value.timezoneUsedForDateRange = TimezoneTypeForStatistics.Default.type;
|
||||
transactionExplorerFilter.value.chartType = TransactionExplorerChartType.Default.value;
|
||||
transactionExplorerFilter.value.categoryDimension = TransactionExplorerDataDimension.CategoryDimensionDefault.value;
|
||||
transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value;
|
||||
transactionExplorerFilter.value.valueMetric = TransactionExplorerValueMetric.Default.value;
|
||||
transactionExplorerFilter.value.chartSortingType = ChartSortingType.Default.type;
|
||||
transactionExplorerAllData.value = [];
|
||||
currentInsightsExplorer.value = InsightsExplorer.createNewExplorer();
|
||||
transactionExplorerStateInvalid.value = true;
|
||||
}
|
||||
|
||||
@@ -751,13 +784,7 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
}
|
||||
|
||||
if (resetQuery) {
|
||||
transactionExplorerFilter.value.query = [];
|
||||
transactionExplorerFilter.value.timezoneUsedForDateRange = TimezoneTypeForStatistics.Default.type;
|
||||
transactionExplorerFilter.value.chartType = TransactionExplorerChartType.Default.value;
|
||||
transactionExplorerFilter.value.categoryDimension = TransactionExplorerDataDimension.CategoryDimensionDefault.value;
|
||||
transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value;
|
||||
transactionExplorerFilter.value.valueMetric = TransactionExplorerValueMetric.Default.value;
|
||||
transactionExplorerFilter.value.chartSortingType = ChartSortingType.Default.type;
|
||||
currentInsightsExplorer.value = InsightsExplorer.createNewExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,49 +806,14 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.timezoneUsedForDateRange) && transactionExplorerFilter.value.timezoneUsedForDateRange !== filter.timezoneUsedForDateRange) {
|
||||
transactionExplorerFilter.value.timezoneUsedForDateRange = filter.timezoneUsedForDateRange;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.chartType) && transactionExplorerFilter.value.chartType !== filter.chartType) {
|
||||
transactionExplorerFilter.value.chartType = filter.chartType;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.categoryDimension) && transactionExplorerFilter.value.categoryDimension !== filter.categoryDimension) {
|
||||
transactionExplorerFilter.value.categoryDimension = filter.categoryDimension;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.seriesDimension) && transactionExplorerFilter.value.seriesDimension !== filter.seriesDimension) {
|
||||
transactionExplorerFilter.value.seriesDimension = filter.seriesDimension;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.valueMetric) && transactionExplorerFilter.value.valueMetric !== filter.valueMetric) {
|
||||
transactionExplorerFilter.value.valueMetric = filter.valueMetric;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (filter && isDefined(filter.chartSortingType) && transactionExplorerFilter.value.chartSortingType !== filter.chartSortingType) {
|
||||
transactionExplorerFilter.value.chartSortingType = filter.chartSortingType;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (transactionExplorerFilter.value.seriesDimension === transactionExplorerFilter.value.categoryDimension && transactionExplorerFilter.value.seriesDimension !== TransactionExplorerDataDimension.SeriesDimensionDefault.value) {
|
||||
transactionExplorerFilter.value.seriesDimension = TransactionExplorerDataDimension.SeriesDimensionDefault.value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function getTransactionExplorerPageParams(currentExplorationId: string, activeTab: string): string {
|
||||
function getTransactionExplorerPageParams(currentExplorerId: string, activeTab: string): string {
|
||||
const querys: string[] = [];
|
||||
|
||||
if (currentExplorationId) {
|
||||
querys.push('id=' + currentExplorationId);
|
||||
if (currentExplorerId) {
|
||||
querys.push('id=' + currentExplorerId);
|
||||
}
|
||||
|
||||
if (activeTab) {
|
||||
@@ -897,20 +889,298 @@ export const useExplorersStore = defineStore('explorers', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function loadAllInsightsExplorerBasicInfos({ force }: { force?: boolean }): Promise<InsightsExplorerBasicInfo[]> {
|
||||
if (!force && !insightsExplorerListStateInvalid.value) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(allInsightsExplorerBasicInfos.value);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getAllInsightsExplorers().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve explorer list' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (insightsExplorerListStateInvalid.value) {
|
||||
updateInsightsExplorerListInvalidState(false);
|
||||
}
|
||||
|
||||
const explorerBasicInfos = InsightsExplorerBasicInfo.ofMulti(data.result);
|
||||
|
||||
if (force && data.result && isEquals(allInsightsExplorerBasicInfos.value, explorerBasicInfos)) {
|
||||
reject({ message: 'Explorer list is up to date', isUpToDate: true });
|
||||
return;
|
||||
}
|
||||
|
||||
loadInsightsExplorerList(explorerBasicInfos);
|
||||
|
||||
resolve(explorerBasicInfos);
|
||||
}).catch(error => {
|
||||
if (force) {
|
||||
logger.error('failed to force load explorer list', error);
|
||||
} else {
|
||||
logger.error('failed to load explorer list', error);
|
||||
}
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to retrieve explorer list' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getInsightsExplorer({ explorerId }: { explorerId: string }): Promise<InsightsExplorer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getInsightsExplorer({
|
||||
id: explorerId
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve explorer' });
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionCategory = InsightsExplorer.of(data.result);
|
||||
|
||||
resolve(transactionCategory);
|
||||
}).catch(error => {
|
||||
logger.error('failed to load explorer info', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to retrieve explorer' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveInsightsExplorer({ explorer, saveAs, clientSessionId }: { explorer: InsightsExplorer, saveAs?: boolean, clientSessionId: string }): Promise<InsightsExplorer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promise: ApiResponsePromise<InsightsExplorerInfoResponse>;
|
||||
|
||||
if (!explorer.id || saveAs) {
|
||||
promise = services.addInsightsExplorer(explorer.toCreateRequest(clientSessionId));
|
||||
} else {
|
||||
promise = services.modifyInsightsExplorer(explorer.toModifyRequest());
|
||||
}
|
||||
|
||||
promise.then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (!explorer.id) {
|
||||
reject({ message: 'Unable to add explorer' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save explorer' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const explorerBasicInfo = InsightsExplorerBasicInfo.of(data.result);
|
||||
|
||||
if (!explorer.id || saveAs) {
|
||||
addExplorerToInsightsExplorerList(explorerBasicInfo);
|
||||
} else {
|
||||
updateExplorerInInsightsExplorerList(explorerBasicInfo);
|
||||
}
|
||||
|
||||
resolve(InsightsExplorer.of(data.result));
|
||||
}).catch(error => {
|
||||
logger.error('failed to save explorer', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (!explorer.id) {
|
||||
reject({ message: 'Unable to add explorer' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save explorer' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeInsightsExplorerDisplayOrder({ explorerId, from, to }: { explorerId: string, from: number, to: number }): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let currentExplorer: InsightsExplorerBasicInfo | null = null;
|
||||
|
||||
for (const insightsExplorer of allInsightsExplorerBasicInfos.value) {
|
||||
if (insightsExplorer.id === explorerId) {
|
||||
currentExplorer = insightsExplorer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentExplorer || !allInsightsExplorerBasicInfos.value[to]) {
|
||||
reject({ message: 'Unable to move explorer' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!insightsExplorerListStateInvalid.value) {
|
||||
updateInsightsExplorerListInvalidState(true);
|
||||
}
|
||||
|
||||
updateExplorerDisplayOrderInInsightsExplorerList({ from, to });
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function updateInsightsExplorerDisplayOrders(): Promise<boolean> {
|
||||
const newDisplayOrders: InsightsExplorerNewDisplayOrderRequest[] = [];
|
||||
|
||||
for (const [insightsExplorer, index] of itemAndIndex(allInsightsExplorerBasicInfos.value)) {
|
||||
newDisplayOrders.push({
|
||||
id: insightsExplorer.id,
|
||||
displayOrder: index + 1
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.moveInsightsExplorer({
|
||||
newDisplayOrders: newDisplayOrders
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to move explorer' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (insightsExplorerListStateInvalid.value) {
|
||||
updateInsightsExplorerListInvalidState(false);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save explorers display order', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to move explorer' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hideInsightsExplorer({ explorer, hidden }: { explorer: InsightsExplorer, hidden: boolean }): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.hideInsightsExplorer({
|
||||
id: explorer.id,
|
||||
hidden: hidden
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (hidden) {
|
||||
reject({ message: 'Unable to hide this explorer' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this explorer' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateExplorerVisibilityInInsightsExplorerList({ explorer: explorer, hidden });
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to change explorer visibility', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (hidden) {
|
||||
reject({ message: 'Unable to hide this explorer' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this explorer' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteInsightsExplorer({ explorer, beforeResolve }: { explorer: InsightsExplorer, beforeResolve?: BeforeResolveFunction }): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.deleteInsightsExplorer({
|
||||
id: explorer.id
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to delete this explorer' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeResolve) {
|
||||
beforeResolve(() => {
|
||||
removeExplorerFromInsightsExplorerList(explorer);
|
||||
});
|
||||
} else {
|
||||
removeExplorerFromInsightsExplorerList(explorer);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to delete explorer', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to delete this explorer' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// states
|
||||
transactionExplorerFilter: transactionExplorerFilter,
|
||||
transactionExplorerFilter,
|
||||
transactionExplorerStateInvalid,
|
||||
allInsightsExplorerBasicInfos,
|
||||
allInsightsExplorerBasicInfosMap,
|
||||
currentInsightsExplorer,
|
||||
insightsExplorerListStateInvalid,
|
||||
// computed
|
||||
filteredTransactions,
|
||||
categoriedTransactionExplorerData,
|
||||
// functions
|
||||
updateTransactionExplorerInvalidState,
|
||||
updateInsightsExplorerListInvalidState,
|
||||
updateCurrentInsightsExplorer,
|
||||
resetTransactionExplorers,
|
||||
initTransactionExplorerFilter,
|
||||
updateTransactionExplorerFilter,
|
||||
getTransactionExplorerPageParams,
|
||||
getTransactionListPageParams,
|
||||
loadAllTransactions
|
||||
loadAllTransactions,
|
||||
loadAllInsightsExplorerBasicInfos,
|
||||
getInsightsExplorer,
|
||||
saveInsightsExplorer,
|
||||
changeInsightsExplorerDisplayOrder,
|
||||
updateInsightsExplorerDisplayOrders,
|
||||
hideInsightsExplorer,
|
||||
deleteInsightsExplorer
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,24 +5,22 @@
|
||||
<v-layout>
|
||||
<v-navigation-drawer :permanent="alwaysShowNav" v-model="showNav">
|
||||
<div class="mx-6 my-4">
|
||||
<btn-vertical-group :disabled="loading" :buttons="allTabs" v-model="activeTab" />
|
||||
<btn-vertical-group :disabled="loading || updating" :buttons="allTabs" v-model="activeTab" />
|
||||
</div>
|
||||
<v-divider />
|
||||
<div class="mx-6 mt-4" v-if="activeTab === 'table'">
|
||||
<span class="text-subtitle-2">{{ tt('Transactions Per Page') }}</span>
|
||||
<v-select class="mt-2" density="compact"
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
:disabled="loading"
|
||||
:items="allPageCounts"
|
||||
v-model="countPerPage"
|
||||
/>
|
||||
</div>
|
||||
<v-tabs show-arrows class="my-4" direction="vertical"
|
||||
:disabled="loading" v-model="currentExplorationId">
|
||||
<v-tab class="tab-text-truncate" key="new" value="">
|
||||
<span class="text-truncate">{{ tt('New Exploration') }}</span>
|
||||
:disabled="loading || updating" :model-value="currentExplorer.id">
|
||||
<v-tab class="tab-text-truncate" key="new" value="" @click="createNewExplorer">
|
||||
<span class="text-truncate">{{ tt('New Explorer') }}</span>
|
||||
</v-tab>
|
||||
<v-tab class="tab-text-truncate" :key="explorer.id" :value="explorer.id"
|
||||
v-for="explorer in allExplorers"
|
||||
@click="loadExplorer(explorer.id)">
|
||||
<span class="text-truncate">{{ explorer.name || tt('Untitled Explorer') }}</span>
|
||||
</v-tab>
|
||||
<!-- <v-btn class="text-left justify-start" variant="text" color="default" :rounded="false">-->
|
||||
<!-- <span class="ps-2">{{ tt('More Explorer') }}</span>-->
|
||||
<!-- </v-btn>-->
|
||||
</v-tabs>
|
||||
</v-navigation-drawer>
|
||||
<v-main>
|
||||
@@ -36,23 +34,23 @@
|
||||
<span>{{ tt('Insights Explorer') }}</span>
|
||||
<v-btn-group class="ms-4" color="default" density="comfortable" variant="outlined" divided>
|
||||
<v-btn class="button-icon-with-direction" :icon="mdiArrowLeft"
|
||||
:disabled="loading || !canShiftDateRange"
|
||||
:disabled="loading || updating || !canShiftDateRange"
|
||||
@click="shiftDateRange(-1)"/>
|
||||
<v-menu location="bottom" max-height="500">
|
||||
<template #activator="{ props }">
|
||||
<v-btn :disabled="loading"
|
||||
<v-btn :disabled="loading || updating"
|
||||
v-bind="props">{{ displayQueryDateRangeName }}</v-btn>
|
||||
</template>
|
||||
<v-list :selected="[query.dateRangeType]">
|
||||
<v-list :selected="[currentFilter.dateRangeType]">
|
||||
<v-list-item :key="dateRange.type" :value="dateRange.type"
|
||||
:append-icon="(query.dateRangeType === dateRange.type ? mdiCheck : undefined)"
|
||||
:append-icon="(currentFilter.dateRangeType === dateRange.type ? mdiCheck : undefined)"
|
||||
v-for="dateRange in allDateRanges">
|
||||
<v-list-item-title class="cursor-pointer"
|
||||
@click="setDateFilter(dateRange.type)">
|
||||
<div class="d-flex align-center">
|
||||
<span>{{ dateRange.displayName }}</span>
|
||||
</div>
|
||||
<div class="statistics-custom-datetime-range smaller" v-if="dateRange.isUserCustomRange && query.dateRangeType === dateRange.type && !!query.startTime && !!query.endTime">
|
||||
<div class="statistics-custom-datetime-range smaller" v-if="dateRange.isUserCustomRange && currentFilter.dateRangeType === dateRange.type && !!currentFilter.startTime && !!currentFilter.endTime">
|
||||
<span>{{ displayQueryStartTime }}</span>
|
||||
<span> - </span>
|
||||
<br/>
|
||||
@@ -63,12 +61,12 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-btn class="button-icon-with-direction" :icon="mdiArrowRight"
|
||||
:disabled="loading || !canShiftDateRange"
|
||||
:disabled="loading || updating || !canShiftDateRange"
|
||||
@click="shiftDateRange(1)"/>
|
||||
</v-btn-group>
|
||||
|
||||
<v-btn density="compact" color="default" variant="text" size="24"
|
||||
class="ms-2" :icon="true" :loading="loading" @click="reload(true)">
|
||||
class="ms-2" :icon="true" :loading="loading" :disabled="updating" @click="reload(true)">
|
||||
<template #loader>
|
||||
<v-progress-circular indeterminate size="20"/>
|
||||
</template>
|
||||
@@ -76,8 +74,28 @@
|
||||
<v-tooltip activator="parent">{{ tt('Refresh') }}</v-tooltip>
|
||||
</v-btn>
|
||||
<v-spacer/>
|
||||
<v-btn class="ms-3" color="default" variant="outlined"
|
||||
:disabled="loading || updating" @click="saveExplorer(false)">
|
||||
{{ tt('Save Explorer') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="updating"></v-progress-circular>
|
||||
<v-menu activator="parent" :open-on-hover="true">
|
||||
<v-list>
|
||||
<v-list-item :prepend-icon="mdiContentSaveOutline" @click="saveExplorer(true)">
|
||||
<v-list-item-title>{{ tt('Save As New Explorer') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider class="my-2" v-if="currentExplorer.id" />
|
||||
<v-list-item :prepend-icon="mdiPencilOutline" @click="setExplorerName" v-if="currentExplorer.id">
|
||||
<v-list-item-title>{{ tt('Rename Explorer') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider class="my-2" v-if="currentExplorer.id" />
|
||||
<v-list-item :prepend-icon="mdiDeleteOutline" @click="removeExplorer" v-if="currentExplorer.id">
|
||||
<v-list-item-title>{{ tt('Delete Explorer') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ms-2"
|
||||
:disabled="loading" :icon="true">
|
||||
:disabled="loading || updating" :icon="true">
|
||||
<v-icon :icon="mdiDotsVertical" />
|
||||
<v-menu activator="parent">
|
||||
<v-list>
|
||||
@@ -86,14 +104,14 @@
|
||||
<template v-if="activeTab === 'query'">
|
||||
<v-list-item :key="timezoneType.type" :value="timezoneType.type"
|
||||
:prepend-icon="timezoneTypeIconMap[timezoneType.type]"
|
||||
:append-icon="(query.timezoneUsedForDateRange === timezoneType.type ? mdiCheck : undefined)"
|
||||
:append-icon="(currentExplorer.timezoneUsedForDateRange === timezoneType.type ? mdiCheck : undefined)"
|
||||
:title="timezoneType.displayName"
|
||||
v-for="timezoneType in allTimezoneTypesUsedForDateRange"
|
||||
@click="updateTimezoneUsedForDateRange(timezoneType.type)"></v-list-item>
|
||||
@click="currentExplorer.timezoneUsedForDateRange = timezoneType.type"></v-list-item>
|
||||
</template>
|
||||
<v-list-item :prepend-icon="mdiExport"
|
||||
:title="tt('Export Results')"
|
||||
:disabled="loading || !filteredTransactions || filteredTransactions.length < 1"
|
||||
:disabled="loading || updating || !filteredTransactions || filteredTransactions.length < 1"
|
||||
@click="exportResults"
|
||||
v-if="activeTab === 'table' || activeTab === 'chart'"></v-list-item>
|
||||
</v-list>
|
||||
@@ -104,17 +122,16 @@
|
||||
|
||||
<v-window class="d-flex flex-grow-1 disable-tab-transition w-100-window-container" v-model="activeTab">
|
||||
<v-window-item value="query">
|
||||
<explorer-query-tab :loading="loading" />
|
||||
<explorer-query-tab :loading="loading" :disabled="loading || updating" />
|
||||
</v-window-item>
|
||||
<v-window-item value="table">
|
||||
<explorer-data-table-tab ref="explorerDataTableTab"
|
||||
:loading="loading"
|
||||
v-model:count-per-page="countPerPage"
|
||||
:loading="loading" :disabled="loading || updating"
|
||||
@click:transaction="onShowTransaction" />
|
||||
</v-window-item>
|
||||
<v-window-item value="chart">
|
||||
<explorer-chart-tab ref="explorerChartTab"
|
||||
:loading="loading" />
|
||||
:loading="loading" :disabled="loading || updating" />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-card>
|
||||
@@ -125,25 +142,29 @@
|
||||
</v-row>
|
||||
|
||||
<date-range-selection-dialog :title="tt('Custom Date Range')"
|
||||
:min-time="query.startTime"
|
||||
:max-time="query.endTime"
|
||||
:min-time="currentFilter.startTime"
|
||||
:max-time="currentFilter.endTime"
|
||||
v-model:show="showCustomDateRangeDialog"
|
||||
@dateRange:change="setCustomDateFilter"
|
||||
@error="onShowDateRangeError" />
|
||||
|
||||
<explorer-rename-dialog ref="explorerRenameDialog" />
|
||||
<edit-dialog ref="editDialog" :type="TransactionEditPageType.Transaction" />
|
||||
<export-dialog ref="exportDialog" />
|
||||
|
||||
<confirm-dialog ref="confirmDialog"/>
|
||||
<snack-bar ref="snackbar" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
import ExplorerQueryTab from '@/views/desktop/insights/tabs/ExplorerQueryTab.vue';
|
||||
import ExplorerDataTableTab from '@/views/desktop/insights/tabs/ExplorerDataTableTab.vue';
|
||||
import ExplorerChartTab from '@/views/desktop/insights/tabs/ExplorerChartTab.vue';
|
||||
import ExplorerRenameDialog from '@/views/desktop/insights/dialogs/ExplorerRenameDialog.vue';
|
||||
import EditDialog from '@/views/desktop/transactions/list/dialogs/EditDialog.vue';
|
||||
import ExportDialog from '@/views/desktop/statistics/transaction/dialogs/ExportDialog.vue';
|
||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||
|
||||
import { ref, computed, useTemplateRef, watch } from 'vue';
|
||||
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
@@ -158,15 +179,12 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
|
||||
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
|
||||
import { type TransactionExplorerPartialFilter, type TransactionExplorerFilter, useExplorersStore } from '@/stores/explorer.ts';
|
||||
|
||||
import type { NameNumeralValue, TypeAndDisplayName } from '@/core/base.ts';
|
||||
import type { NumeralSystem } from '@/core/numeral.ts';
|
||||
import type { TypeAndDisplayName } from '@/core/base.ts';
|
||||
import { type WeekDayValue, type LocalizedDateRange, DateRangeScene, DateRange } from '@/core/datetime.ts';
|
||||
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
||||
|
||||
import {
|
||||
type TransactionInsightDataItem,
|
||||
Transaction
|
||||
} from '@/models/transaction.ts';
|
||||
import { type TransactionInsightDataItem, Transaction } from '@/models/transaction.ts';
|
||||
import { type InsightsExplorerBasicInfo, InsightsExplorer } from '@/models/explorer.ts';
|
||||
|
||||
import {
|
||||
parseDateTimeFromUnixTime,
|
||||
@@ -175,6 +193,8 @@ import {
|
||||
getDateRangeByDateType
|
||||
} from '@/lib/datetime.ts';
|
||||
|
||||
import { generateRandomUUID } from '@/lib/misc.ts';
|
||||
|
||||
import {
|
||||
mdiMenu,
|
||||
mdiArrowLeft,
|
||||
@@ -182,6 +202,9 @@ import {
|
||||
mdiCheck,
|
||||
mdiRefresh,
|
||||
mdiDotsVertical,
|
||||
mdiContentSaveOutline,
|
||||
mdiPencilOutline,
|
||||
mdiDeleteOutline,
|
||||
mdiHomeClockOutline,
|
||||
mdiInvoiceTextClockOutline,
|
||||
mdiExport
|
||||
@@ -198,9 +221,12 @@ interface InsightsExplorerProps {
|
||||
const props = defineProps<InsightsExplorerProps>();
|
||||
|
||||
type ExplorerPageTabType = 'query' | 'table' | 'chart';
|
||||
|
||||
type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
|
||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||
type ExplorerDataTableTabType = InstanceType<typeof ExplorerDataTableTab>;
|
||||
type ExplorerChartTabType = InstanceType<typeof ExplorerChartTab>;
|
||||
type ExplorerRenameDialogType = InstanceType<typeof ExplorerRenameDialog>;
|
||||
type EditDialogType = InstanceType<typeof EditDialog>;
|
||||
type ExportDialogType = InstanceType<typeof ExportDialog>;
|
||||
|
||||
@@ -211,7 +237,6 @@ const {
|
||||
tt,
|
||||
getAllDateRanges,
|
||||
getAllTimezoneTypesUsedForStatistics,
|
||||
getCurrentNumeralSystemType,
|
||||
formatDateTimeToLongDateTime,
|
||||
formatDateRange
|
||||
} = useI18n();
|
||||
@@ -227,34 +252,37 @@ const timezoneTypeIconMap = {
|
||||
[TimezoneTypeForStatistics.TransactionTimezone.type]: mdiInvoiceTextClockOutline
|
||||
};
|
||||
|
||||
const confirmDialog = useTemplateRef<ConfirmDialogType>('confirmDialog');
|
||||
const snackbar = useTemplateRef<SnackBarType>('snackbar');
|
||||
const explorerDataTableTab = useTemplateRef<ExplorerDataTableTabType>('explorerDataTableTab');
|
||||
const explorerChartTab = useTemplateRef<ExplorerChartTabType>('explorerChartTab');
|
||||
const explorerRenameDialog = useTemplateRef<ExplorerRenameDialogType>('explorerRenameDialog');
|
||||
const exportDialog = useTemplateRef<ExportDialogType>('exportDialog');
|
||||
const editDialog = useTemplateRef<EditDialogType>('editDialog');
|
||||
|
||||
const loading = ref<boolean>(true);
|
||||
const initing = ref<boolean>(true);
|
||||
const updating = ref<boolean>(false);
|
||||
const clientSessionId = ref<string>('');
|
||||
const alwaysShowNav = ref<boolean>(display.mdAndUp.value);
|
||||
const showNav = ref<boolean>(display.mdAndUp.value);
|
||||
const activeTab = ref<ExplorerPageTabType>('query');
|
||||
const currentExplorationId = ref<string>('');
|
||||
const countPerPage = ref<number>(15);
|
||||
const showCustomDateRangeDialog = ref<boolean>(false);
|
||||
|
||||
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
||||
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
|
||||
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
|
||||
|
||||
const query = computed<TransactionExplorerFilter>(() => explorersStore.transactionExplorerFilter);
|
||||
const allExplorers = computed<InsightsExplorerBasicInfo[]>(() => explorersStore.allInsightsExplorerBasicInfos.slice(0, 15));
|
||||
const currentFilter = computed<TransactionExplorerFilter>(() => explorersStore.transactionExplorerFilter);
|
||||
const currentExplorer = computed<InsightsExplorer>(() => explorersStore.currentInsightsExplorer);
|
||||
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explorersStore.filteredTransactions);
|
||||
|
||||
const allDateRanges = computed<LocalizedDateRange[]>(() => getAllDateRanges(DateRangeScene.InsightsExplorer, true));
|
||||
const allTimezoneTypesUsedForDateRange = computed<TypeAndDisplayName[]>(() => getAllTimezoneTypesUsedForStatistics());
|
||||
const canShiftDateRange = computed<boolean>(() => query.value.dateRangeType !== DateRange.All.type);
|
||||
const displayQueryDateRangeName = computed<string>(() => formatDateRange(query.value.dateRangeType, query.value.startTime, query.value.endTime));
|
||||
const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.startTime)));
|
||||
const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(query.value.endTime)));
|
||||
const canShiftDateRange = computed<boolean>(() => currentFilter.value.dateRangeType !== DateRange.All.type);
|
||||
const displayQueryDateRangeName = computed<string>(() => formatDateRange(currentFilter.value.dateRangeType, currentFilter.value.startTime, currentFilter.value.endTime));
|
||||
const displayQueryStartTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(currentFilter.value.startTime)));
|
||||
const displayQueryEndTime = computed<string>(() => formatDateTimeToLongDateTime(parseDateTimeFromUnixTime(currentFilter.value.endTime)));
|
||||
|
||||
const allTabs = computed<{ name: string, value: ExplorerPageTabType }[]>(() => {
|
||||
return [
|
||||
@@ -273,24 +301,13 @@ const allTabs = computed<{ name: string, value: ExplorerPageTabType }[]>(() => {
|
||||
];
|
||||
});
|
||||
|
||||
const allPageCounts = computed<NameNumeralValue[]>(() => {
|
||||
const pageCounts: NameNumeralValue[] = [];
|
||||
const availableCountPerPage: number[] = [ 5, 10, 15, 20, 25, 30, 50 ];
|
||||
|
||||
for (const count of availableCountPerPage) {
|
||||
pageCounts.push({ value: count, name: numeralSystem.value.formatNumber(count) });
|
||||
}
|
||||
|
||||
pageCounts.push({ value: -1, name: tt('All') });
|
||||
|
||||
return pageCounts;
|
||||
});
|
||||
|
||||
function getFilterLinkUrl(): string {
|
||||
return `/insights/explorer?${explorersStore.getTransactionExplorerPageParams(currentExplorationId.value, activeTab.value)}`;
|
||||
return `/insights/explorer?${explorersStore.getTransactionExplorerPageParams(currentExplorer.value.id, activeTab.value)}`;
|
||||
}
|
||||
|
||||
function init(initProps: InsightsExplorerProps): void {
|
||||
clientSessionId.value = generateRandomUUID();
|
||||
|
||||
const filter: TransactionExplorerPartialFilter = {
|
||||
dateRangeType: initProps.initDateRangeType ? parseInt(initProps.initDateRangeType) : undefined,
|
||||
startTime: initProps.initStartTime ? parseInt(initProps.initStartTime) : undefined,
|
||||
@@ -299,11 +316,11 @@ function init(initProps: InsightsExplorerProps): void {
|
||||
|
||||
let needReload = false;
|
||||
|
||||
if (filter.dateRangeType !== query.value.dateRangeType) {
|
||||
if (filter.dateRangeType !== currentFilter.value.dateRangeType) {
|
||||
needReload = true;
|
||||
} else if (filter.dateRangeType === DateRange.Custom.type) {
|
||||
if (filter.startTime !== query.value.startTime
|
||||
|| filter.endTime !== query.value.endTime) {
|
||||
if (filter.startTime !== currentFilter.value.startTime
|
||||
|| filter.endTime !== currentFilter.value.endTime) {
|
||||
needReload = true;
|
||||
}
|
||||
}
|
||||
@@ -311,7 +328,6 @@ function init(initProps: InsightsExplorerProps): void {
|
||||
if (initProps.initActiveTab === 'query' || initProps.initActiveTab === 'table' || initProps.initActiveTab === 'chart') {
|
||||
if (initProps.initActiveTab !== activeTab.value) {
|
||||
activeTab.value = initProps.initActiveTab;
|
||||
needReload = true;
|
||||
}
|
||||
} else {
|
||||
activeTab.value = 'query';
|
||||
@@ -319,7 +335,15 @@ function init(initProps: InsightsExplorerProps): void {
|
||||
|
||||
explorersStore.initTransactionExplorerFilter(filter);
|
||||
|
||||
if (!needReload && !explorersStore.transactionExplorerStateInvalid) {
|
||||
if (initProps.initId) {
|
||||
if (explorersStore.currentInsightsExplorer.id !== initProps.initId) {
|
||||
loadExplorer(initProps.initId);
|
||||
}
|
||||
} else {
|
||||
explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer());
|
||||
}
|
||||
|
||||
if (!needReload && !explorersStore.transactionExplorerStateInvalid && !explorersStore.insightsExplorerListStateInvalid) {
|
||||
loading.value = false;
|
||||
initing.value = false;
|
||||
return;
|
||||
@@ -328,7 +352,8 @@ function init(initProps: InsightsExplorerProps): void {
|
||||
Promise.all([
|
||||
accountsStore.loadAllAccounts({ force: false }),
|
||||
transactionCategoriesStore.loadAllCategories({ force: false }),
|
||||
transactionTagsStore.loadAllTags({ force: false })
|
||||
transactionTagsStore.loadAllTags({ force: false }),
|
||||
explorersStore.loadAllInsightsExplorerBasicInfos({ force: false })
|
||||
]).then(() => {
|
||||
return explorersStore.loadAllTransactions({ force: false });
|
||||
}).then(() => {
|
||||
@@ -364,9 +389,100 @@ function reload(force: boolean): Promise<unknown> | null {
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimezoneUsedForDateRange(timezoneType: number): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
timezoneUsedForDateRange: timezoneType
|
||||
function createNewExplorer(): void {
|
||||
if (!currentExplorer.value.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
explorersStore.updateCurrentInsightsExplorer(InsightsExplorer.createNewExplorer());
|
||||
router.push(getFilterLinkUrl());
|
||||
}
|
||||
|
||||
function loadExplorer(explorerId: string): void {
|
||||
if (currentExplorer.value.id === explorerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
explorersStore.getInsightsExplorer({
|
||||
explorerId: explorerId
|
||||
}).then(explorer => {
|
||||
explorersStore.updateCurrentInsightsExplorer(explorer);
|
||||
loading.value = false;
|
||||
router.push(getFilterLinkUrl());
|
||||
}).catch(error => {
|
||||
loading.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveExplorer(saveAs?: boolean): void {
|
||||
if (saveAs || !currentExplorer.value.name) {
|
||||
explorerRenameDialog.value?.open(currentExplorer.value.name || '').then((newName: string) => {
|
||||
currentExplorer.value.name = newName;
|
||||
doSaveExplorer(saveAs);
|
||||
})
|
||||
} else {
|
||||
doSaveExplorer(saveAs);
|
||||
}
|
||||
}
|
||||
|
||||
function doSaveExplorer(saveAs?: boolean): Promise<unknown> {
|
||||
const oldExplorerId = currentExplorer.value.id;
|
||||
|
||||
updating.value = true;
|
||||
|
||||
return explorersStore.saveInsightsExplorer({
|
||||
explorer: currentExplorer.value,
|
||||
saveAs: saveAs,
|
||||
clientSessionId: clientSessionId.value
|
||||
}).then(newExplorer => {
|
||||
updating.value = false;
|
||||
clientSessionId.value = generateRandomUUID();
|
||||
explorersStore.updateCurrentInsightsExplorer(newExplorer);
|
||||
|
||||
if (oldExplorerId !== newExplorer.id) {
|
||||
router.push(getFilterLinkUrl());
|
||||
}
|
||||
}).catch(error => {
|
||||
updating.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setExplorerName(): void {
|
||||
explorerRenameDialog.value?.open(currentExplorer.value.name || '').then((newName: string) => {
|
||||
currentExplorer.value.name = newName;
|
||||
});
|
||||
}
|
||||
|
||||
function removeExplorer(): void {
|
||||
if (!currentExplorer.value.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog.value?.open('Are you sure you want to delete this explorer?').then(() => {
|
||||
updating.value = true;
|
||||
|
||||
explorersStore.deleteInsightsExplorer({
|
||||
explorer: currentExplorer.value
|
||||
}).then(() => {
|
||||
updating.value = false;
|
||||
createNewExplorer();
|
||||
}).catch(error => {
|
||||
updating.value = false;
|
||||
|
||||
if (!error.processed) {
|
||||
snackbar.value?.showError(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -390,7 +506,7 @@ function setDateFilter(dateType: number): void {
|
||||
if (dateType === DateRange.Custom.type) { // Custom
|
||||
showCustomDateRangeDialog.value = true;
|
||||
return;
|
||||
} else if (query.value.dateRangeType === dateType) {
|
||||
} else if (currentFilter.value.dateRangeType === dateType) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,11 +552,11 @@ function setCustomDateFilter(startTime: number, endTime: number): void {
|
||||
}
|
||||
|
||||
function shiftDateRange(scale: number): void {
|
||||
if (query.value.dateRangeType === DateRange.All.type) {
|
||||
if (currentFilter.value.dateRangeType === DateRange.All.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newDateRange = getShiftedDateRangeAndDateType(query.value.startTime, query.value.endTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.Normal);
|
||||
const newDateRange = getShiftedDateRangeAndDateType(currentFilter.value.startTime, currentFilter.value.endTime, scale, firstDayOfWeek.value, fiscalYearStart.value, DateRangeScene.Normal);
|
||||
|
||||
const changed = explorersStore.updateTransactionExplorerFilter({
|
||||
dateRangeType: newDateRange.dateType,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<v-dialog max-width="500" :persistent="oldExplorerName !== newExplorerName" v-model="showState">
|
||||
<v-card class="pa-sm-1 pa-md-2">
|
||||
<template #title>
|
||||
<h4 class="text-h4 text-wrap">{{ tt('Rename Explorer') }}</h4>
|
||||
</template>
|
||||
<v-card-text class="w-100 d-flex justify-center">
|
||||
<v-text-field persistent-placeholder
|
||||
:label="tt('Explorer Name')"
|
||||
:placeholder="tt('Explorer Name')"
|
||||
v-model="newExplorerName"/>
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
<div class="w-100 d-flex justify-center flex-wrap mt-sm-1 mt-md-2 gap-4">
|
||||
<v-btn color="primary" :disabled="!newExplorerName || oldExplorerName === newExplorerName" @click="save">
|
||||
{{ tt('Save') }}
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="tonal" @click="cancel">
|
||||
{{ tt('Cancel') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/locales/helpers.ts';
|
||||
|
||||
const { tt } = useI18n();
|
||||
|
||||
let resolveFunc: ((name: string) => void) | null = null;
|
||||
let rejectFunc: ((reason?: unknown) => void) | null = null;
|
||||
|
||||
const showState = ref<boolean>(false);
|
||||
const oldExplorerName = ref<string>('');
|
||||
const newExplorerName = ref<string>('');
|
||||
|
||||
function open(currentExplorerName: string): Promise<string> {
|
||||
showState.value = true;
|
||||
oldExplorerName.value = currentExplorerName;
|
||||
newExplorerName.value = currentExplorerName;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
resolveFunc = resolve;
|
||||
rejectFunc = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function save(): void {
|
||||
resolveFunc?.(newExplorerName.value);
|
||||
showState.value = false;
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
rejectFunc?.();
|
||||
showState.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
:disabled="loading"
|
||||
:disabled="loading || disabled"
|
||||
:label="tt('Chart Type')"
|
||||
:items="allTransactionExplorerChartTypes"
|
||||
:model-value="currentChartType"
|
||||
@update:model-value="updateChartType($event as TransactionExplorerChartTypeValue)"
|
||||
:model-value="currentExplorer.chartType"
|
||||
@update:model-value="currentExplorer.chartType = $event as TransactionExplorerChartTypeValue"
|
||||
/>
|
||||
<v-select
|
||||
class="flex-0-0"
|
||||
@@ -21,11 +21,11 @@
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
:disabled="loading"
|
||||
:disabled="loading || disabled"
|
||||
:label="tt('Axis / Category')"
|
||||
:items="allTransactionExplorerDataDimensions"
|
||||
:model-value="currentCategoryDimension"
|
||||
@update:model-value="updateCategoryDimension($event as TransactionExplorerDataDimensionType)"
|
||||
:model-value="currentExplorer.categoryDimension"
|
||||
@update:model-value="currentExplorer.categoryDimension = $event as TransactionExplorerDataDimensionType"
|
||||
/>
|
||||
<v-select
|
||||
class="flex-0-0"
|
||||
@@ -33,14 +33,14 @@
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
:disabled="loading || !TransactionExplorerChartType.valueOf(currentChartType)?.seriesDimensionRequired"
|
||||
:disabled="loading || disabled || !TransactionExplorerChartType.valueOf(currentExplorer.chartType)?.seriesDimensionRequired"
|
||||
:label="tt('Series')"
|
||||
:items="allTransactionExplorerDataDimensions"
|
||||
:model-value="TransactionExplorerChartType.valueOf(currentChartType)?.seriesDimensionRequired ? currentSeriesDimension : TransactionExplorerDataDimension.None.value"
|
||||
@update:model-value="updateSeriesDimension($event as TransactionExplorerDataDimensionType)"
|
||||
:model-value="TransactionExplorerChartType.valueOf(currentExplorer.chartType)?.seriesDimensionRequired ? currentExplorer.seriesDimension : TransactionExplorerDataDimension.None.value"
|
||||
@update:model-value="currentExplorer.seriesDimension = $event as TransactionExplorerDataDimensionType"
|
||||
>
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item :disabled="item.value === currentCategoryDimension && item.value !== TransactionExplorerDataDimension.SeriesDimensionDefault.value" v-bind="props">
|
||||
<v-list-item :disabled="item.value === currentExplorer.categoryDimension && item.value !== TransactionExplorerDataDimension.SeriesDimensionDefault.value" v-bind="props">
|
||||
<template #title>
|
||||
<div class="text-truncate">{{ item.raw.name }}</div>
|
||||
</template>
|
||||
@@ -53,11 +53,11 @@
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
:disabled="loading"
|
||||
:disabled="loading || disabled"
|
||||
:label="tt('Value Metric')"
|
||||
:items="allTransactionExplorerValueMetrics"
|
||||
:model-value="currentValueMetric"
|
||||
@update:model-value="updateValueMetric($event as TransactionExplorerValueMetricType)"
|
||||
:model-value="currentExplorer.valueMetric"
|
||||
@update:model-value="currentExplorer.valueMetric = $event as TransactionExplorerValueMetricType"
|
||||
/>
|
||||
<v-select
|
||||
class="flex-0-0"
|
||||
@@ -65,18 +65,18 @@
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
density="compact"
|
||||
:disabled="loading"
|
||||
:disabled="loading || disabled"
|
||||
:label="tt('Sort Order')"
|
||||
:items="allTransactionExplorerChartSortingTypes"
|
||||
:model-value="currentChartSortingType"
|
||||
@update:model-value="updateChartSortingType($event)"
|
||||
:model-value="currentExplorer.chartSortingType"
|
||||
@update:model-value="currentExplorer.chartSortingType = $event"
|
||||
/>
|
||||
<v-spacer class="flex-1-1"/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExplorerChartType.Pie.value">
|
||||
<v-card-text :class="{ 'readonly': loading }" v-if="currentExplorer.chartType === TransactionExplorerChartType.Pie.value">
|
||||
<pie-chart
|
||||
:items="[
|
||||
{id: '1', name: '---', value: 60, color: '7c7c7f'},
|
||||
@@ -95,7 +95,7 @@
|
||||
:show-value="true"
|
||||
:show-percent="true"
|
||||
:enable-click-item="true"
|
||||
:amount-value="explorersStore.transactionExplorerFilter.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
|
||||
:amount-value="currentExplorer.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
|
||||
:default-currency="defaultCurrency"
|
||||
id-field="id"
|
||||
name-field="name"
|
||||
@@ -104,7 +104,7 @@
|
||||
@click="onClickPieChartItem"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-text :class="{ 'readonly': loading }" v-if="currentChartType === TransactionExplorerChartType.Radar.value">
|
||||
<v-card-text :class="{ 'readonly': loading }" v-if="currentExplorer.chartType === TransactionExplorerChartType.Radar.value">
|
||||
<radar-chart
|
||||
:items="[
|
||||
{name: '---', value: 10},
|
||||
@@ -124,7 +124,7 @@
|
||||
:min-valid-percent="0.0001"
|
||||
:show-value="true"
|
||||
:show-percent="true"
|
||||
:amount-value="explorersStore.transactionExplorerFilter.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
|
||||
:amount-value="currentExplorer.valueMetric !== TransactionExplorerValueMetric.TransactionCount.value"
|
||||
:default-currency="defaultCurrency"
|
||||
name-field="name"
|
||||
value-field="totalAmount"
|
||||
@@ -159,6 +159,7 @@ import {
|
||||
} from '@/core/explorer.ts';
|
||||
|
||||
import { type SortableTransactionStatisticDataItem } from '@/models/transaction.ts';
|
||||
import type { InsightsExplorer } from '@/models/explorer.ts';
|
||||
|
||||
import { isDefined } from '@/lib/common.ts';
|
||||
import { parseDateTimeFromString } from '@/lib/datetime.ts';
|
||||
@@ -166,6 +167,7 @@ import { sortStatisticsItems } from '@/lib/statistics.ts';
|
||||
|
||||
interface InsightsExplorerDataTableTabProps {
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface CategoryDimensionData extends SortableTransactionStatisticDataItem {
|
||||
@@ -210,14 +212,10 @@ const allTransactionExplorerValueMetrics = computed<NameValue[]>(() => getAllTra
|
||||
const allTransactionExplorerChartTypes = computed<NameValue[]>(() => getAllTransactionExplorerChartTypes());
|
||||
const allTransactionExplorerChartSortingTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsSortingTypes());
|
||||
|
||||
const currentCategoryDimension = computed<TransactionExplorerDataDimensionType>(() => explorersStore.transactionExplorerFilter.categoryDimension);
|
||||
const currentSeriesDimension = computed<TransactionExplorerDataDimensionType>(() => explorersStore.transactionExplorerFilter.seriesDimension);
|
||||
const currentValueMetric = computed<TransactionExplorerValueMetricType>(() => explorersStore.transactionExplorerFilter.valueMetric);
|
||||
const currentChartType = computed<TransactionExplorerChartTypeValue>(() => explorersStore.transactionExplorerFilter.chartType);
|
||||
const currentChartSortingType = computed<number>(() => explorersStore.transactionExplorerFilter.chartSortingType);
|
||||
const currentExplorer = computed<InsightsExplorer>(() => explorersStore.currentInsightsExplorer);
|
||||
|
||||
const categoryDimensionTransactionExplorerData = computed<CategoryDimensionData[]>(() => {
|
||||
if (currentChartType.value !== TransactionExplorerChartType.Pie.value && currentChartType.value !== TransactionExplorerChartType.Radar.value) {
|
||||
if (currentExplorer.value.chartType !== TransactionExplorerChartType.Pie.value && currentExplorer.value.chartType !== TransactionExplorerChartType.Radar.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -245,7 +243,7 @@ const categoryDimensionTransactionExplorerData = computed<CategoryDimensionData[
|
||||
});
|
||||
}
|
||||
|
||||
sortStatisticsItems(result, explorersStore.transactionExplorerFilter.chartSortingType);
|
||||
sortStatisticsItems(result, currentExplorer.value.chartSortingType);
|
||||
|
||||
return result;
|
||||
});
|
||||
@@ -262,13 +260,13 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesedInfo): stri
|
||||
needI18n = info.categoryNameNeedI18n;
|
||||
i18nParameters = info.categoryNameI18nParameters;
|
||||
dimessionType = info.categoryIdType;
|
||||
dimession = explorersStore.transactionExplorerFilter.categoryDimension;
|
||||
dimession = currentExplorer.value.categoryDimension;
|
||||
} else if ('seriesName' in info) {
|
||||
name = info.seriesName;
|
||||
needI18n = info.seriesNameNeedI18n;
|
||||
i18nParameters = info.seriesNameI18nParameters;
|
||||
dimessionType = info.seriesIdType;
|
||||
dimession = explorersStore.transactionExplorerFilter.seriesDimension;
|
||||
dimession = currentExplorer.value.seriesDimension;
|
||||
}
|
||||
|
||||
let displayName: string = name;
|
||||
@@ -323,36 +321,6 @@ function getCategoriedDataDisplayName(info: CategoriedInfo | SeriesedInfo): stri
|
||||
return displayName;
|
||||
}
|
||||
|
||||
function updateChartType(chartType: TransactionExplorerChartTypeValue): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
chartType: chartType,
|
||||
});
|
||||
}
|
||||
|
||||
function updateCategoryDimension(categoryDimension: TransactionExplorerDataDimensionType): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
categoryDimension: categoryDimension,
|
||||
});
|
||||
}
|
||||
|
||||
function updateSeriesDimension(seriesDimension: TransactionExplorerDataDimensionType): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
seriesDimension: seriesDimension,
|
||||
});
|
||||
}
|
||||
|
||||
function updateValueMetric(valueMetric: TransactionExplorerValueMetricType): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
valueMetric: valueMetric,
|
||||
});
|
||||
}
|
||||
|
||||
function updateChartSortingType(sortingType: number): void {
|
||||
explorersStore.updateTransactionExplorerFilter({
|
||||
chartSortingType: sortingType,
|
||||
});
|
||||
}
|
||||
|
||||
function onClickPieChartItem(item: Record<string, unknown>): void {
|
||||
if (!item || !('id' in item) || !('dimension' in item)) {
|
||||
return;
|
||||
@@ -367,8 +335,8 @@ function onClickPieChartItem(item: Record<string, unknown>): void {
|
||||
}
|
||||
|
||||
function buildExportResults(): { headers: string[], data: string[][] } | undefined {
|
||||
if (currentChartType.value === TransactionExplorerChartType.Pie.value || currentChartType.value === TransactionExplorerChartType.Radar.value) {
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(explorersStore.transactionExplorerFilter.valueMetric);
|
||||
if (currentExplorer.value.chartType === TransactionExplorerChartType.Pie.value || currentExplorer.value.chartType === TransactionExplorerChartType.Radar.value) {
|
||||
const valueMetric = TransactionExplorerValueMetric.valueOf(currentExplorer.value.valueMetric);
|
||||
|
||||
return {
|
||||
headers: [
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
<template>
|
||||
<v-card-text class="px-5 py-0 mb-4">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<div class="d-flex overflow-x-auto align-center gap-2 pt-2">
|
||||
<v-select
|
||||
class="flex-0-0"
|
||||
min-width="150"
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
:disabled="loading || disabled"
|
||||
:label="tt('Transactions Per Page')"
|
||||
:items="allPageCounts"
|
||||
v-model="countPerPage"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-data-table
|
||||
fixed-header
|
||||
fixed-footer
|
||||
multi-sort
|
||||
item-value="index"
|
||||
:class="{ 'insights-explorer-table': true, 'text-sm': true, 'disabled': loading, 'loading-skeleton': loading }"
|
||||
:class="{ 'insights-explorer-table': true, 'text-sm': true, 'disabled': loading || disabled, 'loading-skeleton': loading }"
|
||||
:headers="dataTableHeaders"
|
||||
:items="filteredTransactions"
|
||||
:hover="true"
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:items-per-page="countPerPage"
|
||||
v-model:page="currentPage"
|
||||
>
|
||||
<template #item.time="{ item }">
|
||||
@@ -61,7 +80,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #item.operation="{ item }">
|
||||
<v-btn density="compact" variant="text" color="default" :disabled="loading"
|
||||
<v-btn density="compact" variant="text" color="default" :disabled="loading || disabled"
|
||||
@click="showTransaction(item)">
|
||||
{{ tt('View') }}
|
||||
</v-btn>
|
||||
@@ -78,7 +97,7 @@
|
||||
</template>
|
||||
<template #bottom>
|
||||
<div class="title-and-toolbar d-flex align-center justify-center text-no-wrap mt-2 mb-4">
|
||||
<pagination-buttons :disabled="loading"
|
||||
<pagination-buttons :disabled="loading || disabled"
|
||||
:totalPageCount="totalPageCount"
|
||||
v-model="currentPage">
|
||||
</pagination-buttons>
|
||||
@@ -98,6 +117,7 @@ import { useSettingsStore } from '@/stores/setting.ts';
|
||||
import { useUserStore } from '@/stores/user.ts';
|
||||
import { useExplorersStore } from '@/stores/explorer.ts';
|
||||
|
||||
import type { NameNumeralValue } from '@/core/base.ts';
|
||||
import type { NumeralSystem } from '@/core/numeral.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
|
||||
@@ -121,13 +141,13 @@ import {
|
||||
|
||||
interface InsightsExplorerDataTableTabProps {
|
||||
loading?: boolean;
|
||||
countPerPage: number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<InsightsExplorerDataTableTabProps>();
|
||||
defineProps<InsightsExplorerDataTableTabProps>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click:transaction', value: TransactionInsightDataItem): void;
|
||||
(e: 'update:countPerPage', value: number): void;
|
||||
}>();
|
||||
|
||||
const {
|
||||
@@ -144,21 +164,30 @@ const userStore = useUserStore();
|
||||
const explorersStore = useExplorersStore();
|
||||
|
||||
const currentPage = ref<number>(1);
|
||||
const countPerPage = ref<number>(15);
|
||||
|
||||
const numeralSystem = computed<NumeralSystem>(() => getCurrentNumeralSystemType());
|
||||
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
|
||||
|
||||
const filteredTransactions = computed<TransactionInsightDataItem[]>(() => explorersStore.filteredTransactions);
|
||||
|
||||
const itemsPerPage = computed<number>({
|
||||
get: () => props.countPerPage,
|
||||
set: (value: number) => emit('update:countPerPage', value)
|
||||
})
|
||||
const allPageCounts = computed<NameNumeralValue[]>(() => {
|
||||
const pageCounts: NameNumeralValue[] = [];
|
||||
const availableCountPerPage: number[] = [ 5, 10, 15, 20, 25, 30, 50 ];
|
||||
|
||||
for (const count of availableCountPerPage) {
|
||||
pageCounts.push({ value: count, name: numeralSystem.value.formatNumber(count) });
|
||||
}
|
||||
|
||||
pageCounts.push({ value: -1, name: tt('All') });
|
||||
|
||||
return pageCounts;
|
||||
});
|
||||
|
||||
const skeletonData = computed<number[]>(() => {
|
||||
const data: number[] = [];
|
||||
|
||||
for (let i = 0; i < itemsPerPage.value; i++) {
|
||||
for (let i = 0; i < countPerPage.value; i++) {
|
||||
data.push(i);
|
||||
}
|
||||
|
||||
@@ -171,7 +200,7 @@ const totalPageCount = computed<number>(() => {
|
||||
}
|
||||
|
||||
const count = filteredTransactions.value.length;
|
||||
return Math.ceil(count / itemsPerPage.value);
|
||||
return Math.ceil(count / countPerPage.value);
|
||||
});
|
||||
|
||||
const dataTableHeaders = computed<object[]>(() => {
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<v-card-subtitle class="px-5">
|
||||
<div class="title-and-toolbar d-flex">
|
||||
<v-btn color="default" variant="outlined"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
@click="addQuery">{{ tt('Add Query') }}</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn color="secondary" variant="tonal"
|
||||
:disabled="loading || !!editingQuery || queries.length < 1"
|
||||
:disabled="loading || disabled || !!editingQuery || queries.length < 1"
|
||||
@click="clearAllQueries">{{ tt('Clear All') }}</v-btn>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="query-name text-subtitle-1 ms-2" v-if="editingQuery !== query">{{ query.name || tt('format.misc.queryIndex', { index: queryIndex + 1 }) }}</span>
|
||||
<div class="query-name-edit ms-2" v-if="editingQuery === query">
|
||||
<v-text-field autofocus type="text" density="compact" variant="underlined"
|
||||
:disabled="loading"
|
||||
:disabled="loading || disabled"
|
||||
:placeholder="tt('format.misc.queryIndex', { index: queryIndex + 1 })"
|
||||
v-text-field-auto-width="{ minWidth: 20, maxWidth: 300, auxSpanId: `query-name-aux-span-${queryIndex + 1}` }"
|
||||
v-model="editingQueryName"
|
||||
@@ -27,28 +27,28 @@
|
||||
<span :id="`query-name-aux-span-${queryIndex + 1}`" />
|
||||
</div>
|
||||
<v-btn class="ms-2" density="compact" color="primary" variant="text" size="small"
|
||||
:icon="true" :disabled="loading"
|
||||
:icon="true" :disabled="loading || disabled"
|
||||
@click="updateQueryName(query)"
|
||||
v-if="editingQuery === query">
|
||||
<v-icon :icon="mdiCheck" size="18" />
|
||||
<v-tooltip activator="parent">{{ tt('Update') }}</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn class="ms-2" density="compact" color="default" variant="text" size="small"
|
||||
:icon="true" :disabled="loading"
|
||||
:icon="true" :disabled="loading || disabled"
|
||||
@click="cancelUpdateQueryName"
|
||||
v-if="editingQuery === query">
|
||||
<v-icon :icon="mdiClose" size="18" />
|
||||
<v-tooltip activator="parent">{{ tt('Cancel') }}</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn class="ms-2" density="compact" color="default" variant="text" size="small"
|
||||
:icon="true" :disabled="loading || !!editingQuery"
|
||||
:icon="true" :disabled="loading || disabled || !!editingQuery"
|
||||
@click="editingQueryName = query.name; editingQuery = query"
|
||||
v-if="!editingQuery || editingQuery !== query">
|
||||
<v-icon :icon="mdiPencilOutline" size="18" />
|
||||
<v-tooltip activator="parent">{{ tt('Modify Query Name') }}</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn class="ms-2" density="compact" color="default" variant="text" size="small"
|
||||
:icon="true" :disabled="loading || !!editingQuery"
|
||||
:icon="true" :disabled="loading || disabled || !!editingQuery"
|
||||
@click="duplicateQuery(query)"
|
||||
v-if="!editingQuery || editingQuery !== query">
|
||||
<v-icon :icon="mdiContentCopy" size="18" />
|
||||
@@ -56,7 +56,7 @@
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-switch class="bidirectional-switch ms-2" color="secondary"
|
||||
:disabled="loading || !!editingQuery || !query.conditions || query.conditions.length < 1"
|
||||
:disabled="loading || disabled || !!editingQuery || !query.conditions || query.conditions.length < 1"
|
||||
:label="tt('Expression')"
|
||||
v-model="showExpression[queryIndex]"
|
||||
@click="showExpression[queryIndex] = !showExpression[queryIndex]">
|
||||
@@ -65,7 +65,7 @@
|
||||
</template>
|
||||
</v-switch>
|
||||
<v-btn class="ms-2" density="compact" color="default" variant="text" size="small"
|
||||
:icon="true" :disabled="loading || !!editingQuery || queries.length < 1"
|
||||
:icon="true" :disabled="loading || disabled || !!editingQuery || queries.length < 1"
|
||||
@click="removeQuery(queryIndex)">
|
||||
<v-icon :icon="mdiClose" size="18" />
|
||||
<v-tooltip activator="parent">{{ tt('Remove Query') }}</v-tooltip>
|
||||
@@ -102,7 +102,7 @@
|
||||
density="compact"
|
||||
item-title="displayName"
|
||||
item-value="value"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:items="[
|
||||
{ value: TransactionExplorerConditionRelation.And, displayName: tt('AND') },
|
||||
{ value: TransactionExplorerConditionRelation.Or, displayName: tt('OR') }
|
||||
@@ -116,7 +116,7 @@
|
||||
density="compact"
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:items="allTransactionExplorerConditionFields"
|
||||
@update:model-value="updateConditionField(queryIndex, conditionIndex, TransactionExplorerConditionField.valueOf($event))"
|
||||
v-model="conditionWithRelation.condition.field"
|
||||
@@ -127,7 +127,7 @@
|
||||
density="compact"
|
||||
item-title="name"
|
||||
item-value="value"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:items="getAllTransactionExplorerConditionOperators(conditionWithRelation.getSupportedOperators())"
|
||||
v-model="conditionWithRelation.condition.operator"
|
||||
/>
|
||||
@@ -138,7 +138,7 @@
|
||||
density="compact"
|
||||
item-title="displayName"
|
||||
item-value="type"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:placeholder="tt('None')"
|
||||
:items="[
|
||||
{ type: TransactionType.Expense, displayName: tt('Expense') },
|
||||
@@ -166,7 +166,7 @@
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:readonly="true"
|
||||
:disabled="loading || !!editingQuery || !hasAnyTransactionCategory"
|
||||
:disabled="loading || disabled || !!editingQuery || !hasAnyTransactionCategory"
|
||||
:placeholder="tt('None')"
|
||||
:model-value="getFilteredTransactionCategoriesDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
|
||||
@click="currentCondition = conditionWithRelation.condition; showFilterTransactionCategoriesDialog = true"
|
||||
@@ -180,7 +180,7 @@
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:readonly="true"
|
||||
:disabled="loading || !!editingQuery || !hasAnyAccount"
|
||||
:disabled="loading || disabled || !!editingQuery || !hasAnyAccount"
|
||||
:placeholder="tt('None')"
|
||||
:model-value="getFilteredAccountsDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
|
||||
@click="currentCondition = conditionWithRelation.condition; showFilterSourceAccountsDialog = true"
|
||||
@@ -194,7 +194,7 @@
|
||||
item-value="type"
|
||||
persistent-placeholder
|
||||
:readonly="true"
|
||||
:disabled="loading || !!editingQuery || !hasAnyAccount"
|
||||
:disabled="loading || disabled || !!editingQuery || !hasAnyAccount"
|
||||
:placeholder="tt('None')"
|
||||
:model-value="getFilteredAccountsDisplayContent(arrayItemToObjectField(conditionWithRelation.condition.value as string[], true))"
|
||||
@click="currentCondition = conditionWithRelation.condition; showFilterDestinationAccountsDialog = true"
|
||||
@@ -206,7 +206,7 @@
|
||||
conditionWithRelation.condition.field === TransactionExplorerConditionField.DestinationAmount.value">
|
||||
<amount-input density="compact"
|
||||
:currency="defaultCurrency"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
v-model="conditionWithRelation.condition.value[0]"
|
||||
/>
|
||||
<span class="ms-2 me-2"
|
||||
@@ -214,7 +214,7 @@
|
||||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.NotBetween.value">~</span>
|
||||
<amount-input density="compact"
|
||||
:currency="defaultCurrency"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
v-model="conditionWithRelation.condition.value[1]"
|
||||
v-if="conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.Between.value ||
|
||||
conditionWithRelation.condition.operator === TransactionExplorerConditionOperator.NotBetween.value"
|
||||
@@ -245,7 +245,7 @@
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:placeholder="tt('None')"
|
||||
:items="allTags"
|
||||
v-model="conditionWithRelation.condition.value"
|
||||
@@ -300,7 +300,7 @@
|
||||
/>
|
||||
|
||||
<v-text-field density="compact"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
:placeholder="tt('None')"
|
||||
v-model="conditionWithRelation.condition.value"
|
||||
v-else-if="conditionWithRelation.condition.field === TransactionExplorerConditionField.Description.value &&
|
||||
@@ -311,7 +311,7 @@
|
||||
<v-btn color="default" density="compact"
|
||||
variant="text" size="small"
|
||||
:icon="true"
|
||||
:disabled="loading || !!editingQuery"
|
||||
:disabled="loading || disabled || !!editingQuery"
|
||||
@click="removeCondition(queryIndex, conditionIndex)">
|
||||
<v-icon :icon="mdiClose" size="18" />
|
||||
<v-tooltip activator="parent">{{ tt('Remove Condition') }}</v-tooltip>
|
||||
@@ -328,7 +328,7 @@
|
||||
|
||||
<v-btn class="px-2" density="comfortable" color="primary" variant="text" size="small"
|
||||
:prepend-icon="mdiPlus"
|
||||
:disabled="loading || !!editingQuery || showExpression[queryIndex]"
|
||||
:disabled="loading || disabled || !!editingQuery || showExpression[queryIndex]"
|
||||
@click="addCondition(queryIndex)">
|
||||
{{ tt('Add Condition') }}
|
||||
</v-btn>
|
||||
@@ -421,6 +421,7 @@ import {
|
||||
|
||||
interface ExplorerQueryTabProps {
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
type SnackBarType = InstanceType<typeof SnackBar>;
|
||||
@@ -451,7 +452,7 @@ const tagSearchContent = ref<string>('');
|
||||
const editingQuery = ref<TransactionExplorerQuery | undefined>(undefined);
|
||||
const editingQueryName = ref<string>('');
|
||||
|
||||
const queries = computed<TransactionExplorerQuery[]>(() => explorersStore.transactionExplorerFilter.query);
|
||||
const queries = computed<TransactionExplorerQuery[]>(() => explorersStore.currentInsightsExplorer.queries);
|
||||
|
||||
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
|
||||
const hasAnyAccount = computed<boolean>(() => accountsStore.allPlainAccounts.length > 0);
|
||||
|
||||
Reference in New Issue
Block a user