335 lines
11 KiB
Vue
335 lines
11 KiB
Vue
<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-toolbar tabbar bottom class="toolbar-item-auto-size">
|
|
<f7-link>
|
|
<f7-icon f7="arrow_left_square"></f7-icon>
|
|
</f7-link>
|
|
<f7-link class="tabbar-text-with-ellipsis" popover-open=".date-popover-menu">
|
|
<span :class="{ 'tabbar-item-changed': query.maxTime > 0 || query.minTime > 0 }">{{ query | dateRange }}</span>
|
|
</f7-link>
|
|
<f7-link>
|
|
<f7-icon f7="arrow_right_square"></f7-icon>
|
|
</f7-link>
|
|
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType($constants.statistics.allChartTypes.Pie)">
|
|
<span :class="{ 'tabbar-item-changed': query.chartType === $constants.statistics.allChartTypes.Pie }">{{ $t('Pie Chart') }}</span>
|
|
</f7-link>
|
|
<f7-link class="tabbar-text-with-ellipsis" @click="setChartType($constants.statistics.allChartTypes.Bar)">
|
|
<span :class="{ 'tabbar-item-changed': query.chartType === $constants.statistics.allChartTypes.Bar }">{{ $t('Bar Chart') }}</span>
|
|
</f7-link>
|
|
</f7-toolbar>
|
|
</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() {
|
|
const self = this;
|
|
|
|
if (!self.$store.state.transactionStatistics ||
|
|
!self.$store.state.transactionStatistics.items ||
|
|
!self.$store.state.transactionStatistics.items.length) {
|
|
return self.skeletonChart();
|
|
}
|
|
|
|
const combinedData = {};
|
|
const allData = [];
|
|
|
|
for (let i = 0; i < self.$store.state.transactionStatistics.items.length; i++) {
|
|
const item = self.$store.state.transactionStatistics.items[i];
|
|
|
|
if (!item.account || !item.category) {
|
|
continue;
|
|
}
|
|
|
|
if (item.category.type !== self.$constants.category.allCategoryTypes.Expense) {
|
|
continue;
|
|
}
|
|
|
|
if (self.query.chartLegendType === self.$constants.statistics.allChartLegendTypes.Account) {
|
|
if (self.$utilities.isNumber(item.amountInDefaultCurrency)) {
|
|
let data = combinedData[item.account.name];
|
|
|
|
if (data) {
|
|
data.totalAmount += item.amountInDefaultCurrency;
|
|
} else {
|
|
data = {
|
|
totalAmount: item.amountInDefaultCurrency,
|
|
color: item.account.color || self.$constants.colors.defaultAccountColor
|
|
}
|
|
}
|
|
|
|
combinedData[item.account.name] = data;
|
|
}
|
|
} else if (self.query.chartLegendType === self.$constants.statistics.allChartLegendTypes.SecondaryCategory) {
|
|
if (self.$utilities.isNumber(item.amountInDefaultCurrency)) {
|
|
let data = combinedData[item.category.name];
|
|
|
|
if (data) {
|
|
data.totalAmount += item.amountInDefaultCurrency;
|
|
} else {
|
|
data = {
|
|
totalAmount: item.amountInDefaultCurrency,
|
|
color: item.category.color || self.$constants.colors.defaultCategoryColor
|
|
}
|
|
}
|
|
|
|
combinedData[item.category.name] = data;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let legendName in combinedData) {
|
|
if (!Object.prototype.hasOwnProperty.call(combinedData, legendName)) {
|
|
continue;
|
|
}
|
|
|
|
const totalAmount = Math.floor(combinedData[legendName].totalAmount) / 100;
|
|
|
|
const data = {
|
|
name: legendName,
|
|
value: totalAmount,
|
|
itemStyle: {
|
|
color: `#${combinedData[legendName].color}`
|
|
}
|
|
};
|
|
|
|
allData.push(data);
|
|
}
|
|
|
|
if (self.query.chartType === self.$constants.statistics.allChartTypes.Bar) {
|
|
allData.sort(function (data1, data2) {
|
|
return data1.value - data2.value;
|
|
});
|
|
}
|
|
|
|
const chartData = {
|
|
label: {
|
|
show: true,
|
|
overflow: 'truncate',
|
|
align: 'left',
|
|
formatter: params => {
|
|
return `${params.name} ${self.$options.filters.currency(params.value * 100, self.defaultCurrency)}`;
|
|
}
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
data: allData,
|
|
}
|
|
],
|
|
animation: false
|
|
};
|
|
|
|
if (this.query.chartType === this.$constants.statistics.allChartTypes.Bar) {
|
|
return this.$utilities.copyObjectTo({
|
|
grid: {
|
|
left: 30,
|
|
top: 30,
|
|
right: 50,
|
|
bottom: 50
|
|
},
|
|
xAxis: {
|
|
type: 'value'
|
|
},
|
|
yAxis: {
|
|
type: 'category'
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
}
|
|
},
|
|
series: [{
|
|
type: 'bar'
|
|
}]
|
|
}, chartData);
|
|
} else {
|
|
return this.$utilities.copyObjectTo({
|
|
tooltip: {
|
|
trigger: 'item'
|
|
},
|
|
series: [{
|
|
type: 'pie',
|
|
label: {
|
|
position: 'inside'
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 0,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
}
|
|
}]
|
|
}, chartData);
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
const self = this;
|
|
const router = self.$f7router;
|
|
|
|
if (self.query.startTime < 0 || self.query.endTime < 0) {
|
|
const dateParam = self.$utilities.getDateRangeByDateType(self.$constants.datetime.allDateRanges.ThisMonth.type);
|
|
|
|
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);
|
|
}
|
|
});
|
|
},
|
|
setChartType(chartType) {
|
|
this.$store.dispatch('updateTransactionStatisticsFilter', {
|
|
chartType: chartType
|
|
});
|
|
},
|
|
skeletonChart() {
|
|
const skeletonChartData = {
|
|
series: [{
|
|
data: [{
|
|
value: 20,
|
|
itemStyle: {
|
|
color: '#7c7c7f'
|
|
}
|
|
},{
|
|
value: 20,
|
|
itemStyle: {
|
|
color: '#a5a5aa'
|
|
}
|
|
},{
|
|
value: 60,
|
|
itemStyle: {
|
|
color: '#c5c5c9'
|
|
}
|
|
}]
|
|
}],
|
|
animation: false
|
|
};
|
|
|
|
if (this.query.chartType === this.$constants.statistics.allChartTypes.Bar) {
|
|
return this.$utilities.copyObjectTo({
|
|
grid: {
|
|
left: 30,
|
|
top: 30,
|
|
right: 50,
|
|
bottom: 50
|
|
},
|
|
xAxis: {
|
|
type: 'value',
|
|
axisLabel: {
|
|
show: false
|
|
},
|
|
splitLine: {
|
|
show: false
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'category'
|
|
},
|
|
series: [{
|
|
type: 'bar'
|
|
}]
|
|
}, skeletonChartData);
|
|
} else {
|
|
return this.$utilities.copyObjectTo({
|
|
series: [{
|
|
type: 'pie',
|
|
label: {
|
|
position: 'inside'
|
|
}
|
|
}]
|
|
}, skeletonChartData);
|
|
}
|
|
}
|
|
},
|
|
filters: {
|
|
dateRange() {
|
|
return 'Date';
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style>
|
|
.chart-container {
|
|
height: 400px;
|
|
}
|
|
|
|
.chart-container .echarts {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
</style>
|