mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
add parts of statistics ui
This commit is contained in:
Generated
+47
-2
@@ -4623,6 +4623,22 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"echarts": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-JYn22Dolt2esY2jEzUsw1OxbobuW67oGjIoTjZO3rW89SWkfJ4kbrmC2OW9JjsBrD1rdkmaWBuZZ2HgmThyxJw==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.0.3",
|
||||||
|
"zrender": "5.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ee-first": {
|
"ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -7058,8 +7074,7 @@
|
|||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"lodash.defaultsdeep": {
|
"lodash.defaultsdeep": {
|
||||||
"version": "4.6.1",
|
"version": "4.6.1",
|
||||||
@@ -9346,6 +9361,11 @@
|
|||||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"resize-detector": {
|
||||||
|
"version": "0.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/resize-detector/-/resize-detector-0.1.10.tgz",
|
||||||
|
"integrity": "sha512-iLcXC8A6Fb0DfA+TRiywrK/0A22bFqkhntjMJMEzXDA4XkcEkfwpNbv7W8iewUiD0xYIaeiXOfiEehTqGKsUFw=="
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.17.0",
|
"version": "1.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
|
||||||
@@ -11081,6 +11101,16 @@
|
|||||||
"clipboard": "^2.0.0"
|
"clipboard": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-echarts": {
|
||||||
|
"version": "5.0.0-beta.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-5.0.0-beta.0.tgz",
|
||||||
|
"integrity": "sha512-QZFKGXDAYFQo+F20REpzcdLx79nsl4kOorJRpN+08aYq4YiIlmtWss1Lxadm7Fo+NYyWm8nnT+h4xHv3uqWIDQ==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^3.4.4",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"resize-detector": "^0.1.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz",
|
||||||
@@ -12144,6 +12174,21 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"zrender": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-TVcN2IMdo7je3GEq/E4CER4AGBe/n50/izILdupppyHf/hVHuiXCRliqdu8+32Z1OmGg6RfKt5qQlkX+bOtU0g==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"cbor-js": "^0.1.0",
|
"cbor-js": "^0.1.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.0.0",
|
||||||
|
"echarts": "^5.0.1",
|
||||||
"framework7": "^5.7.14",
|
"framework7": "^5.7.14",
|
||||||
"framework7-icons": "^3.0.1",
|
"framework7-icons": "^3.0.1",
|
||||||
"framework7-vue": "^5.7.14",
|
"framework7-vue": "^5.7.14",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"ua-parser-js": "^0.7.22",
|
"ua-parser-js": "^0.7.22",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue-clipboard2": "^0.3.1",
|
"vue-clipboard2": "^0.3.1",
|
||||||
|
"vue-echarts": "^5.0.0-beta.0",
|
||||||
"vue-i18n": "^8.22.1",
|
"vue-i18n": "^8.22.1",
|
||||||
"vue-i18n-filter": "^0.1.6",
|
"vue-i18n-filter": "^0.1.6",
|
||||||
"vue-moment": "^4.1.0",
|
"vue-moment": "^4.1.0",
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const allChartTypes = {
|
||||||
|
Pie: 0,
|
||||||
|
Bar: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultChartType = allChartTypes.Pie;
|
||||||
|
|
||||||
|
const allChartLegendTypes = {
|
||||||
|
Account: 0,
|
||||||
|
PrimaryCategory: 1,
|
||||||
|
SecondaryCategory: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultChartLegendType = allChartLegendTypes.SecondaryCategory;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
allChartTypes: allChartTypes,
|
||||||
|
defaultChartType: defaultChartType,
|
||||||
|
allChartLegendTypes: allChartLegendTypes,
|
||||||
|
defaultChartLegendType: defaultChartLegendType,
|
||||||
|
};
|
||||||
@@ -185,6 +185,19 @@ export default {
|
|||||||
|
|
||||||
return axios.get('v1/overviews/transaction.json' + (queryParams.length ? '?query=' + queryParams.join('|') : ''));
|
return axios.get('v1/overviews/transaction.json' + (queryParams.length ? '?query=' + queryParams.join('|') : ''));
|
||||||
},
|
},
|
||||||
|
getTransactionStatistics: ({ startTime, endTime }) => {
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
if (startTime) {
|
||||||
|
queryParams.push(`start_time=${startTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTime) {
|
||||||
|
queryParams.push(`end_time=${endTime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.get('v1/statistics/transaction.json' + (queryParams.length ? '?' + 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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import VueI18n from 'vue-i18n';
|
|||||||
import VueI18nFilter from 'vue-i18n-filter';
|
import VueI18nFilter from 'vue-i18n-filter';
|
||||||
import Framework7 from 'framework7/framework7.esm.bundle.js';
|
import Framework7 from 'framework7/framework7.esm.bundle.js';
|
||||||
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js';
|
import Framework7Vue from 'framework7-vue/framework7-vue.esm.bundle.js';
|
||||||
|
import ECharts from 'vue-echarts';
|
||||||
import PincodeInput from 'vue-pincode-input';
|
import PincodeInput from 'vue-pincode-input';
|
||||||
import VueMoment from 'vue-moment';
|
import VueMoment from 'vue-moment';
|
||||||
import VueClipboard from 'vue-clipboard2';
|
import VueClipboard from 'vue-clipboard2';
|
||||||
@@ -15,6 +16,13 @@ import 'framework7-icons';
|
|||||||
|
|
||||||
import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
||||||
|
|
||||||
|
import 'echarts/lib/chart/line';
|
||||||
|
import 'echarts/lib/chart/pie';
|
||||||
|
import 'echarts/lib/chart/bar';
|
||||||
|
import 'echarts/lib/component/legend';
|
||||||
|
import 'echarts/lib/component/title';
|
||||||
|
import 'echarts/lib/component/tooltip';
|
||||||
|
|
||||||
import { getAllLanguages, getLanguage, getDefaultLanguage, getI18nOptions, getLocalizedError, getLocalizedErrorParameters } from './lib/i18n.js';
|
import { getAllLanguages, getLanguage, getDefaultLanguage, getI18nOptions, getLocalizedError, getLocalizedErrorParameters } from './lib/i18n.js';
|
||||||
import api from './consts/api.js';
|
import api from './consts/api.js';
|
||||||
import datetime from './consts/datetime.js';
|
import datetime from './consts/datetime.js';
|
||||||
@@ -24,6 +32,7 @@ import icons from './consts/icon.js';
|
|||||||
import account from './consts/account.js';
|
import account from './consts/account.js';
|
||||||
import transaction from './consts/transaction.js';
|
import transaction from './consts/transaction.js';
|
||||||
import category from './consts/category.js';
|
import category from './consts/category.js';
|
||||||
|
import statistics from './consts/statistics.js';
|
||||||
import licenses from './lib/licenses.js';
|
import licenses from './lib/licenses.js';
|
||||||
import version from './lib/version.js';
|
import version from './lib/version.js';
|
||||||
import logger from './lib/logger.js';
|
import logger from './lib/logger.js';
|
||||||
@@ -62,6 +71,7 @@ Vue.use(VueI18n);
|
|||||||
Vue.use(VueI18nFilter);
|
Vue.use(VueI18nFilter);
|
||||||
Vue.use(VueMoment, { moment });
|
Vue.use(VueMoment, { moment });
|
||||||
Vue.use(VueClipboard);
|
Vue.use(VueClipboard);
|
||||||
|
Vue.component('v-chart', ECharts);
|
||||||
Vue.component('PincodeInput', PincodeInput);
|
Vue.component('PincodeInput', PincodeInput);
|
||||||
Vue.component('PasswordInputSheet', PasswordInputSheet);
|
Vue.component('PasswordInputSheet', PasswordInputSheet);
|
||||||
Vue.component('PasscodeInputSheet', PasscodeInputSheet);
|
Vue.component('PasscodeInputSheet', PasscodeInputSheet);
|
||||||
@@ -92,6 +102,7 @@ Vue.prototype.$constants = {
|
|||||||
account: account,
|
account: account,
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
category: category,
|
category: category,
|
||||||
|
statistics: statistics,
|
||||||
};
|
};
|
||||||
|
|
||||||
Vue.prototype.$utilities = utils;
|
Vue.prototype.$utilities = utils;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import TransactionEditPage from '../views/mobile/transactions/Edit.vue';
|
|||||||
import AccountListPage from '../views/mobile/accounts/List.vue';
|
import AccountListPage from '../views/mobile/accounts/List.vue';
|
||||||
import AccountEditPage from '../views/mobile/accounts/Edit.vue';
|
import AccountEditPage from '../views/mobile/accounts/Edit.vue';
|
||||||
|
|
||||||
import StatisticsOverviewPage from '../views/mobile/statistics/Overview.vue';
|
import StatisticsTransactionPage from '../views/mobile/statistics/Transaction.vue';
|
||||||
|
|
||||||
import SettingsPage from '../views/mobile/Settings.vue';
|
import SettingsPage from '../views/mobile/Settings.vue';
|
||||||
import ApplicationLockPage from '../views/mobile/ApplicationLock.vue';
|
import ApplicationLockPage from '../views/mobile/ApplicationLock.vue';
|
||||||
@@ -171,8 +171,8 @@ const routes = [
|
|||||||
beforeEnter: checkLogin
|
beforeEnter: checkLogin
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/statistic/overview',
|
path: '/statistic/transaction',
|
||||||
component: StatisticsOverviewPage,
|
component: StatisticsTransactionPage,
|
||||||
beforeEnter: checkLogin
|
beforeEnter: checkLogin
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import datetimeConstants from "../consts/datetime.js";
|
import datetimeConstants from "../consts/datetime.js";
|
||||||
|
import statisticsConstants from "../consts/statistics.js";
|
||||||
import userState from "../lib/userstate.js";
|
import userState from "../lib/userstate.js";
|
||||||
import utils from "../lib/utils.js";
|
import utils from "../lib/utils.js";
|
||||||
|
|
||||||
@@ -44,6 +45,11 @@ import {
|
|||||||
|
|
||||||
LOAD_TRANSACTION_OVERVIEW,
|
LOAD_TRANSACTION_OVERVIEW,
|
||||||
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
|
|
||||||
|
LOAD_TRANSACTION_STATISTICS,
|
||||||
|
INIT_TRANSACTION_STATISTICS_FILTER,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_FILTER,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE,
|
||||||
} from './mutations.js';
|
} from './mutations.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -88,6 +94,12 @@ import {
|
|||||||
loadTransactionOverview
|
loadTransactionOverview
|
||||||
} from './overview.js';
|
} from './overview.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadTransactionStatistics,
|
||||||
|
initTransactionStatisticsFilter,
|
||||||
|
updateTransactionStatisticsFilter
|
||||||
|
} from './statistics.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loadAllAccounts,
|
loadAllAccounts,
|
||||||
getAccount,
|
getAccount,
|
||||||
@@ -165,6 +177,15 @@ const stores = {
|
|||||||
transactionTagListStateInvalid: true,
|
transactionTagListStateInvalid: true,
|
||||||
transactionOverview: {},
|
transactionOverview: {},
|
||||||
transactionOverviewStateInvalid: true,
|
transactionOverviewStateInvalid: true,
|
||||||
|
transactionStatisticsFilter: {
|
||||||
|
dateType: datetimeConstants.allDateRanges.ThisMonth.type,
|
||||||
|
startTime: 0,
|
||||||
|
endTime: 0,
|
||||||
|
chartType: statisticsConstants.defaultChartType,
|
||||||
|
chartLegendType: statisticsConstants.defaultChartLegendType,
|
||||||
|
},
|
||||||
|
transactionStatistics: [],
|
||||||
|
transactionStatisticsStateInvalid: true,
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
// user
|
// user
|
||||||
@@ -216,6 +237,14 @@ const stores = {
|
|||||||
state.transactionOverview = {};
|
state.transactionOverview = {};
|
||||||
state.transactionOverviewStateInvalid = true;
|
state.transactionOverviewStateInvalid = true;
|
||||||
|
|
||||||
|
state.transactionStatisticsFilter.dateType = datetimeConstants.allDateRanges.ThisMonth.type;
|
||||||
|
state.transactionStatisticsFilter.startTime = 0;
|
||||||
|
state.transactionStatisticsFilter.endTime = 0;
|
||||||
|
state.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
|
||||||
|
state.transactionStatisticsFilter.chartLegendType = statisticsConstants.defaultChartLegendType;
|
||||||
|
state.transactionStatistics = {};
|
||||||
|
state.transactionStatisticsStateInvalid = true;
|
||||||
|
|
||||||
clearExchangeRatesFromLocalStorage();
|
clearExchangeRatesFromLocalStorage();
|
||||||
},
|
},
|
||||||
[STORE_USER_INFO] (state, userInfo) {
|
[STORE_USER_INFO] (state, userInfo) {
|
||||||
@@ -731,6 +760,86 @@ const stores = {
|
|||||||
[UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE] (state, invalidState) {
|
[UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE] (state, invalidState) {
|
||||||
state.transactionOverviewStateInvalid = invalidState;
|
state.transactionOverviewStateInvalid = invalidState;
|
||||||
},
|
},
|
||||||
|
[LOAD_TRANSACTION_STATISTICS] (state, { statistics, defaultCurrency }) {
|
||||||
|
if (statistics && statistics.items && statistics.items.length) {
|
||||||
|
for (let i = 0; i < statistics.items.length; i++) {
|
||||||
|
const item = statistics.items[i];
|
||||||
|
|
||||||
|
if (item.accountId) {
|
||||||
|
item.account = state.allAccountsMap[item.accountId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.categoryId) {
|
||||||
|
item.category = state.allTransactionCategoriesMap[item.categoryId];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.account && item.account.currency !== defaultCurrency) {
|
||||||
|
item.amountInDefaultCurrency = getExchangedAmount(state)(item.amount, item.account.currency, defaultCurrency);
|
||||||
|
} else if (item.account && item.account.currency === defaultCurrency) {
|
||||||
|
item.amountInDefaultCurrency = item.amount;
|
||||||
|
} else {
|
||||||
|
item.amountInDefaultCurrency = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.transactionStatistics = statistics;
|
||||||
|
},
|
||||||
|
[INIT_TRANSACTION_STATISTICS_FILTER] (state, filter) {
|
||||||
|
if (filter && utils.isNumber(filter.dateType)) {
|
||||||
|
state.transactionStatisticsFilter.dateType = filter.dateType;
|
||||||
|
} else {
|
||||||
|
state.transactionStatisticsFilter.dateType = datetimeConstants.allDateRanges.ThisMonth.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.startTime)) {
|
||||||
|
state.transactionStatisticsFilter.startTime = filter.startTime;
|
||||||
|
} else {
|
||||||
|
state.transactionStatisticsFilter.startTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.endTime)) {
|
||||||
|
state.transactionStatisticsFilter.endTime = filter.endTime;
|
||||||
|
} else {
|
||||||
|
state.transactionStatisticsFilter.endTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.chartType)) {
|
||||||
|
state.transactionStatisticsFilter.chartType = filter.chartType;
|
||||||
|
} else {
|
||||||
|
state.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.chartLegendType)) {
|
||||||
|
state.transactionStatisticsFilter.chartLegendType = filter.chartLegendType;
|
||||||
|
} else {
|
||||||
|
state.transactionStatisticsFilter.chartLegendType = statisticsConstants.defaultChartLegendType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[UPDATE_TRANSACTION_STATISTICS_FILTER] (state, filter) {
|
||||||
|
if (filter && utils.isNumber(filter.dateType)) {
|
||||||
|
state.transactionStatisticsFilter.dateType = filter.dateType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.startTime)) {
|
||||||
|
state.transactionStatisticsFilter.startTime = filter.startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.endTime)) {
|
||||||
|
state.transactionStatisticsFilter.endTime = filter.endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.chartType)) {
|
||||||
|
state.transactionStatisticsFilter.chartType = filter.chartType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter && utils.isNumber(filter.chartLegendType)) {
|
||||||
|
state.transactionStatisticsFilter.chartLegendType = filter.chartLegendType;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[UPDATE_TRANSACTION_STATISTICS_INVALID_STATE] (state, invalidState) {
|
||||||
|
state.transactionStatisticsStateInvalid = invalidState;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// user
|
// user
|
||||||
@@ -763,6 +872,11 @@ const stores = {
|
|||||||
// overview
|
// overview
|
||||||
loadTransactionOverview,
|
loadTransactionOverview,
|
||||||
|
|
||||||
|
// statistics
|
||||||
|
loadTransactionStatistics,
|
||||||
|
initTransactionStatisticsFilter,
|
||||||
|
updateTransactionStatisticsFilter,
|
||||||
|
|
||||||
// account
|
// account
|
||||||
loadAllAccounts,
|
loadAllAccounts,
|
||||||
saveAccount,
|
saveAccount,
|
||||||
|
|||||||
@@ -39,3 +39,8 @@ export const UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE = 'UPDATE_TRANSACTION_TAG
|
|||||||
|
|
||||||
export const LOAD_TRANSACTION_OVERVIEW = 'LOAD_TRANSACTION_OVERVIEW';
|
export const LOAD_TRANSACTION_OVERVIEW = 'LOAD_TRANSACTION_OVERVIEW';
|
||||||
export const UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE = 'UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE';
|
export const UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE = 'UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE';
|
||||||
|
|
||||||
|
export const LOAD_TRANSACTION_STATISTICS = 'LOAD_TRANSACTION_STATISTICS';
|
||||||
|
export const INIT_TRANSACTION_STATISTICS_FILTER = 'INIT_TRANSACTION_STATISTICS_FILTER';
|
||||||
|
export const UPDATE_TRANSACTION_STATISTICS_FILTER = 'UPDATE_TRANSACTION_STATISTICS_FILTER';
|
||||||
|
export const UPDATE_TRANSACTION_STATISTICS_INVALID_STATE = 'UPDATE_TRANSACTION_STATISTICS_INVALID_STATE';
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import services from '../lib/services.js';
|
||||||
|
import logger from '../lib/logger.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LOAD_TRANSACTION_STATISTICS,
|
||||||
|
INIT_TRANSACTION_STATISTICS_FILTER,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_FILTER,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE
|
||||||
|
} from "./mutations.js";
|
||||||
|
|
||||||
|
export function loadTransactionStatistics(context, { defaultCurrency }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
services.getTransactionStatistics({
|
||||||
|
startTime: context.state.transactionStatisticsFilter.startTime,
|
||||||
|
endTime: context.state.transactionStatisticsFilter.endTime
|
||||||
|
}).then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
reject({ message: 'Unable to get transaction statistics' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.commit(LOAD_TRANSACTION_STATISTICS, {
|
||||||
|
statistics: data.result,
|
||||||
|
defaultCurrency: defaultCurrency
|
||||||
|
});
|
||||||
|
|
||||||
|
if (context.state.transactionStatisticsStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(data.result);
|
||||||
|
}).catch(error => {
|
||||||
|
logger.error('failed to get transaction statistics', error);
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
reject({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
reject({ message: 'Unable to get transaction statistics' });
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initTransactionStatisticsFilter(context, filter) {
|
||||||
|
context.commit(INIT_TRANSACTION_STATISTICS_FILTER, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTransactionStatisticsFilter(context, filter) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_FILTER, filter);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
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,
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE,
|
||||||
} from './mutations.js';
|
} from './mutations.js';
|
||||||
|
|
||||||
const emptyTransactionResult = {
|
const emptyTransactionResult = {
|
||||||
@@ -177,6 +178,10 @@ export function saveTransaction(context, { transaction, defaultCurrency }) {
|
|||||||
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.state.transactionStatisticsStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
logger.error('failed to save transaction', error);
|
logger.error('failed to save transaction', error);
|
||||||
@@ -230,6 +235,10 @@ export function deleteTransaction(context, { transaction, defaultCurrency, befor
|
|||||||
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.state.transactionStatisticsStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
logger.error('failed to delete transaction', error);
|
logger.error('failed to delete transaction', error);
|
||||||
|
|||||||
+10
-1
@@ -13,7 +13,8 @@ import {
|
|||||||
UPDATE_ACCOUNT_LIST_INVALID_STATE,
|
UPDATE_ACCOUNT_LIST_INVALID_STATE,
|
||||||
UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE,
|
UPDATE_TRANSACTION_CATEGORY_LIST_INVALID_STATE,
|
||||||
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
|
UPDATE_TRANSACTION_TAG_LIST_INVALID_STATE,
|
||||||
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE
|
UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE,
|
||||||
|
UPDATE_TRANSACTION_STATISTICS_INVALID_STATE
|
||||||
} from './mutations.js';
|
} from './mutations.js';
|
||||||
|
|
||||||
export function authorize(context, { loginName, password }) {
|
export function authorize(context, { loginName, password }) {
|
||||||
@@ -258,6 +259,10 @@ export function updateUserProfile(context, { profile, currentPassword }) {
|
|||||||
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.state.transactionStatisticsStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
logger.error('failed to save user profile', error);
|
logger.error('failed to save user profile', error);
|
||||||
@@ -301,6 +306,10 @@ export function clearUserData(context, { password }) {
|
|||||||
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
context.commit(UPDATE_TRANSACTION_OVERVIEW_INVALID_STATE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.state.transactionStatisticsStateInvalid) {
|
||||||
|
context.commit(UPDATE_TRANSACTION_STATISTICS_INVALID_STATE, true);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(data.result);
|
resolve(data.result);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
logger.error('failed to clear user data', error);
|
logger.error('failed to clear user data', error);
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
<f7-link href="/transaction/add">
|
<f7-link href="/transaction/add">
|
||||||
<f7-icon f7="plus_square" class="lab-tarbar-big-icon"></f7-icon>
|
<f7-icon f7="plus_square" class="lab-tarbar-big-icon"></f7-icon>
|
||||||
</f7-link>
|
</f7-link>
|
||||||
<f7-link href="/statistic/overview">
|
<f7-link href="/statistic/transaction">
|
||||||
<f7-icon f7="chart_pie"></f7-icon>
|
<f7-icon f7="chart_pie"></f7-icon>
|
||||||
<span class="tabbar-label">{{ $t('Statistics') }}</span>
|
<span class="tabbar-label">{{ $t('Statistics') }}</span>
|
||||||
</f7-link>
|
</f7-link>
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<template>
|
|
||||||
<f7-page>
|
|
||||||
<f7-navbar :title="$t('Statistics')" :back-link="$t('Back')"></f7-navbar>
|
|
||||||
|
|
||||||
<f7-list media-list class="skeleton-text">
|
|
||||||
<f7-list-item title="Placeholder"></f7-list-item>
|
|
||||||
</f7-list>
|
|
||||||
</f7-page>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<f7-page>
|
||||||
|
<f7-navbar :title="$t('Statistics')" :back-link="$t('Back')"></f7-navbar>
|
||||||
|
|
||||||
|
<f7-card>
|
||||||
|
<f7-card-content class="no-safe-areas chart-container" :padding="false">
|
||||||
|
<v-chart :options="chartData" v-if="chartData" />
|
||||||
|
</f7-card-content>
|
||||||
|
</f7-card>
|
||||||
|
</f7-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
defaultCurrency() {
|
||||||
|
if (this.query.accountId && this.query.accountId !== '0') {
|
||||||
|
const account = this.allAccounts[this.query.accountId];
|
||||||
|
|
||||||
|
if (account && account.currency && account.currency !== this.$constants.currency.parentAccountCurrencyPlaceholder) {
|
||||||
|
return account.currency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.$store.getters.currentUserDefaultCurrency || this.$t('default.currency');
|
||||||
|
},
|
||||||
|
query() {
|
||||||
|
return this.$store.state.transactionStatisticsFilter;
|
||||||
|
},
|
||||||
|
chartData() {
|
||||||
|
if (!this.$store.state.transactionStatistics ||
|
||||||
|
!this.$store.state.transactionStatistics.items ||
|
||||||
|
!this.$store.state.transactionStatistics.items.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedData = {};
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.$store.state.transactionStatistics.items.length; i++) {
|
||||||
|
const item = this.$store.state.transactionStatistics.items[i];
|
||||||
|
|
||||||
|
if (!item.account || !item.category) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.category.type !== this.$constants.category.allCategoryTypes.Expense) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.query.chartLegendType === this.$constants.statistics.allChartLegendTypes.Account) {
|
||||||
|
if (this.$utilities.isNumber(item.amountInDefaultCurrency)) {
|
||||||
|
let totalAmount = combinedData[item.account.name];
|
||||||
|
|
||||||
|
if (totalAmount) {
|
||||||
|
totalAmount += totalAmount = item.amountInDefaultCurrency;
|
||||||
|
} else {
|
||||||
|
totalAmount = item.amountInDefaultCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedData[item.account.name] = totalAmount;
|
||||||
|
}
|
||||||
|
} else if (this.query.chartLegendType === this.$constants.statistics.allChartLegendTypes.SecondaryCategory) {
|
||||||
|
if (this.$utilities.isNumber(item.amountInDefaultCurrency)) {
|
||||||
|
let totalAmount = combinedData[item.category.name];
|
||||||
|
|
||||||
|
if (totalAmount) {
|
||||||
|
totalAmount += totalAmount = item.amountInDefaultCurrency;
|
||||||
|
} else {
|
||||||
|
totalAmount = item.amountInDefaultCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedData[item.category.name] = totalAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let chartType = 'pie';
|
||||||
|
|
||||||
|
if (this.query.chartType === this.$constants.statistics.allChartTypes.Bar) {
|
||||||
|
chartType = 'bar';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let legendName in combinedData) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(combinedData, legendName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
name: legendName,
|
||||||
|
value: combinedData[legendName] / 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: chartType,
|
||||||
|
data: data,
|
||||||
|
label: {
|
||||||
|
position: 'inside'
|
||||||
|
},
|
||||||
|
animation: false,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const self = this;
|
||||||
|
const router = self.$f7router;
|
||||||
|
|
||||||
|
const dateParam = self.$utilities.getDateRangeByDateType(self.query.dateType);
|
||||||
|
|
||||||
|
if (dateParam.minTime !== self.query.startTime || dateParam.maxTime !== self.query.endTime) {
|
||||||
|
self.$store.dispatch('updateTransactionStatisticsFilter', {
|
||||||
|
startTime: dateParam.minTime,
|
||||||
|
endTime: dateParam.maxTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
self.$store.dispatch('loadAllAccounts', { force: false }),
|
||||||
|
self.$store.dispatch('loadAllCategories', { force: false })
|
||||||
|
]).then(() => {
|
||||||
|
return self.$store.dispatch('loadTransactionStatistics', {
|
||||||
|
defaultCurrency: self.defaultCurrency
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
self.loading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.loading = false;
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
reload(done) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$store.dispatch('loadTransactionStatistics', {
|
||||||
|
defaultCurrency: self.defaultCurrency
|
||||||
|
}).then(() => {
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
if (done) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.chart-container {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container .echarts {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user