mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
add date filter for trend analysis
This commit is contained in:
@@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog class="month-range-selection-dialog" width="640" :persistent="!!persistent" v-model="showState">
|
||||||
|
<v-card class="pa-2 pa-sm-4 pa-md-4">
|
||||||
|
<template #title>
|
||||||
|
<div class="d-flex align-center justify-center">
|
||||||
|
<h4 class="text-h4">{{ title }}</h4>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #subtitle>
|
||||||
|
<div class="text-body-1 text-center text-wrap mt-6">
|
||||||
|
<p v-if="hint">{{ hint }}</p>
|
||||||
|
<span v-if="beginDateTime && endDateTime">
|
||||||
|
<span>{{ beginDateTime }}</span>
|
||||||
|
<span> - </span>
|
||||||
|
<span>{{ endDateTime }}</span>
|
||||||
|
</span>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<v-card-text class="mb-md-4 w-100 d-flex justify-center">
|
||||||
|
<v-row class="match-height">
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<vue-date-picker inline month-picker auto-apply
|
||||||
|
month-name-format="long"
|
||||||
|
:clearable="false"
|
||||||
|
:dark="isDarkMode"
|
||||||
|
:year-range="yearRange"
|
||||||
|
:year-first="isYearFirst"
|
||||||
|
:partial-range="false"
|
||||||
|
v-model="startTime">
|
||||||
|
<template #month="{ text }">
|
||||||
|
{{ getMonthShortName(text) }}
|
||||||
|
</template>
|
||||||
|
<template #month-overlay-value="{ text }">
|
||||||
|
{{ getMonthShortName(text) }}
|
||||||
|
</template>
|
||||||
|
</vue-date-picker>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<vue-date-picker inline month-picker auto-apply
|
||||||
|
month-name-format="long"
|
||||||
|
:clearable="false"
|
||||||
|
:dark="isDarkMode"
|
||||||
|
:year-range="yearRange"
|
||||||
|
:year-first="isYearFirst"
|
||||||
|
:partial-range="false"
|
||||||
|
v-model="endTime">
|
||||||
|
<template #month="{ text }">
|
||||||
|
{{ getMonthShortName(text) }}
|
||||||
|
</template>
|
||||||
|
<template #month-overlay-value="{ text }">
|
||||||
|
{{ getMonthShortName(text) }}
|
||||||
|
</template>
|
||||||
|
</vue-date-picker>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text class="overflow-y-visible">
|
||||||
|
<div class="w-100 d-flex justify-center gap-4">
|
||||||
|
<v-btn :disabled="!startTime || !endTime" @click="confirm">{{ $t('OK') }}</v-btn>
|
||||||
|
<v-btn color="secondary" variant="tonal" @click="cancel">{{ $t('Cancel') }}</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useTheme } from 'vuetify';
|
||||||
|
|
||||||
|
import { mapStores } from 'pinia';
|
||||||
|
import { useUserStore } from '@/stores/user.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getYearMonthObjectFromString,
|
||||||
|
getYearMonthStringFromObject,
|
||||||
|
getCurrentUnixTime,
|
||||||
|
getCurrentDateTime,
|
||||||
|
getThisYearFirstUnixTime,
|
||||||
|
getYearMonthFirstUnixTime,
|
||||||
|
getYearMonthLastUnixTime,
|
||||||
|
getYear
|
||||||
|
} from '@/lib/datetime.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'minTime',
|
||||||
|
'maxTime',
|
||||||
|
'title',
|
||||||
|
'hint',
|
||||||
|
'persistent',
|
||||||
|
'show'
|
||||||
|
],
|
||||||
|
emits: [
|
||||||
|
'update:show',
|
||||||
|
'dateRange:change'
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
const self = this;
|
||||||
|
let minDate = getThisYearFirstUnixTime();
|
||||||
|
let maxDate = getCurrentUnixTime();
|
||||||
|
|
||||||
|
if (self.minTime) {
|
||||||
|
minDate = getYearMonthObjectFromString(self.minTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.maxTime) {
|
||||||
|
maxDate = getYearMonthObjectFromString(self.maxTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
yearRange: [
|
||||||
|
2000,
|
||||||
|
getYear(getCurrentDateTime()) + 1
|
||||||
|
],
|
||||||
|
startTime: minDate,
|
||||||
|
endTime: maxDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useUserStore),
|
||||||
|
showState: {
|
||||||
|
get: function () {
|
||||||
|
return this.show;
|
||||||
|
},
|
||||||
|
set: function (value) {
|
||||||
|
this.$emit('update:show', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isDarkMode() {
|
||||||
|
return this.globalTheme.global.name.value === 'dark';
|
||||||
|
},
|
||||||
|
isYearFirst() {
|
||||||
|
return this.$locale.isLongDateMonthAfterYear(this.userStore);
|
||||||
|
},
|
||||||
|
beginDateTime() {
|
||||||
|
return this.$locale.formatUnixTimeToLongYearMonth(this.userStore, getYearMonthFirstUnixTime(this.startTime));
|
||||||
|
},
|
||||||
|
endDateTime() {
|
||||||
|
return this.$locale.formatUnixTimeToLongYearMonth(this.userStore, getYearMonthLastUnixTime(this.endTime));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'minTime': function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.startTime = getYearMonthObjectFromString(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'maxTime': function (newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.endTime = getYearMonthObjectFromString(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return {
|
||||||
|
globalTheme: theme
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirm() {
|
||||||
|
if (!this.startTime || !this.endTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.startTime.year <= 0 || this.startTime.month < 0 || this.endTime.year <= 0 || this.endTime.month < 0) {
|
||||||
|
this.$toast('Date is too early');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minYearMonth = getYearMonthStringFromObject(this.startTime);
|
||||||
|
const maxYearMonth = getYearMonthStringFromObject(this.endTime);
|
||||||
|
|
||||||
|
this.$emit('dateRange:change', minYearMonth, maxYearMonth);
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.$emit('update:show', false);
|
||||||
|
},
|
||||||
|
getMonthShortName(month) {
|
||||||
|
return this.$locale.getMonthShortName(month);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.month-range-selection-dialog .dp__preset_ranges {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-range-selection-dialog .dp__overlay {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -240,14 +240,16 @@ const allDateRanges = {
|
|||||||
type: 9,
|
type: 9,
|
||||||
name: 'This year',
|
name: 'This year',
|
||||||
availableScenes: {
|
availableScenes: {
|
||||||
[allDateRangeScenes.Normal]: true
|
[allDateRangeScenes.Normal]: true,
|
||||||
|
[allDateRangeScenes.TrendAnalysis]: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LastYear: {
|
LastYear: {
|
||||||
type: 10,
|
type: 10,
|
||||||
name: 'Last year',
|
name: 'Last year',
|
||||||
availableScenes: {
|
availableScenes: {
|
||||||
[allDateRangeScenes.Normal]: true
|
[allDateRangeScenes.Normal]: true,
|
||||||
|
[allDateRangeScenes.TrendAnalysis]: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RecentTwelveMonths: {
|
RecentTwelveMonths: {
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
|
|||||||
import SnackBar from '@/components/desktop/SnackBar.vue';
|
import SnackBar from '@/components/desktop/SnackBar.vue';
|
||||||
import PieChartComponent from '@/components/desktop/PieChart.vue';
|
import PieChartComponent from '@/components/desktop/PieChart.vue';
|
||||||
import DateRangeSelectionDialog from '@/components/desktop/DateRangeSelectionDialog.vue';
|
import DateRangeSelectionDialog from '@/components/desktop/DateRangeSelectionDialog.vue';
|
||||||
|
import MonthRangeSelectionDialog from '@/components/desktop/MonthRangeSelectionDialog.vue';
|
||||||
import SwitchToMobileDialog from '@/components/desktop/SwitchToMobileDialog.vue';
|
import SwitchToMobileDialog from '@/components/desktop/SwitchToMobileDialog.vue';
|
||||||
|
|
||||||
import '@/styles/desktop/template/vuetify/index.scss';
|
import '@/styles/desktop/template/vuetify/index.scss';
|
||||||
@@ -453,6 +454,7 @@ app.component('ConfirmDialog', ConfirmDialog);
|
|||||||
app.component('SnackBar', SnackBar);
|
app.component('SnackBar', SnackBar);
|
||||||
app.component('PieChart', PieChartComponent);
|
app.component('PieChart', PieChartComponent);
|
||||||
app.component('DateRangeSelectionDialog', DateRangeSelectionDialog);
|
app.component('DateRangeSelectionDialog', DateRangeSelectionDialog);
|
||||||
|
app.component('MonthRangeSelectionDialog', MonthRangeSelectionDialog);
|
||||||
app.component('SwitchToMobileDialog', SwitchToMobileDialog);
|
app.component('SwitchToMobileDialog', SwitchToMobileDialog);
|
||||||
|
|
||||||
app.config.globalProperties.$version = getVersion();
|
app.config.globalProperties.$version = getVersion();
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ export function isYearMonth(val) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isNumber(items[0]) && isNumber(items[1]);
|
return parseInt(items[0]) && parseInt(items[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEquals(obj1, obj2) {
|
export function isEquals(obj1, obj2) {
|
||||||
|
|||||||
+67
-1
@@ -1,7 +1,47 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import dateTimeConstants from '@/consts/datetime.js';
|
import dateTimeConstants from '@/consts/datetime.js';
|
||||||
import { isNumber } from './common.js';
|
import { isObject, isString, isNumber } from './common.js';
|
||||||
|
|
||||||
|
export function isYearMonthValid(year, month) {
|
||||||
|
if (!isNumber(year) || !isNumber(month)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return year > 0 && month >= 0 && month <= 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getYearMonthObjectFromString(yearMonth) {
|
||||||
|
if (!isString(yearMonth)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = yearMonth.split('-');
|
||||||
|
|
||||||
|
if (items.length !== 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = parseInt(items[0]);
|
||||||
|
const month = parseInt(items[1]) - 1;
|
||||||
|
|
||||||
|
if (!isYearMonthValid(year, month)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: year,
|
||||||
|
month: month
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getYearMonthStringFromObject(yearMonth) {
|
||||||
|
if (!yearMonth || !isYearMonthValid(yearMonth.year, yearMonth.month)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${yearMonth.year}-${yearMonth.month + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function getTwoDigitsString(value) {
|
export function getTwoDigitsString(value) {
|
||||||
if (value < 10) {
|
if (value < 10) {
|
||||||
@@ -155,6 +195,14 @@ export function getYearAndMonth(date) {
|
|||||||
return `${year}-${month}`;
|
return `${year}-${month}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getYearAndMonthFromUnixTime(unixTime) {
|
||||||
|
if (!unixTime) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return getYearAndMonth(parseDateFromUnixTime(unixTime));
|
||||||
|
}
|
||||||
|
|
||||||
export function getDay(date) {
|
export function getDay(date) {
|
||||||
return moment(date).date();
|
return moment(date).date();
|
||||||
}
|
}
|
||||||
@@ -262,6 +310,24 @@ export function getSpecifiedDayFirstUnixTime(unixTime) {
|
|||||||
return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
return moment.unix(unixTime).set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getYearMonthFirstUnixTime(yearMonth) {
|
||||||
|
if (isString(yearMonth)) {
|
||||||
|
yearMonth = getYearMonthObjectFromString(yearMonth);
|
||||||
|
} else if (isObject(yearMonth) && !isYearMonthValid(yearMonth.year, yearMonth.month)) {
|
||||||
|
yearMonth = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yearMonth) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moment().set({ year: yearMonth.year, month: yearMonth.month, date: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getYearMonthLastUnixTime(yearMonth) {
|
||||||
|
return moment.unix(getYearMonthFirstUnixTime(yearMonth)).add(1, 'months').subtract(1, 'seconds').unix();
|
||||||
|
}
|
||||||
|
|
||||||
export function getDateTimeFormatType(allFormatMap, allFormatArray, localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue) {
|
export function getDateTimeFormatType(allFormatMap, allFormatArray, localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue) {
|
||||||
if (formatTypeValue > dateTimeConstants.defaultDateTimeFormatValue && allFormatArray[formatTypeValue - 1] && allFormatArray[formatTypeValue - 1].key) {
|
if (formatTypeValue > dateTimeConstants.defaultDateTimeFormatValue && allFormatArray[formatTypeValue - 1] && allFormatArray[formatTypeValue - 1].key) {
|
||||||
return allFormatArray[formatTypeValue - 1];
|
return allFormatArray[formatTypeValue - 1];
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
isObject
|
isObject
|
||||||
} from '@/lib/common.js';
|
} from '@/lib/common.js';
|
||||||
import {
|
import {
|
||||||
|
getYearAndMonthFromUnixTime,
|
||||||
getDateRangeByDateType
|
getDateRangeByDateType
|
||||||
} from '@/lib/datetime.js';
|
} from '@/lib/datetime.js';
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ export const useStatisticsStore = defineStore('statistics', {
|
|||||||
filterCategoryIds: {}
|
filterCategoryIds: {}
|
||||||
},
|
},
|
||||||
transactionCategoryStatisticsData: {},
|
transactionCategoryStatisticsData: {},
|
||||||
|
transactionCategoryTrendsData: {},
|
||||||
transactionStatisticsStateInvalid: true
|
transactionStatisticsStateInvalid: true
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
@@ -424,6 +426,7 @@ export const useStatisticsStore = defineStore('statistics', {
|
|||||||
this.transactionStatisticsFilter.filterAccountIds = {};
|
this.transactionStatisticsFilter.filterAccountIds = {};
|
||||||
this.transactionStatisticsFilter.filterCategoryIds = {};
|
this.transactionStatisticsFilter.filterCategoryIds = {};
|
||||||
this.transactionCategoryStatisticsData = {};
|
this.transactionCategoryStatisticsData = {};
|
||||||
|
this.transactionCategoryTrendsData = {};
|
||||||
this.transactionStatisticsStateInvalid = true;
|
this.transactionStatisticsStateInvalid = true;
|
||||||
},
|
},
|
||||||
initTransactionStatisticsFilter(filter) {
|
initTransactionStatisticsFilter(filter) {
|
||||||
@@ -478,8 +481,8 @@ export const useStatisticsStore = defineStore('statistics', {
|
|||||||
categoricalChartEndTime: categoricalChartDateRange ? categoricalChartDateRange.maxTime : undefined,
|
categoricalChartEndTime: categoricalChartDateRange ? categoricalChartDateRange.maxTime : undefined,
|
||||||
trendChartType: defaultTrendChartType,
|
trendChartType: defaultTrendChartType,
|
||||||
trendChartDateType: trendChartDateRange ? trendChartDateRange.dateType : undefined,
|
trendChartDateType: trendChartDateRange ? trendChartDateRange.dateType : undefined,
|
||||||
trendChartStartYearMonth: trendChartDateRange ? trendChartDateRange.minTime : undefined,
|
trendChartStartYearMonth: trendChartDateRange ? getYearAndMonthFromUnixTime(trendChartDateRange.minTime) : undefined,
|
||||||
trendChartEndYearMonth: trendChartDateRange ? trendChartDateRange.maxTime : undefined,
|
trendChartEndYearMonth: trendChartDateRange ? getYearAndMonthFromUnixTime(trendChartDateRange.maxTime) : undefined,
|
||||||
filterAccountIds: settingsStore.appSettings.statistics.defaultAccountFilter || {},
|
filterAccountIds: settingsStore.appSettings.statistics.defaultAccountFilter || {},
|
||||||
filterCategoryIds: settingsStore.appSettings.statistics.defaultTransactionCategoryFilter || {},
|
filterCategoryIds: settingsStore.appSettings.statistics.defaultTransactionCategoryFilter || {},
|
||||||
sortingType: defaultSortType,
|
sortingType: defaultSortType,
|
||||||
@@ -587,11 +590,11 @@ export const useStatisticsStore = defineStore('statistics', {
|
|||||||
this.transactionStatisticsFilter.trendChartDateType = filter.trendChartDateType;
|
this.transactionStatisticsFilter.trendChartDateType = filter.trendChartDateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter && isYearMonth(filter.trendChartStartYearMonth)) {
|
if (filter && (isYearMonth(filter.trendChartStartYearMonth) || filter.trendChartStartYearMonth === '')) {
|
||||||
this.transactionStatisticsFilter.trendChartStartYearMonth = filter.trendChartStartYearMonth;
|
this.transactionStatisticsFilter.trendChartStartYearMonth = filter.trendChartStartYearMonth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter && isYearMonth(filter.trendChartEndYearMonth)) {
|
if (filter && (isYearMonth(filter.trendChartEndYearMonth) || filter.trendChartEndYearMonth === '')) {
|
||||||
this.transactionStatisticsFilter.trendChartEndYearMonth = filter.trendChartEndYearMonth;
|
this.transactionStatisticsFilter.trendChartEndYearMonth = filter.trendChartEndYearMonth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,8 +689,49 @@ export const useStatisticsStore = defineStore('statistics', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadTrendAnalysis() {
|
loadTrendAnalysis({ force }) {
|
||||||
return Promise.resolve(true);
|
const self = this;
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolve(true);
|
||||||
|
|
||||||
|
services.getTransactionStatisticsTrends({
|
||||||
|
startYearMonth: self.transactionStatisticsFilter.trendChartStartYearMonth,
|
||||||
|
endYearMonth: self.transactionStatisticsFilter.trendChartEndYearMonth,
|
||||||
|
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType
|
||||||
|
}).then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
reject({ message: 'Unable to retrieve transaction statistics' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.transactionStatisticsStateInvalid) {
|
||||||
|
self.updateTransactionStatisticsInvalidState(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force && data.result && isEquals(self.transactionCategoryTrendsData, data.result)) {
|
||||||
|
reject({ message: 'Data is up to date' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transactionCategoryTrendsData = data.result;
|
||||||
|
|
||||||
|
resolve(data.result);
|
||||||
|
}).catch(error => {
|
||||||
|
logger.error('failed to retrieve 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 retrieve transaction statistics' });
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,11 +55,10 @@
|
|||||||
<v-icon :icon="icons.menu" size="24" />
|
<v-icon :icon="icons.menu" size="24" />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<span>{{ $t('Statistics & Analysis') }}</span>
|
<span>{{ $t('Statistics & Analysis') }}</span>
|
||||||
<v-btn-group class="ml-4" color="default" density="comfortable" variant="outlined" divided
|
<v-btn-group class="ml-4" color="default" density="comfortable" variant="outlined" divided>
|
||||||
v-if="analysisType === allAnalysisTypes.CategoricalAnalysis">
|
|
||||||
<v-btn :icon="icons.left"
|
<v-btn :icon="icons.left"
|
||||||
:disabled="loading || query.categoricalChartDateType === allDateRanges.All.type || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type"
|
:disabled="loading || !canShiftDateRange(query)"
|
||||||
@click="shiftDateRange(query.categoricalChartStartTime, query.categoricalChartEndTime, -1)"/>
|
@click="shiftDateRange(query, -1)"/>
|
||||||
<v-menu location="bottom">
|
<v-menu location="bottom">
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<v-btn :disabled="loading || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type"
|
<v-btn :disabled="loading || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type"
|
||||||
@@ -67,13 +66,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item :key="dateRange.type" :value="dateRange.type"
|
<v-list-item :key="dateRange.type" :value="dateRange.type"
|
||||||
:append-icon="(query.categoricalChartDateType === dateRange.type ? icons.check : null)"
|
:append-icon="(isDateFilterChecked(dateRange.type) ? icons.check : null)"
|
||||||
v-for="dateRange in allDateRangesArray">
|
v-for="dateRange in allDateRangesArray">
|
||||||
<v-list-item-title
|
<v-list-item-title
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="setDateFilter(dateRange.type)">
|
@click="setDateFilter(dateRange.type)">
|
||||||
{{ dateRange.displayName }}
|
{{ dateRange.displayName }}
|
||||||
<div class="statistics-custom-datetime-range" v-if="dateRange.type === allDateRanges.Custom.type && query.categoricalChartDateType === allDateRanges.Custom.type && query.categoricalChartStartTime && query.categoricalChartEndTime">
|
<div class="statistics-custom-datetime-range" v-if="dateRange.type === allDateRanges.Custom.type && showCustomDateRange(query)">
|
||||||
<span>{{ queryStartTime }}</span>
|
<span>{{ queryStartTime }}</span>
|
||||||
<span> - </span>
|
<span> - </span>
|
||||||
<br/>
|
<br/>
|
||||||
@@ -84,8 +83,8 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-btn :icon="icons.right"
|
<v-btn :icon="icons.right"
|
||||||
:disabled="loading || query.categoricalChartDateType === allDateRanges.All.type || query.chartDataType === allChartDataTypes.AccountTotalAssets.type || query.chartDataType === allChartDataTypes.AccountTotalLiabilities.type"
|
:disabled="loading || !canShiftDateRange(query)"
|
||||||
@click="shiftDateRange(query.categoricalChartStartTime, query.categoricalChartEndTime, 1)"/>
|
@click="shiftDateRange(query, 1)"/>
|
||||||
</v-btn-group>
|
</v-btn-group>
|
||||||
|
|
||||||
<v-btn density="compact" color="default" variant="text" size="24"
|
<v-btn density="compact" color="default" variant="text" size="24"
|
||||||
@@ -236,6 +235,12 @@
|
|||||||
v-model:show="showCustomDateRangeDialog"
|
v-model:show="showCustomDateRangeDialog"
|
||||||
@dateRange:change="setCustomDateFilter" />
|
@dateRange:change="setCustomDateFilter" />
|
||||||
|
|
||||||
|
<month-range-selection-dialog :title="$t('Custom Date Range')"
|
||||||
|
:min-time="query.trendChartStartYearMonth"
|
||||||
|
:max-time="query.trendChartEndYearMonth"
|
||||||
|
v-model:show="showCustomMonthRangeDialog"
|
||||||
|
@dateRange:change="setCustomDateFilter" />
|
||||||
|
|
||||||
<v-dialog width="800" v-model="showFilterAccountDialog">
|
<v-dialog width="800" v-model="showFilterAccountDialog">
|
||||||
<account-filter-settings-card
|
<account-filter-settings-card
|
||||||
:dialog-mode="true" :modify-default="false"
|
:dialog-mode="true" :modify-default="false"
|
||||||
@@ -265,6 +270,9 @@ import datetimeConstants from '@/consts/datetime.js';
|
|||||||
import statisticsConstants from '@/consts/statistics.js';
|
import statisticsConstants from '@/consts/statistics.js';
|
||||||
import { limitText, formatPercent } from '@/lib/common.js'
|
import { limitText, formatPercent } from '@/lib/common.js'
|
||||||
import {
|
import {
|
||||||
|
getYearAndMonthFromUnixTime,
|
||||||
|
getYearMonthFirstUnixTime,
|
||||||
|
getYearMonthLastUnixTime,
|
||||||
getShiftedDateRangeAndDateType,
|
getShiftedDateRangeAndDateType,
|
||||||
getDateRangeByDateType
|
getDateRangeByDateType
|
||||||
} from '@/lib/datetime.js';
|
} from '@/lib/datetime.js';
|
||||||
@@ -303,6 +311,7 @@ export default {
|
|||||||
showNav: mdAndUp.value,
|
showNav: mdAndUp.value,
|
||||||
analysisType: statisticsConstants.allAnalysisTypes.CategoricalAnalysis,
|
analysisType: statisticsConstants.allAnalysisTypes.CategoricalAnalysis,
|
||||||
showCustomDateRangeDialog: false,
|
showCustomDateRangeDialog: false,
|
||||||
|
showCustomMonthRangeDialog: false,
|
||||||
showFilterAccountDialog: false,
|
showFilterAccountDialog: false,
|
||||||
showFilterCategoryDialog: false,
|
showFilterCategoryDialog: false,
|
||||||
icons: {
|
icons: {
|
||||||
@@ -360,10 +369,22 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryStartTime() {
|
queryStartTime() {
|
||||||
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.categoricalChartStartTime);
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.categoricalChartStartTime);
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return this.$locale.formatUnixTimeToLongYearMonth(this.userStore, getYearMonthFirstUnixTime(this.query.trendChartStartYearMonth));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
queryEndTime() {
|
queryEndTime() {
|
||||||
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.categoricalChartEndTime);
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return this.$locale.formatUnixTimeToLongDateTime(this.userStore, this.query.categoricalChartEndTime);
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return this.$locale.formatUnixTimeToLongYearMonth(this.userStore, getYearMonthLastUnixTime(this.query.trendChartEndYearMonth));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
allAnalysisTypes() {
|
allAnalysisTypes() {
|
||||||
return statisticsConstants.allAnalysisTypes;
|
return statisticsConstants.allAnalysisTypes;
|
||||||
@@ -393,7 +414,13 @@ export default {
|
|||||||
return datetimeConstants.allDateRanges;
|
return datetimeConstants.allDateRanges;
|
||||||
},
|
},
|
||||||
allDateRangesArray() {
|
allDateRangesArray() {
|
||||||
return this.$locale.getAllDateRanges(datetimeConstants.allDateRangeScenes.Normal, true);
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return this.$locale.getAllDateRanges(datetimeConstants.allDateRangeScenes.Normal, true);
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return this.$locale.getAllDateRanges(datetimeConstants.allDateRangeScenes.TrendAnalysis, true);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showAccountBalance() {
|
showAccountBalance() {
|
||||||
return this.settingsStore.appSettings.showAccountBalance;
|
return this.settingsStore.appSettings.showAccountBalance;
|
||||||
@@ -445,6 +472,8 @@ export default {
|
|||||||
if (!isChartDataTypeAvailableForAnalysisType(this.query.chartDataType, newValue)) {
|
if (!isChartDataTypeAvailableForAnalysisType(this.query.chartDataType, newValue)) {
|
||||||
this.query.chartDataType = statisticsConstants.defaultChartDataType;
|
this.query.chartDataType = statisticsConstants.defaultChartDataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.reload(null);
|
||||||
},
|
},
|
||||||
'query.chartDataType': function (newValue) {
|
'query.chartDataType': function (newValue) {
|
||||||
this.statisticsStore.updateTransactionStatisticsFilter({
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
@@ -565,12 +594,30 @@ export default {
|
|||||||
|
|
||||||
this.reload(null);
|
this.reload(null);
|
||||||
},
|
},
|
||||||
|
isDateFilterChecked(dateType) {
|
||||||
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis && this.query.categoricalChartDateType === dateType) {
|
||||||
|
return true;
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis && this.query.trendChartDateType === dateType) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
setDateFilter(dateType) {
|
setDateFilter(dateType) {
|
||||||
if (dateType === this.allDateRanges.Custom.type) { // Custom
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
this.showCustomDateRangeDialog = true;
|
if (dateType === this.allDateRanges.Custom.type) { // Custom
|
||||||
return;
|
this.showCustomDateRangeDialog = true;
|
||||||
} else if (this.query.categoricalChartDateType === dateType) {
|
return;
|
||||||
return;
|
} else if (this.query.categoricalChartDateType === dateType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
if (dateType === this.allDateRanges.Custom.type) { // Custom
|
||||||
|
this.showCustomMonthRangeDialog = true;
|
||||||
|
return;
|
||||||
|
} else if (this.query.trendChartDateType === dateType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateRange = getDateRangeByDateType(dateType, this.firstDayOfWeek);
|
const dateRange = getDateRangeByDateType(dateType, this.firstDayOfWeek);
|
||||||
@@ -579,11 +626,19 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.statisticsStore.updateTransactionStatisticsFilter({
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
categoricalChartDateType: dateRange.dateType,
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
categoricalChartStartTime: dateRange.minTime,
|
categoricalChartDateType: dateRange.dateType,
|
||||||
categoricalChartEndTime: dateRange.maxTime
|
categoricalChartStartTime: dateRange.minTime,
|
||||||
});
|
categoricalChartEndTime: dateRange.maxTime
|
||||||
|
});
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
|
trendChartDateType: dateRange.dateType,
|
||||||
|
trendChartStartYearMonth: getYearAndMonthFromUnixTime(dateRange.minTime),
|
||||||
|
trendChartEndYearMonth: getYearAndMonthFromUnixTime(dateRange.maxTime)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.reload(null);
|
this.reload(null);
|
||||||
},
|
},
|
||||||
@@ -592,28 +647,70 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.statisticsStore.updateTransactionStatisticsFilter({
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
categoricalChartDateType: this.allDateRanges.Custom.type,
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
categoricalChartStartTime: startTime,
|
categoricalChartDateType: this.allDateRanges.Custom.type,
|
||||||
categoricalChartEndTime: endTime
|
categoricalChartStartTime: startTime,
|
||||||
});
|
categoricalChartEndTime: endTime
|
||||||
|
});
|
||||||
|
|
||||||
this.showCustomDateRangeDialog = false;
|
this.showCustomDateRangeDialog = false;
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
|
trendChartDateType: this.allDateRanges.Custom.type,
|
||||||
|
trendChartStartYearMonth: startTime,
|
||||||
|
trendChartEndYearMonth: endTime
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showCustomMonthRangeDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.reload(null);
|
this.reload(null);
|
||||||
},
|
},
|
||||||
shiftDateRange(startTime, endTime, scale) {
|
showCustomDateRange(query) {
|
||||||
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return query.categoricalChartDateType === this.allDateRanges.Custom.type && query.categoricalChartStartTime && query.categoricalChartEndTime;
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return query.trendChartDateType === this.allDateRanges.Custom.type && query.trendChartStartYearMonth && query.trendChartEndYearMonth;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canShiftDateRange(query) {
|
||||||
|
if (query.chartDataType === this.allChartDataTypes.AccountTotalAssets.type || query.chartDataType === this.allChartDataTypes.AccountTotalLiabilities.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return query.categoricalChartDateType !== this.allDateRanges.All.type;
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return query.trendChartDateType !== this.allDateRanges.All.type;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shiftDateRange(query, scale) {
|
||||||
if (this.query.categoricalChartDateType === this.allDateRanges.All.type) {
|
if (this.query.categoricalChartDateType === this.allDateRanges.All.type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDateRange = getShiftedDateRangeAndDateType(startTime, endTime, scale, this.firstDayOfWeek, datetimeConstants.allDateRangeScenes.Normal);
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
const newDateRange = getShiftedDateRangeAndDateType(query.categoricalChartStartTime, query.categoricalChartEndTime, scale, this.firstDayOfWeek, datetimeConstants.allDateRangeScenes.Normal);
|
||||||
|
|
||||||
this.statisticsStore.updateTransactionStatisticsFilter({
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
categoricalChartDateType: newDateRange.dateType,
|
categoricalChartDateType: newDateRange.dateType,
|
||||||
categoricalChartStartTime: newDateRange.minTime,
|
categoricalChartStartTime: newDateRange.minTime,
|
||||||
categoricalChartEndTime: newDateRange.maxTime
|
categoricalChartEndTime: newDateRange.maxTime
|
||||||
});
|
});
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
const newDateRange = getShiftedDateRangeAndDateType(getYearMonthFirstUnixTime(query.trendChartStartYearMonth), getYearMonthLastUnixTime(query.trendChartEndYearMonth), scale, this.firstDayOfWeek, datetimeConstants.allDateRangeScenes.TrendAnalysis);
|
||||||
|
|
||||||
|
this.statisticsStore.updateTransactionStatisticsFilter({
|
||||||
|
trendChartDateType: newDateRange.dateType,
|
||||||
|
trendChartStartYearMonth: getYearAndMonthFromUnixTime(newDateRange.minTime),
|
||||||
|
trendChartEndYearMonth: getYearAndMonthFromUnixTime(newDateRange.maxTime)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.reload(null);
|
this.reload(null);
|
||||||
},
|
},
|
||||||
@@ -623,7 +720,13 @@ export default {
|
|||||||
return this.$t(this.allDateRanges.All.name);
|
return this.$t(this.allDateRanges.All.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$locale.getDateRangeDisplayName(this.userStore, query.categoricalChartDateType, query.categoricalChartStartTime, query.categoricalChartEndTime);
|
if (this.analysisType === statisticsConstants.allAnalysisTypes.CategoricalAnalysis) {
|
||||||
|
return this.$locale.getDateRangeDisplayName(this.userStore, query.categoricalChartDateType, query.categoricalChartStartTime, query.categoricalChartEndTime);
|
||||||
|
} else if (this.analysisType === statisticsConstants.allAnalysisTypes.TrendAnalysis) {
|
||||||
|
return this.$locale.getDateRangeDisplayName(this.userStore, query.trendChartDateType, getYearMonthFirstUnixTime(query.trendChartStartYearMonth), getYearMonthLastUnixTime(query.trendChartEndYearMonth));
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clickPieChartItem(item) {
|
clickPieChartItem(item) {
|
||||||
this.$router.push(this.getItemLinkUrl(item));
|
this.$router.push(this.getItemLinkUrl(item));
|
||||||
|
|||||||
Reference in New Issue
Block a user