mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 01:04:25 +08:00
add total amount in home page
This commit is contained in:
@@ -185,6 +185,9 @@ func startWebServer(c *cli.Context) error {
|
|||||||
apiV1Route.POST("/users/2fa/recovery/regenerate.json", bindApi(api.TwoFactorAuthorizations.TwoFactorRecoveryCodeRegenerateHandler))
|
apiV1Route.POST("/users/2fa/recovery/regenerate.json", bindApi(api.TwoFactorAuthorizations.TwoFactorRecoveryCodeRegenerateHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overview
|
||||||
|
apiV1Route.GET("/overviews/transaction.json", bindApi(api.Overviews.TransactionOverviewHandler))
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
apiV1Route.GET("/accounts/list.json", bindApi(api.Accounts.AccountListHandler))
|
apiV1Route.GET("/accounts/list.json", bindApi(api.Accounts.AccountListHandler))
|
||||||
apiV1Route.GET("/accounts/get.json", bindApi(api.Accounts.AccountGetHandler))
|
apiV1Route.GET("/accounts/get.json", bindApi(api.Accounts.AccountGetHandler))
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mayswind/lab/pkg/core"
|
||||||
|
"github.com/mayswind/lab/pkg/errs"
|
||||||
|
"github.com/mayswind/lab/pkg/log"
|
||||||
|
"github.com/mayswind/lab/pkg/models"
|
||||||
|
"github.com/mayswind/lab/pkg/services"
|
||||||
|
"github.com/mayswind/lab/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OverviewApi represents overview api
|
||||||
|
type OverviewApi struct {
|
||||||
|
transactions *services.TransactionService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize an overview api singleton instance
|
||||||
|
var (
|
||||||
|
Overviews = &OverviewApi{
|
||||||
|
transactions: services.Transactions,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionOverviewHandler returns transaction over of current user
|
||||||
|
func (a *OverviewApi) TransactionOverviewHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||||
|
var transactionOverviewReq models.TransactionOverviewRequest
|
||||||
|
err := c.ShouldBindQuery(&transactionOverviewReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := strings.Split(transactionOverviewReq.Query, "|")
|
||||||
|
requestItems := make([]*models.TransactionOverviewRequestItem, 0, len(items))
|
||||||
|
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
itemValues := strings.Split(items[i], "_")
|
||||||
|
|
||||||
|
if len(itemValues) != 3 {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request item failed, because its not valid item, content is \"%s\"", items[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime, err := utils.StringToInt64(itemValues[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request item start time failed, because %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime, err := utils.StringToInt64(itemValues[2])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request item end time failed, because %s", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
requestItem := &models.TransactionOverviewRequestItem{
|
||||||
|
Name: itemValues[0],
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestItems = append(requestItems, requestItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(requestItems) < 1 {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request failed, because there are no valid items")
|
||||||
|
return nil, errs.ErrQueryItemsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(requestItems) > 5 {
|
||||||
|
log.WarnfWithRequestId(c, "[overviews.TransactionOverviewHandler] parse request failed, because there are too many items")
|
||||||
|
return nil, errs.ErrQueryItemsTooMuch
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
|
||||||
|
overviewResp := make(map[string]*models.TransactionOverviewResponseItem)
|
||||||
|
|
||||||
|
for i := 0; i < len(requestItems); i++ {
|
||||||
|
requestItem := requestItems[i]
|
||||||
|
|
||||||
|
incomeAmount, expenseAmount, err := a.transactions.GetTotalIncomeAndExpenseByDateRange(uid, requestItem.StartTime, requestItem.EndTime)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorfWithRequestId(c, "[overviews.TransactionOverviewHandler] failed to get transaction overview item for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
overviewResp[requestItem.Name] = &models.TransactionOverviewResponseItem{
|
||||||
|
StartTime: requestItem.StartTime,
|
||||||
|
EndTime: requestItem.EndTime,
|
||||||
|
IncomeAmount: incomeAmount,
|
||||||
|
ExpenseAmount: expenseAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return overviewResp, nil
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ const (
|
|||||||
NormalSubcategoryCategory = 6
|
NormalSubcategoryCategory = 6
|
||||||
NormalSubcategoryTag = 7
|
NormalSubcategoryTag = 7
|
||||||
NormalSubcategoryDataManagement = 8
|
NormalSubcategoryDataManagement = 8
|
||||||
|
NormalSubcategoryOverview = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error represents the specific error returned to user
|
// Error represents the specific error returned to user
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package errs
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Error codes related to overview
|
||||||
|
var (
|
||||||
|
ErrQueryItemsEmpty = NewNormalError(NormalSubcategoryOverview, 0, http.StatusBadRequest, "query items cannot be empty")
|
||||||
|
ErrQueryItemsTooMuch = NewNormalError(NormalSubcategoryOverview, 1, http.StatusBadRequest, "query items too much")
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// TransactionOverviewRequest represents all parameters of transaction overview request
|
||||||
|
type TransactionOverviewRequest struct {
|
||||||
|
Query string `form:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionOverviewRequestItem represents an item of transaction overview request
|
||||||
|
type TransactionOverviewRequestItem struct {
|
||||||
|
Name string
|
||||||
|
StartTime int64
|
||||||
|
EndTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionOverviewResponseItem represents an item of transaction overview
|
||||||
|
type TransactionOverviewResponseItem struct {
|
||||||
|
StartTime int64 `json:"startTime"`
|
||||||
|
EndTime int64 `json:"endTime"`
|
||||||
|
IncomeAmount int64 `json:"incomeAmount"`
|
||||||
|
ExpenseAmount int64 `json:"expenseAmount"`
|
||||||
|
}
|
||||||
@@ -44,6 +44,12 @@ type Transaction struct {
|
|||||||
DeletedUnixTime int64
|
DeletedUnixTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TransactionTotalAmount struct {
|
||||||
|
Uid int64
|
||||||
|
Type TransactionDbType
|
||||||
|
TotalAmount int64 `xorm:"NOT NULL"`
|
||||||
|
}
|
||||||
|
|
||||||
// TransactionCreateRequest represents all parameters of transaction creation request
|
// TransactionCreateRequest represents all parameters of transaction creation request
|
||||||
type TransactionCreateRequest struct {
|
type TransactionCreateRequest struct {
|
||||||
Type TransactionType `json:"type" binding:"required"`
|
Type TransactionType `json:"type" binding:"required"`
|
||||||
|
|||||||
@@ -922,6 +922,38 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
|
|||||||
return relatedTransaction
|
return relatedTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTotalIncomeAndExpenseByDateRange returns the total income and expense amount by specific date range
|
||||||
|
func (s *TransactionService) GetTotalIncomeAndExpenseByDateRange(uid int64, startUnixTime int64, endUnixTime int64) (int64, int64, error) {
|
||||||
|
if uid <= 0 {
|
||||||
|
return 0, 0, errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
startTransactionTime := utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
|
||||||
|
endTransactionTime := utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
|
||||||
|
|
||||||
|
var transactionTotalAmounts []*models.Transaction
|
||||||
|
err := s.UserDataDB(uid).Select("uid, type, SUM(amount) as amount").Where("uid=? AND deleted=? AND (type=? OR type=?) AND transaction_time>=? AND transaction_time<=?", uid, false, models.TRANSACTION_DB_TYPE_INCOME, models.TRANSACTION_DB_TYPE_EXPENSE, startTransactionTime, endTransactionTime).GroupBy("type").Find(&transactionTotalAmounts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var incomeAmount int64
|
||||||
|
var expenseAmount int64
|
||||||
|
|
||||||
|
for i := 0; i < len(transactionTotalAmounts); i++ {
|
||||||
|
transactionTotalAmount := transactionTotalAmounts[i]
|
||||||
|
|
||||||
|
if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_INCOME {
|
||||||
|
incomeAmount = transactionTotalAmount.Amount
|
||||||
|
} else if transactionTotalAmount.Type == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
|
expenseAmount = transactionTotalAmount.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return incomeAmount, expenseAmount, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TransactionService) isAccountIdValid(transaction *models.Transaction) error {
|
func (s *TransactionService) isAccountIdValid(transaction *models.Transaction) error {
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
if transaction.RelatedAccountId != 0 && transaction.RelatedAccountId != transaction.AccountId {
|
||||||
|
|||||||
@@ -159,6 +159,27 @@ export default {
|
|||||||
password
|
password
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getTransactionOverview: ( { today, thisWeek, thisMonth, thisYear } ) => {
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
if (today) {
|
||||||
|
queryParams.push(`today_${today.startTime}_${today.endTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisWeek) {
|
||||||
|
queryParams.push(`thisWeek_${thisWeek.startTime}_${thisWeek.endTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisMonth) {
|
||||||
|
queryParams.push(`thisMonth_${thisMonth.startTime}_${thisMonth.endTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisYear) {
|
||||||
|
queryParams.push(`thisYear_${thisYear.startTime}_${thisYear.endTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.get('v1/overviews/transaction.json' + (queryParams.length ? '?query=' + queryParams.join('|') : ''));
|
||||||
|
},
|
||||||
getAllAccounts: ({ visibleOnly }) => {
|
getAllAccounts: ({ visibleOnly }) => {
|
||||||
return axios.get('v1/accounts/list.json?visible_only=' + !!visibleOnly);
|
return axios.get('v1/accounts/list.json?visible_only=' + !!visibleOnly);
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-1
@@ -10,7 +10,9 @@ export default {
|
|||||||
'format': {
|
'format': {
|
||||||
'date': {
|
'date': {
|
||||||
'long': 'MM/DD/YYYY',
|
'long': 'MM/DD/YYYY',
|
||||||
'yearMonth': 'YYYY-MM'
|
'year': 'YYYY',
|
||||||
|
'yearMonth': 'YYYY-M',
|
||||||
|
'monthDay': 'M/D'
|
||||||
},
|
},
|
||||||
'datetime': {
|
'datetime': {
|
||||||
'long': 'MM/DD/YYYY HH:mm:ss',
|
'long': 'MM/DD/YYYY HH:mm:ss',
|
||||||
@@ -362,6 +364,8 @@ export default {
|
|||||||
'transaction tag name is empty': 'Transaction tag title is empty',
|
'transaction tag name is empty': 'Transaction tag title is empty',
|
||||||
'transaction tag name already exists': 'Transaction tag title already exists',
|
'transaction tag name already exists': 'Transaction tag title already exists',
|
||||||
'transaction tag is in use and cannot be deleted': 'Transaction tag is in use and it cannot be deleted',
|
'transaction tag is in use and cannot be deleted': 'Transaction tag is in use and it cannot be deleted',
|
||||||
|
'query items cannot be empty': 'There are no query items',
|
||||||
|
'query items too much': 'There are too many query items',
|
||||||
},
|
},
|
||||||
'parameter': {
|
'parameter': {
|
||||||
'id': 'ID',
|
'id': 'ID',
|
||||||
@@ -496,6 +500,10 @@ export default {
|
|||||||
'Sign Up': 'Sign Up',
|
'Sign Up': 'Sign Up',
|
||||||
'Transaction List': 'Transaction List',
|
'Transaction List': 'Transaction List',
|
||||||
'Account List': 'Account List',
|
'Account List': 'Account List',
|
||||||
|
'This Week': 'This Week',
|
||||||
|
'This Month': 'This Month',
|
||||||
|
'This Year': 'This Year',
|
||||||
|
'Unable to get transaction overview': 'Unable to get transaction overview',
|
||||||
'Net assets': 'Net assets',
|
'Net assets': 'Net assets',
|
||||||
'Total assets': 'Total assets',
|
'Total assets': 'Total assets',
|
||||||
'Total liabilities': 'Total liabilities',
|
'Total liabilities': 'Total liabilities',
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ export default {
|
|||||||
'format': {
|
'format': {
|
||||||
'date': {
|
'date': {
|
||||||
'long': 'YYYY年MM月DD日',
|
'long': 'YYYY年MM月DD日',
|
||||||
'yearMonth': 'YYYY年MM月'
|
'year': 'YYYY年',
|
||||||
|
'yearMonth': 'YYYY年M月',
|
||||||
|
'monthDay': 'M月D日'
|
||||||
},
|
},
|
||||||
'datetime': {
|
'datetime': {
|
||||||
'long': 'YYYY年MM月DD日 HH:mm:ss',
|
'long': 'YYYY年MM月DD日 HH:mm:ss',
|
||||||
@@ -362,6 +364,8 @@ export default {
|
|||||||
'transaction tag name is empty': '交易标签标题不能为空',
|
'transaction tag name is empty': '交易标签标题不能为空',
|
||||||
'transaction tag name already exists': '交易标签标题已经存在',
|
'transaction tag name already exists': '交易标签标题已经存在',
|
||||||
'transaction tag is in use and cannot be deleted': '交易标签正在被使用,无法删除',
|
'transaction tag is in use and cannot be deleted': '交易标签正在被使用,无法删除',
|
||||||
|
'query items cannot be empty': '请求项目不能为空',
|
||||||
|
'query items too much': '请求项目过多',
|
||||||
},
|
},
|
||||||
'parameter': {
|
'parameter': {
|
||||||
'id': 'ID',
|
'id': 'ID',
|
||||||
@@ -496,6 +500,10 @@ export default {
|
|||||||
'Sign Up': '注册',
|
'Sign Up': '注册',
|
||||||
'Transaction List': '交易列表',
|
'Transaction List': '交易列表',
|
||||||
'Account List': '账户列表',
|
'Account List': '账户列表',
|
||||||
|
'This Week': '本周',
|
||||||
|
'This Month': '本月',
|
||||||
|
'This Year': '今年',
|
||||||
|
'Unable to get transaction overview': '无法获取交易概要',
|
||||||
'Net assets': '净资产',
|
'Net assets': '净资产',
|
||||||
'Total assets': '总资产',
|
'Total assets': '总资产',
|
||||||
'Total liabilities': '总负债',
|
'Total liabilities': '总负债',
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ import {
|
|||||||
UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST,
|
UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST,
|
||||||
REMOVE_TAG_FROM_TRANSACTION_TAG_LIST,
|
REMOVE_TAG_FROM_TRANSACTION_TAG_LIST,
|
||||||
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
|
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
|
||||||
|
|
||||||
|
LOAD_TRANSACTION_OVERVIEW,
|
||||||
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
} from './mutations.js';
|
} from './mutations.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -79,6 +82,10 @@ import {
|
|||||||
clearExchangeRatesFromLocalStorage,
|
clearExchangeRatesFromLocalStorage,
|
||||||
} from './exchangeRates.js';
|
} from './exchangeRates.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadTransactionOverview
|
||||||
|
} from './overview.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loadAllAccounts,
|
loadAllAccounts,
|
||||||
getAccount,
|
getAccount,
|
||||||
@@ -154,6 +161,8 @@ const stores = {
|
|||||||
allTransactionTags: [],
|
allTransactionTags: [],
|
||||||
allTransactionTagsMap: {},
|
allTransactionTagsMap: {},
|
||||||
transactionTagListStateInvalid: true,
|
transactionTagListStateInvalid: true,
|
||||||
|
transactionOverview: {},
|
||||||
|
transactionOverviewStateInvalid: true,
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
// user
|
// user
|
||||||
@@ -706,6 +715,12 @@ const stores = {
|
|||||||
[UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE] (state, invalidState) {
|
[UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE] (state, invalidState) {
|
||||||
state.transactionTagListStateInvalid = invalidState;
|
state.transactionTagListStateInvalid = invalidState;
|
||||||
},
|
},
|
||||||
|
[LOAD_TRANSACTION_OVERVIEW] (state, transactionOverview) {
|
||||||
|
state.transactionOverview = transactionOverview;
|
||||||
|
},
|
||||||
|
[UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE] (state, invalidState) {
|
||||||
|
state.transactionOverviewStateInvalid = invalidState;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// user
|
// user
|
||||||
@@ -734,6 +749,9 @@ const stores = {
|
|||||||
// exchange rates
|
// exchange rates
|
||||||
getLatestExchangeRates,
|
getLatestExchangeRates,
|
||||||
|
|
||||||
|
// overview
|
||||||
|
loadTransactionOverview,
|
||||||
|
|
||||||
// account
|
// account
|
||||||
loadAllAccounts,
|
loadAllAccounts,
|
||||||
saveAccount,
|
saveAccount,
|
||||||
|
|||||||
@@ -36,3 +36,6 @@ export const CHANGE_TAG_DISPLAY_ORDER_IN_TRANSACTION_TAG_LIST = 'CHANGE_TAG_DISP
|
|||||||
export const UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST = 'UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST';
|
export const UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST = 'UPDATE_TAG_VISIBILITY_IN_TRANSACTION_TAG_LIST';
|
||||||
export const REMOVE_TAG_FROM_TRANSACTION_TAG_LIST = 'REMOVE_TAG_FROM_TRANSACTION_TAG_LIST';
|
export const REMOVE_TAG_FROM_TRANSACTION_TAG_LIST = 'REMOVE_TAG_FROM_TRANSACTION_TAG_LIST';
|
||||||
export const UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE';
|
export const UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE';
|
||||||
|
|
||||||
|
export const LOAD_TRANSACTION_OVERVIEW = 'LOAD_TRANSACTION_OVERVIEW';
|
||||||
|
export const UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE = 'UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE';
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import services from '../lib/services.js';
|
||||||
|
import logger from '../lib/logger.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LOAD_TRANSACTION_OVERVIEW,
|
||||||
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
|
} from './mutations.js';
|
||||||
|
|
||||||
|
export function loadTransactionOverview(context, { dateRange, force }) {
|
||||||
|
if (!force && !context.state.transactionOverviewStateInvalid) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve(context.state.transactionOverview);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
services.getTransactionOverview({
|
||||||
|
today: dateRange.today,
|
||||||
|
thisWeek: dateRange.thisWeek,
|
||||||
|
thisMonth: dateRange.thisMonth,
|
||||||
|
thisYear: dateRange.thisYear
|
||||||
|
}).then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
reject({ message: 'Unable to get transaction overview' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.commit(LOAD_TRANSACTION_OVERVIEW, data.result);
|
||||||
|
|
||||||
|
if (context.state.transactionOverviewStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(data.result);
|
||||||
|
}).catch(error => {
|
||||||
|
if (force) {
|
||||||
|
logger.error('failed to force load transaction overview', error);
|
||||||
|
} else {
|
||||||
|
logger.error('failed to load transaction overview', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
reject({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
reject({ message: 'Unable to get transaction overview' });
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
REMOVE_TRANSACTION_FROM_TRANSACTION_LIST,
|
REMOVE_TRANSACTION_FROM_TRANSACTION_LIST,
|
||||||
UPDATE_TRANSACTION_LIST_INVALID_STATE,
|
UPDATE_TRANSACTION_LIST_INVALID_STATE,
|
||||||
UPDATE_ACCOUNT_LIST_INVALID_STATE,
|
UPDATE_ACCOUNT_LIST_INVALID_STATE,
|
||||||
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
} from './mutations.js';
|
} from './mutations.js';
|
||||||
|
|
||||||
const emptyTransactionResult = {
|
const emptyTransactionResult = {
|
||||||
@@ -169,6 +170,7 @@ export function saveTransaction(context, { transaction, defaultCurrency }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
|
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
|
||||||
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -216,6 +218,7 @@ export function deleteTransaction(context, { transaction, defaultCurrency, befor
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
|
context.commit(UPDATE_ACCOUNT_LIST_INVALID_STATE, true);
|
||||||
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
|||||||
+285
-5
@@ -1,12 +1,184 @@
|
|||||||
<template>
|
<template>
|
||||||
<f7-page>
|
<f7-page ptr @ptr:refresh="reload" @page:afterin="onPageAfterIn">
|
||||||
<f7-navbar>
|
<f7-navbar>
|
||||||
<f7-nav-title :title="$t('global.app.title')"></f7-nav-title>
|
<f7-nav-title :title="$t('global.app.title')"></f7-nav-title>
|
||||||
</f7-navbar>
|
</f7-navbar>
|
||||||
|
|
||||||
<f7-list media-list class="skeleton-text">
|
<f7-card class="skeleton-text" v-if="loading">
|
||||||
<f7-list-item title="Placeholder"></f7-list-item>
|
<f7-card-content class="no-safe-areas" :padding="false">
|
||||||
</f7-list>
|
<f7-list>
|
||||||
|
<f7-list-item link="#" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar_today"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>Today</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>MM/DD/YYYY</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="#" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>This week</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>MM/DD - MM/DD</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="#" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>This month</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>MM/DD - MM/DD</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="#" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="square_stack_3d_up"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>This year</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>YYYY</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small>0.00 USD</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
</f7-card-content>
|
||||||
|
</f7-card>
|
||||||
|
|
||||||
|
<f7-card v-else-if="!loading">
|
||||||
|
<f7-card-content class="no-safe-areas" :padding="false">
|
||||||
|
<f7-list>
|
||||||
|
<f7-list-item link="/transaction/list?dateType=1" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar_today"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>{{ $t('Today' )}}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>{{ dateRange.today.startTime | moment($t('format.date.long')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small v-if="transactionOverview.today">{{ transactionOverview.today.incomeAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small v-if="transactionOverview.today">{{ transactionOverview.today.expenseAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="/transaction/list?dateType=5" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>{{ $t('This Week' )}}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>{{ dateRange.thisWeek.startTime | moment($t('format.date.monthDay')) }}</span>
|
||||||
|
<span>-</span>
|
||||||
|
<span>{{ dateRange.thisWeek.endTime | moment($t('format.date.monthDay')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small v-if="transactionOverview.thisWeek">{{ transactionOverview.thisWeek.incomeAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small v-if="transactionOverview.thisWeek">{{ transactionOverview.thisWeek.expenseAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="/transaction/list?dateType=7" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="calendar"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>{{ $t('This Month' )}}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>{{ dateRange.thisMonth.startTime | moment($t('format.date.monthDay')) }}</span>
|
||||||
|
<span>-</span>
|
||||||
|
<span>{{ dateRange.thisMonth.endTime | moment($t('format.date.monthDay')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small v-if="transactionOverview.thisMonth">{{ transactionOverview.thisMonth.incomeAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small v-if="transactionOverview.thisMonth">{{ transactionOverview.thisMonth.expenseAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item link="/transaction/list?dateType=9" chevron-center>
|
||||||
|
<div slot="media">
|
||||||
|
<f7-icon f7="square_stack_3d_up"></f7-icon>
|
||||||
|
</div>
|
||||||
|
<div slot="title" class="no-padding">
|
||||||
|
<span>{{ $t('This Year' )}}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="overview-transaction-footer">
|
||||||
|
<span>{{ dateRange.thisYear.startTime | moment($t('format.date.year')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div slot="after">
|
||||||
|
<div class="text-color-red">
|
||||||
|
<small v-if="transactionOverview.thisYear">{{ transactionOverview.thisYear.incomeAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-color-teal">
|
||||||
|
<small v-if="transactionOverview.thisYear">{{ transactionOverview.thisYear.expenseAmount | currency(defaultCurrency) }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</f7-list-item>
|
||||||
|
</f7-list>
|
||||||
|
</f7-card-content>
|
||||||
|
</f7-card>
|
||||||
|
|
||||||
<f7-toolbar tabbar labels bottom>
|
<f7-toolbar tabbar labels bottom>
|
||||||
<f7-link href="/transaction/list">
|
<f7-link href="/transaction/list">
|
||||||
@@ -33,10 +205,118 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {}
|
export default {
|
||||||
|
data() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
dateRange: self.getCurrentDateRange(),
|
||||||
|
loading: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
transactionOverview() {
|
||||||
|
return this.$store.state.transactionOverview;
|
||||||
|
},
|
||||||
|
defaultCurrency() {
|
||||||
|
return this.$store.getters.currentUserDefaultCurrency || this.$t('default.currency');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.loading = true;
|
||||||
|
|
||||||
|
self.$store.dispatch('loadTransactionOverview', {
|
||||||
|
dateRange: self.dateRange,
|
||||||
|
force: false
|
||||||
|
}).then(() => {
|
||||||
|
self.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.loading = false;
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onPageAfterIn() {
|
||||||
|
const newDateRange = this.getCurrentDateRange();
|
||||||
|
|
||||||
|
if (newDateRange.today.startTime !== this.dateRange.today.startTime ||
|
||||||
|
newDateRange.today.endTime !== this.dateRange.today.endTime) {
|
||||||
|
this.dateRange = newDateRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$store.state.transactionOverviewStateInvalid && !this.loading) {
|
||||||
|
this.reload(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reload(done) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$store.dispatch('loadTransactionOverview', {
|
||||||
|
dateRange: self.dateRange,
|
||||||
|
force: true
|
||||||
|
}).then(() => {
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCurrentDateRange() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
today: {
|
||||||
|
startTime: self.$utilities.getTodayFirstUnixTime(),
|
||||||
|
endTime: self.$utilities.getTodayLastUnixTime()
|
||||||
|
},
|
||||||
|
thisWeek: {
|
||||||
|
startTime: self.$utilities.getThisWeekFirstUnixTime(),
|
||||||
|
endTime: self.$utilities.getThisWeekLastUnixTime()
|
||||||
|
},
|
||||||
|
thisMonth: {
|
||||||
|
startTime: self.$utilities.getThisMonthFirstUnixTime(),
|
||||||
|
endTime: self.$utilities.getThisMonthLastUnixTime()
|
||||||
|
},
|
||||||
|
thisYear: {
|
||||||
|
startTime: self.$utilities.getThisYearFirstUnixTime(),
|
||||||
|
endTime: self.$utilities.getThisYearLastUnixTime()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.home-overview-card {
|
||||||
|
background-color: var(--f7-color-yellow);
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .home-overview-card {
|
||||||
|
background-color: var(--f7-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-transaction-footer {
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-transaction-footer > span {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.tabbar-labels i.lab-tarbar-big-icon {
|
.tabbar-labels i.lab-tarbar-big-icon {
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
width: 42px;
|
width: 42px;
|
||||||
|
|||||||
Reference in New Issue
Block a user