support scheduled transaction (#2)

This commit is contained in:
MaysWind
2024-08-26 01:52:52 +08:00
parent 17d4fec256
commit d2eaf5c6da
42 changed files with 1437 additions and 112 deletions
@@ -0,0 +1,208 @@
<template>
<v-select
persistent-placeholder
:readonly="readonly"
:disabled="disabled"
:label="label"
:menu-props="{ 'content-class': 'schedule-frequency-select-menu' }"
v-model="frequencyType"
v-model:menu="menuState"
@update:menu="onMenuStateChanged"
>
<template #selection>
<span class="text-truncate cursor-pointer">{{ displayFrequency }}</span>
</template>
<template #no-data>
<div ref="dropdownMenu" class="schedule-frequency-container">
<div class="schedule-frequency-type-container">
<v-list>
<v-list-item :class="{ 'v-list-item--active text-primary': type.type === frequencyType }"
:key="type.type" :title="type.displayName"
v-for="type in allTransactionScheduledFrequencyTypes"
@click="frequencyType = type.type">
</v-list-item>
</v-list>
</div>
<div class="schedule-frequency-value-container">
<v-list v-if="frequencyType === allTemplateScheduledFrequencyTypes.Disabled.type">
<v-list-item :title="$t('None')"></v-list-item>
</v-list>
<v-list select-strategy="classic" v-model:selected="frequencyValue"
v-else-if="frequencyType === allTemplateScheduledFrequencyTypes.Weekly.type">
<v-list-item :key="weekDay.type" :value="weekDay.type" :title="weekDay.displayName"
:class="{ 'frequency-value-selected v-list-item--active text-primary': isFrequencyValueSelected(weekDay.type) }"
v-for="weekDay in allWeekDays">
<template v-slot:prepend="{ isActive }">
<v-checkbox density="compact" class="mr-1" :model-value="isActive"></v-checkbox>
</template>
</v-list-item>
</v-list>
<v-list select-strategy="classic" v-model:selected="frequencyValue"
v-else-if="frequencyType === allTemplateScheduledFrequencyTypes.Monthly.type">
<v-list-item :key="monthDay.day" :value="monthDay.day" :title="monthDay.displayName"
:class="{ 'frequency-value-selected v-list-item--active text-primary': isFrequencyValueSelected(monthDay.day) }"
v-for="monthDay in allAvailableMonthDays">
<template v-slot:prepend="{ isActive }">
<v-checkbox density="compact" class="mr-1" :model-value="isActive"></v-checkbox>
</template>
</v-list-item>
</v-list>
</div>
</div>
</template>
</v-select>
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import templateConstants from '@/consts/template.js';
import { sortNumbersArray } from '@/lib/common.js';
import { scrollToSelectedItem } from '@/lib/ui.desktop.js';
export default {
props: [
'type',
'modelValue',
'disabled',
'readonly',
'label'
],
emits: [
'update:type',
'update:modelValue'
],
data() {
return {
menuState: false
}
},
computed: {
...mapStores(useUserStore),
allTransactionScheduledFrequencyTypes() {
return this.$locale.getAllTransactionScheduledFrequencyTypes();
},
allTemplateScheduledFrequencyTypes() {
return templateConstants.allTemplateScheduledFrequencyTypes;
},
allWeekDays() {
return this.$locale.getAllWeekDays(this.firstDayOfWeek);
},
allAvailableMonthDays() {
const allAvailableDays = [];
for (let i = 1; i <= 28; i++) {
allAvailableDays.push({
day: i,
displayName: this.$locale.getMonthdayShortName(i),
});
}
return allAvailableDays;
},
firstDayOfWeek() {
return this.userStore.currentUserFirstDayOfWeek;
},
frequencyType: {
get: function () {
return this.type;
},
set: function (value) {
if (this.type !== value) {
this.$emit('update:type', value);
if (value === templateConstants.allTemplateScheduledFrequencyTypes.Weekly.type) {
this.frequencyValue = [this.firstDayOfWeek];
} else if (value === templateConstants.allTemplateScheduledFrequencyTypes.Monthly.type) {
this.frequencyValue = [1];
} else {
this.frequencyValue = [];
}
}
}
},
frequencyValue: {
get: function () {
const values = this.modelValue.split(',');
const ret = [];
for (let i = 0; i < values.length; i++) {
if (values[i]) {
ret.push(parseInt(values[i]));
}
}
return sortNumbersArray(ret);
},
set: function (value) {
this.$emit('update:modelValue', sortNumbersArray(value).join(','));
}
},
displayFrequency() {
if (this.type === templateConstants.allTemplateScheduledFrequencyTypes.Disabled.type) {
return this.$t('Disabled');
} else if (this.type === templateConstants.allTemplateScheduledFrequencyTypes.Weekly.type) {
if (this.frequencyValue.length) {
return this.$t('format.misc.everyMultiDaysOfWeek', {
days: this.$locale.getMultiWeekdayLongNames(this.frequencyValue)
});
} else {
return this.$t('Weekly');
}
} else if (this.type === templateConstants.allTemplateScheduledFrequencyTypes.Monthly.type) {
if (this.frequencyValue.length) {
return this.$t('format.misc.everyMultiDaysOfMonth', {
days: this.$locale.getMultiMonthdayShortNames(this.frequencyValue)
});
} else {
return this.$t('Monthly');
}
} else {
return '';
}
}
},
methods: {
onMenuStateChanged(state) {
const self = this;
if (state) {
self.$nextTick(() => {
if (self.$refs.dropdownMenu && self.$refs.dropdownMenu.parentElement) {
scrollToSelectedItem(self.$refs.dropdownMenu.parentElement, '.schedule-frequency-value-container', '.frequency-value-selected');
}
});
}
},
isFrequencyValueSelected(value) {
for (let i = 0; i < this.frequencyValue.length; i++) {
if (this.frequencyValue[i] === value) {
return true;
}
}
return false;
}
}
}
</script>
<style>
.schedule-frequency-select-menu {
max-height: inherit !important;
}
.schedule-frequency-select-menu .schedule-frequency-container {
width: 100%;
display: flex;
}
.schedule-frequency-select-menu .schedule-frequency-type-container,
.schedule-frequency-select-menu .schedule-frequency-value-container {
width: 100%;
max-height: 310px;
overflow-y: scroll;
}
</style>
@@ -0,0 +1,212 @@
<template>
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
style="height: auto" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
<f7-toolbar>
<div class="swipe-handler"></div>
<div class="left">
<f7-link sheet-close :text="$t('Cancel')"></f7-link>
</div>
<div class="right">
<f7-link :text="$t('Done')" @click="save"></f7-link>
</div>
</f7-toolbar>
<f7-page-content>
<div class="grid grid-cols-2 grid-gap">
<div>
<div class="schedule-frequency-type-container">
<f7-list dividers class="schedule-frequency-type-list no-margin-vertical">
<f7-list-item link="#" no-chevron
:key="type.type"
:title="type.displayName"
v-for="type in allTransactionScheduledFrequencyTypes"
@click="changeFrequencyType(type.type)">
<template #after>
<f7-icon class="list-item-showing" f7="chevron_right" v-if="currentFrequencyType === type.type"></f7-icon>
</template>
</f7-list-item>
</f7-list>
</div>
</div>
<div>
<div class="schedule-frequency-value-container">
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === allTemplateScheduledFrequencyTypes.Disabled.type">
<f7-list-item :title="$t('None')"></f7-list-item>
</f7-list>
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === allTemplateScheduledFrequencyTypes.Weekly.type">
<f7-list-item checkbox
:class="isChecked(weekDay.type) ? 'list-item-selected' : ''"
:key="weekDay.type"
:value="weekDay.type"
:checked="isChecked(weekDay.type)"
:title="weekDay.displayName"
v-for="weekDay in allWeekDays"
@change="changeFrequencyValue">
</f7-list-item>
</f7-list>
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === allTemplateScheduledFrequencyTypes.Monthly.type">
<f7-list-item checkbox
:class="isChecked(monthDay.day) ? 'list-item-selected' : ''"
:key="monthDay.day"
:value="monthDay.day"
:checked="isChecked(monthDay.day)"
:title="monthDay.displayName"
v-for="monthDay in allAvailableMonthDays"
@change="changeFrequencyValue">
</f7-list-item>
</f7-list>
</div>
</div>
</div>
</f7-page-content>
</f7-sheet>
</template>
<script>
import { mapStores } from 'pinia';
import { useUserStore } from '@/stores/user.js';
import templateConstants from '@/consts/template.js';
import { sortNumbersArray } from '@/lib/common.js';
import { scrollToSelectedItem } from '@/lib/ui.mobile.js';
export default {
props: [
'type',
'modelValue',
'disabled',
'readonly',
'label',
'show'
],
emits: [
'update:type',
'update:modelValue',
'update:show'
],
data() {
const self = this;
return {
currentFrequencyType: self.type,
currentFrequencyValue: self.getFrequencyValues(self.modelValue)
}
},
computed: {
...mapStores(useUserStore),
allTransactionScheduledFrequencyTypes() {
return this.$locale.getAllTransactionScheduledFrequencyTypes();
},
allTemplateScheduledFrequencyTypes() {
return templateConstants.allTemplateScheduledFrequencyTypes;
},
allWeekDays() {
return this.$locale.getAllWeekDays(this.firstDayOfWeek);
},
allAvailableMonthDays() {
const allAvailableDays = [];
for (let i = 1; i <= 28; i++) {
allAvailableDays.push({
day: i,
displayName: this.$locale.getMonthdayShortName(i),
});
}
return allAvailableDays;
},
firstDayOfWeek() {
return this.userStore.currentUserFirstDayOfWeek;
}
},
methods: {
onSheetOpen(event) {
this.currentFrequencyType = this.type;
this.currentFrequencyValue = this.getFrequencyValues(this.modelValue);
scrollToSelectedItem(event.$el, '.schedule-frequency-value-container', 'li.list-item-selected');
},
onSheetClosed() {
this.close();
},
changeFrequencyType(value) {
if (this.currentFrequencyType !== value) {
this.currentFrequencyType = value;
if (value === templateConstants.allTemplateScheduledFrequencyTypes.Weekly.type) {
this.currentFrequencyValue = [this.firstDayOfWeek];
} else if (value === templateConstants.allTemplateScheduledFrequencyTypes.Monthly.type) {
this.currentFrequencyValue = [1];
} else {
this.currentFrequencyValue = [];
}
}
},
changeFrequencyValue(e) {
const value = parseInt(e.target.value);
if (e.target.checked) {
for (let i = 0; i < this.currentFrequencyValue.length; i++) {
if (this.currentFrequencyValue[i] === value) {
return;
}
}
this.currentFrequencyValue.push(value);
} else {
for (let i = 0; i < this.currentFrequencyValue.length; i++) {
if (this.currentFrequencyValue[i] === value) {
this.currentFrequencyValue.splice(i, 1);
break;
}
}
}
},
save() {
this.$emit('update:type', this.currentFrequencyType);
this.$emit('update:modelValue', sortNumbersArray(this.currentFrequencyValue).join(','));
this.$emit('update:show', false);
},
close() {
this.$emit('update:show', false);
},
isChecked(value) {
for (let i = 0; i < this.currentFrequencyValue.length; i++) {
if (this.currentFrequencyValue[i] === value) {
return true;
}
}
return false;
},
getFrequencyValues(value) {
const values = value.split(',');
const ret = [];
for (let i = 0; i < values.length; i++) {
if (values[i]) {
ret.push(parseInt(values[i]));
}
}
return sortNumbersArray(ret);
}
}
}
</script>
<style>
.schedule-frequency-type-container, .schedule-frequency-value-container {
height: 260px;
overflow-y: auto;
}
.schedule-frequency-type-list.list .item-inner {
padding-right: 6px;
}
.schedule-frequency-value-list-list.list .item-content {
padding-left: 0;
}
</style>
+18 -1
View File
@@ -1,7 +1,24 @@
const allTemplateTypes = {
Normal: 1
Normal: 1,
Schedule: 2,
};
const allTemplateScheduledFrequencyTypes = {
Disabled: {
type: 0,
name: 'Disabled'
},
Weekly: {
type: 1,
name: 'Weekly'
},
Monthly: {
type: 2,
name: 'Monthly'
}
};
export default {
allTemplateTypes: allTemplateTypes,
allTemplateScheduledFrequencyTypes: allTemplateScheduledFrequencyTypes,
}
+2
View File
@@ -88,6 +88,7 @@ import DateTimeSelect from '@/components/desktop/DateTimeSelect.vue';
import ColorSelect from '@/components/desktop/ColorSelect.vue';
import IconSelect from '@/components/desktop/IconSelect.vue';
import TwoColumnSelect from '@/components/desktop/TwoColumnSelect.vue';
import ScheduleFrequencySelect from '@/components/desktop/ScheduleFrequencySelect.vue';
import StepsBar from '@/components/desktop/StepsBar.vue';
import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue';
import SnackBar from '@/components/desktop/SnackBar.vue';
@@ -454,6 +455,7 @@ app.component('DateTimeSelect', DateTimeSelect);
app.component('ColorSelect', ColorSelect);
app.component('IconSelect', IconSelect);
app.component('TwoColumnSelect', TwoColumnSelect);
app.component('ScheduleFrequencySelect', ScheduleFrequencySelect);
app.component('StepsBar', StepsBar);
app.component('ConfirmDialog', ConfirmDialog);
app.component('SnackBar', SnackBar);
+6
View File
@@ -130,6 +130,12 @@ export function isObjectEmpty(obj) {
return true;
}
export function sortNumbersArray(array) {
return array.sort(function (num1, num2) {
return num1 - num2;
});
}
export function getObjectOwnFieldCount(object) {
let count = 0;
+76 -3
View File
@@ -9,6 +9,7 @@ import colorConstants from '@/consts/color.js';
import accountConstants from '@/consts/account.js';
import categoryConstants from '@/consts/category.js';
import transactionConstants from '@/consts/transaction.js';
import templateConstants from '@/consts/template.js';
import statisticsConstants from '@/consts/statistics.js';
import apiConstants from '@/consts/api.js';
@@ -17,6 +18,7 @@ import {
isString,
isNumber,
isBoolean,
getNameByKeyValue,
copyObjectTo,
copyArrayTo
} from './common.js';
@@ -311,6 +313,16 @@ function getMonthLongName(monthName, translateFn) {
return translateFn(`datetime.${monthName}.long`);
}
function getMonthdayOrdinal(monthDay, translateFn) {
return translateFn(`datetime.monthDayOrdinal.${monthDay}`);
}
function getMonthdayShortName(monthDay, translateFn) {
return translateFn('format.misc.monthDay', {
ordinal: getMonthdayOrdinal(monthDay, translateFn)
});
}
function getWeekdayShortName(weekDayName, translateFn) {
return translateFn(`datetime.${weekDayName}.short`);
}
@@ -319,6 +331,30 @@ function getWeekdayLongName(weekDayName, translateFn) {
return translateFn(`datetime.${weekDayName}.long`);
}
function getMultiMonthdayShortNames(monthDays, translateFn) {
if (!monthDays) {
return '';
}
if (monthDays.length === 1) {
return translateFn('format.misc.monthDay', {
ordinal: getMonthdayOrdinal(monthDays[0], translateFn)
});
} else {
return translateFn('format.misc.monthDays', {
multiMonthDays: joinMultiText(monthDays.map(monthDay =>
translateFn('format.misc.eachMonthDayInMonthDays', {
ordinal: getMonthdayOrdinal(monthDay, translateFn)
})), translateFn)
});
}
}
function getMultiWeekdayLongNames(weekdayTypes, translateFn) {
const allWeekDays = getAllWeekDays(null, translateFn)
return joinMultiText(weekdayTypes.map(type => getNameByKeyValue(allWeekDays, type, 'type', 'displayName')), translateFn);
}
function getI18nLongDateFormat(translateFn, formatTypeValue) {
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
return getDateTimeFormat(translateFn, datetimeConstants.allLongDateFormat, datetimeConstants.allLongDateFormatArray, 'format.longDate', defaultLongDateFormatTypeName, datetimeConstants.defaultLongDateFormat, formatTypeValue);
@@ -534,10 +570,23 @@ function getAllCurrencies(translateFn) {
return allCurrencies;
}
function getAllWeekDays(translateFn) {
function getAllWeekDays(firstDayOfWeek, translateFn) {
const allWeekDays = [];
for (let i = 0; i < datetimeConstants.allWeekDaysArray.length; i++) {
if (!isNumber(firstDayOfWeek)) {
firstDayOfWeek = datetimeConstants.allWeekDays.Sunday.type;
}
for (let i = firstDayOfWeek; i < datetimeConstants.allWeekDaysArray.length; i++) {
const weekDay = datetimeConstants.allWeekDaysArray[i];
allWeekDays.push({
type: weekDay.type,
displayName: translateFn(`datetime.${weekDay.name}.long`)
});
}
for (let i = 0; i < firstDayOfWeek; i++) {
const weekDay = datetimeConstants.allWeekDaysArray[i];
allWeekDays.push({
@@ -1078,6 +1127,25 @@ function getAllTransactionEditScopeTypes(translateFn) {
return allEditScopeTypes;
}
function getAllTransactionScheduledFrequencyTypes(translateFn) {
const allScheduledFrequencyTypes = [];
for (const typeName in templateConstants.allTemplateScheduledFrequencyTypes) {
if (!Object.prototype.hasOwnProperty.call(templateConstants.allTemplateScheduledFrequencyTypes, typeName)) {
continue;
}
const frequencyType = templateConstants.allTemplateScheduledFrequencyTypes[typeName];
allScheduledFrequencyTypes.push({
type: frequencyType.type,
displayName: translateFn(frequencyType.name)
});
}
return allScheduledFrequencyTypes;
}
function getAllTransactionDefaultCategories(categoryType, locale, translateFn) {
const allCategories = {};
const categoryTypes = [];
@@ -1442,8 +1510,12 @@ export function i18nFunctions(i18nGlobal) {
getAllShortTimeFormats: () => getAllShortTimeFormats(i18nGlobal.t),
getMonthShortName: (month) => getMonthShortName(month, i18nGlobal.t),
getMonthLongName: (month) => getMonthLongName(month, i18nGlobal.t),
getMonthdayOrdinal: (monthDay) => getMonthdayOrdinal(monthDay, i18nGlobal.t),
getMonthdayShortName: (monthDay) => getMonthdayShortName(monthDay, i18nGlobal.t),
getWeekdayShortName: (weekDay) => getWeekdayShortName(weekDay, i18nGlobal.t),
getWeekdayLongName: (weekDay) => getWeekdayLongName(weekDay, i18nGlobal.t),
getMultiMonthdayShortNames: (monthdays) => getMultiMonthdayShortNames(monthdays, i18nGlobal.t),
getMultiWeekdayLongNames: (weekdayTypes) => getMultiWeekdayLongNames(weekdayTypes, i18nGlobal.t),
formatUnixTimeToLongDateTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongDateFormat(i18nGlobal.t, userStore.currentUserLongDateFormat) + ' ' + getI18nLongTimeFormat(i18nGlobal.t, userStore.currentUserLongTimeFormat), utcOffset, currentUtcOffset),
formatUnixTimeToShortDateTime: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nShortDateFormat(i18nGlobal.t, userStore.currentUserShortDateFormat) + ' ' + getI18nShortTimeFormat(i18nGlobal.t, userStore.currentUserShortTimeFormat), utcOffset, currentUtcOffset),
formatUnixTimeToLongDate: (userStore, unixTime, utcOffset, currentUtcOffset) => formatUnixTime(unixTime, getI18nLongDateFormat(i18nGlobal.t, userStore.currentUserLongDateFormat), utcOffset, currentUtcOffset),
@@ -1465,7 +1537,7 @@ export function i18nFunctions(i18nGlobal) {
getAllTimezones: (includeSystemDefault) => getAllTimezones(includeSystemDefault, i18nGlobal.t),
getTimezoneDifferenceDisplayText: (utcOffset) => getTimezoneDifferenceDisplayText(utcOffset, i18nGlobal.t),
getAllCurrencies: () => getAllCurrencies(i18nGlobal.t),
getAllWeekDays: () => getAllWeekDays(i18nGlobal.t),
getAllWeekDays: (firstDayOfWeek) => getAllWeekDays(firstDayOfWeek, i18nGlobal.t),
getAllDateRanges: (scene, includeCustom) => getAllDateRanges(scene, includeCustom, i18nGlobal.t),
getAllRecentMonthDateRanges: (userStore, includeAll, includeCustom) => getAllRecentMonthDateRanges(userStore, includeAll, includeCustom, i18nGlobal.t),
getDateRangeDisplayName: (userStore, dateType, startTime, endTime) => getDateRangeDisplayName(userStore, dateType, startTime, endTime, i18nGlobal.t),
@@ -1493,6 +1565,7 @@ export function i18nFunctions(i18nGlobal) {
getAllStatisticsChartDataTypes: (analysisType) => getAllStatisticsChartDataTypes(i18nGlobal.t, analysisType),
getAllStatisticsSortingTypes: () => getAllStatisticsSortingTypes(i18nGlobal.t),
getAllTransactionEditScopeTypes: () => getAllTransactionEditScopeTypes(i18nGlobal.t),
getAllTransactionScheduledFrequencyTypes: () => getAllTransactionScheduledFrequencyTypes(i18nGlobal.t),
getAllTransactionDefaultCategories: (categoryType, locale) => getAllTransactionDefaultCategories(categoryType, locale, i18nGlobal.t),
getAllDisplayExchangeRates: (exchangeRatesData) => getAllDisplayExchangeRates(exchangeRatesData, i18nGlobal.t),
getEnableDisableOptions: () => getEnableDisableOptions(i18nGlobal.t),
+4
View File
@@ -41,6 +41,10 @@ export function isUserVerifyEmailEnabled() {
return getServerSetting('v') === '1';
}
export function isUserScheduledTransactionEnabled() {
return getServerSetting('s') === '1';
}
export function isDataExportingEnabled() {
return getServerSetting('e') === '1';
}
+9 -3
View File
@@ -523,7 +523,7 @@ export default {
getTransactionTemplate: ({ id }) => {
return axios.get('v1/transaction/templates/get.json?id=' + id);
},
addTransactionTemplate: ({ templateType, name, type, categoryId, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, clientSessionId }) => {
addTransactionTemplate: ({ templateType, name, type, categoryId, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, scheduledFrequencyType, scheduledFrequency, utcOffset, clientSessionId }) => {
return axios.post('v1/transaction/templates/add.json', {
templateType,
name,
@@ -536,10 +536,13 @@ export default {
hideAmount,
tagIds,
comment,
scheduledFrequencyType,
scheduledFrequency,
utcOffset,
clientSessionId
});
},
modifyTransactionTemplate: ({ id, name, type, categoryId, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment }) => {
modifyTransactionTemplate: ({ id, name, type, categoryId, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, scheduledFrequencyType, scheduledFrequency, utcOffset }) => {
return axios.post('v1/transaction/templates/modify.json', {
id,
name,
@@ -551,7 +554,10 @@ export default {
destinationAmount,
hideAmount,
tagIds,
comment
comment,
scheduledFrequencyType,
scheduledFrequency,
utcOffset
});
},
hideTransactionTemplate: ({ id, hidden }) => {
+47
View File
@@ -73,6 +73,11 @@
"hoursAheadOfDefaultTimezone": "{hours} hour(s) ahead of default timezone",
"hoursMinutesBehindDefaultTimezone": "{hours} hour(s) and {minutes} minutes behind default timezone",
"hoursMinutesAheadOfDefaultTimezone": "{hours} hour(s) and {minutes} minutes ahead of default timezone",
"monthDay": "{ordinal} day",
"eachMonthDayInMonthDays": "{ordinal}",
"monthDays": "{multiMonthDays} days",
"everyMultiDaysOfWeek": "Every {days}",
"everyMultiDaysOfMonth": "Every {days} of month",
"youHaveAccounts": "You have recorded {count} accounts",
"accountActivationAndResendValidationEmailTip": "Account activation link has been sent to your email address: {email}, If you don't receive the mail, please fill password again and click the button below to resend the validation mail.",
"resendValidationEmailTip": "If you don't receive the mail, please fill password again and click the button below to resend the validation mail to: {email}"
@@ -171,6 +176,39 @@
"December": {
"short": "Dec",
"long": "December"
},
"monthDayOrdinal": {
"1": "1th",
"2": "2nd",
"3": "3rd",
"4": "4th",
"5": "5th",
"6": "6th",
"7": "7th",
"8": "8th",
"9": "9th",
"10": "10th",
"11": "11th",
"12": "12th",
"13": "13th",
"14": "14th",
"15": "15th",
"16": "16th",
"17": "17th",
"18": "18th",
"19": "19th",
"20": "20th",
"21": "21th",
"22": "22nd",
"23": "23rd",
"24": "24th",
"25": "25th",
"26": "26th",
"27": "27th",
"28": "28th",
"29": "29th",
"30": "30th",
"31": "31th"
}
},
"numeral": {
@@ -1047,6 +1085,8 @@
"transaction template id is invalid": "Transaction template ID is invalid",
"transaction template not found": "Transaction template is not found",
"transaction template type is invalid": "Transaction template type is invalid",
"scheduled transaction is not enabled": "Scheduled transaction is not enabled",
"scheduled transaction frequency is invalid": "Scheduled transaction frequency is invalid",
"query items cannot be blank": "There are no query items",
"query items too much": "There are too many query items",
"query items have invalid item": "There is invalid item in query items",
@@ -1168,6 +1208,8 @@
"Select Date": "Select Date",
"Select Time": "Select Time",
"Now": "Now",
"Weekly": "Weekly",
"Monthly": "Monthly",
"Custom": "Custom",
"Greater than": "Greater than",
"Less than": "Less than",
@@ -1368,6 +1410,8 @@
"Edit Transaction": "Edit Transaction",
"Add Transaction Template": "Add Transaction Template",
"Edit Transaction Template": "Edit Transaction Template",
"Add Scheduled Transaction": "Add Scheduled Transaction",
"Edit Scheduled Transaction": "Edit Scheduled Transaction",
"Modify Balance": "Modify Balance",
"Expense Amount": "Expense Amount",
"Income Amount": "Income Amount",
@@ -1387,6 +1431,7 @@
"Without Tags": "Without Tags",
"Multiple Tags": "Multiple Tags",
"Transaction Time": "Transaction Time",
"Scheduled Transaction Frequency": "Scheduled Transaction Frequency",
"Transaction Timezone": "Transaction Timezone",
"Same time as default timezone": "Same time as default timezone",
"Geographic Location": "Geographic Location",
@@ -1622,10 +1667,12 @@
"Show Hidden Transaction Tags": "Show Hidden Transaction Tags",
"Hide Hidden Transaction Tags": "Hide Hidden Transaction Tags",
"Transaction Templates": "Transaction Templates",
"Scheduled Transactions": "Scheduled Transactions",
"Template Name": "Template Name",
"No available template": "No available template",
"Once you add templates, you can long press the Add button on the home page to quickly add a new transaction": "Once you add templates, you can long press the Add button on the home page to quickly add a new transaction",
"No available template. Once you add templates, you can quickly add a new transaction using the dropdown menu of the Add button on the transaction list page": "No available template. Once you add templates, you can quickly add a new transaction using the dropdown menu of the Add button on the transaction list page",
"No available scheduled transactions": "No available scheduled transactions",
"Unable to retrieve template list": "Unable to retrieve template list",
"Template list is up to date": "Template list is up to date",
"Template list has been updated": "Template list has been updated",
+47
View File
@@ -73,6 +73,11 @@
"hoursAheadOfDefaultTimezone": "比默认时区早{hours}小时",
"hoursMinutesBehindDefaultTimezone": "比默认时区晚{hours}小时{minutes}分",
"hoursMinutesAheadOfDefaultTimezone": "比默认时区早{time}小时{minutes}分",
"monthDay": "{ordinal}日",
"eachMonthDayInMonthDays": "{ordinal}日",
"monthDays": "{multiMonthDays}",
"everyMultiDaysOfWeek": "每{days}",
"everyMultiDaysOfMonth": "每月{days}",
"youHaveAccounts": "您已经记录了 {count} 个账户",
"accountActivationAndResendValidationEmailTip": "账号激活链接已经发送到您的邮箱地址:{email},如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件。",
"resendValidationEmailTip": "如果您没有收到邮件,请再次输入密码并点击下方的按钮重新发送验证邮件到:{email}"
@@ -171,6 +176,39 @@
"December": {
"short": "12月",
"long": "十二月"
},
"monthDayOrdinal": {
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"10": "10",
"11": "11",
"12": "12",
"13": "13",
"14": "14",
"15": "15",
"16": "16",
"17": "17",
"18": "18",
"19": "19",
"20": "20",
"21": "21",
"22": "22",
"23": "23",
"24": "24",
"25": "25",
"26": "26",
"27": "27",
"28": "28",
"29": "29",
"30": "30",
"31": "31"
}
},
"numeral": {
@@ -1047,6 +1085,8 @@
"transaction template id is invalid": "交易模板ID无效",
"transaction template not found": "交易模板不存在",
"transaction template type is invalid": "交易模板类型无效",
"scheduled transaction is not enabled": "定时交易没有启用",
"scheduled transaction frequency is invalid": "定时交易周期无效",
"query items cannot be blank": "请求项目不能为空",
"query items too much": "请求项目过多",
"query items have invalid item": "请求项目中有非法项目",
@@ -1168,6 +1208,8 @@
"Select Date": "选择日期",
"Select Time": "选择时间",
"Now": "现在",
"Weekly": "每周",
"Monthly": "每月",
"Custom": "自定义",
"Greater than": "大于",
"Less than": "小于",
@@ -1368,6 +1410,8 @@
"Edit Transaction": "编辑交易",
"Add Transaction Template": "添加交易模板",
"Edit Transaction Template": "编辑交易模板",
"Add Scheduled Transaction": "添加定时交易",
"Edit Scheduled Transaction": "编辑定时交易",
"Modify Balance": "修改余额",
"Expense Amount": "支出金额",
"Income Amount": "收入金额",
@@ -1387,6 +1431,7 @@
"Without Tags": "没有标签",
"Multiple Tags": "多个标签",
"Transaction Time": "交易时间",
"Scheduled Transaction Frequency": "定时交易周期",
"Transaction Timezone": "交易时区",
"Same time as default timezone": "与默认时区时间相同",
"Geographic Location": "地理位置",
@@ -1622,10 +1667,12 @@
"Show Hidden Transaction Tags": "显示隐藏的交易标签",
"Hide Hidden Transaction Tags": "不显示隐藏的交易标签",
"Transaction Templates": "交易模板",
"Scheduled Transactions": "定时交易",
"Template Name": "模板名称",
"No available template": "没有可用的模板",
"Once you add templates, you can long press the Add button on the home page to quickly add a new transaction": "当添加模板后,您可以在主界面长按添加按钮快速添加新的交易",
"No available template. Once you add templates, you can quickly add a new transaction using the dropdown menu of the Add button on the transaction list page": "没有可用的模板。当添加模板后,您可以通过交易列表添加按钮的下拉菜单快速添加新的交易",
"No available scheduled transactions": "没有可用的定时交易",
"Unable to retrieve template list": "无法获取模板列表",
"Template list is up to date": "模板列表已是最新",
"Template list has been updated": "模板列表已更新",
+2
View File
@@ -110,6 +110,7 @@ import InformationSheet from '@/components/mobile/InformationSheet.vue';
import NumberPadSheet from '@/components/mobile/NumberPadSheet.vue';
import MapSheet from '@/components/mobile/MapSheet.vue';
import TransactionTagSelectionSheet from '@/components/mobile/TransactionTagSelectionSheet.vue';
import ScheduleFrequencySheet from '@/components/mobile/ScheduleFrequencySheet.vue';
import TextareaAutoSize from '@/directives/mobile/textareaAutoSize.js';
@@ -188,6 +189,7 @@ app.component('InformationSheet', InformationSheet);
app.component('NumberPadSheet', NumberPadSheet);
app.component('MapSheet', MapSheet);
app.component('TransactionTagSelectionSheet', TransactionTagSelectionSheet);
app.component('ScheduleFrequencySheet', ScheduleFrequencySheet);
app.directive('TextareaAutoSize', TextareaAutoSize);
+13 -1
View File
@@ -1,5 +1,6 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import templateConstants from '@/consts/template.js';
import userState from '@/lib/userstate.js';
import MainLayout from '@/views/desktop/MainLayout.vue';
@@ -141,7 +142,18 @@ const router = createRouter({
{
path: '/template/list',
component: TransactionTemplateListPage,
beforeEnter: checkLogin
beforeEnter: checkLogin,
props: {
initType: templateConstants.allTemplateTypes.Normal
}
},
{
path: '/schedule/list',
component: TransactionTemplateListPage,
beforeEnter: checkLogin,
props: {
initType: templateConstants.allTemplateTypes.Schedule
}
},
{
path: '/exchange_rates',
+5
View File
@@ -297,6 +297,11 @@ const routes = [
async: asyncResolve(TemplateListPage),
beforeEnter: [checkLogin]
},
{
path: '/schedule/list',
async: asyncResolve(TemplateListPage),
beforeEnter: [checkLogin]
},
{
path: '/template/add',
async: asyncResolve(TransactionEditPage),
+7
View File
@@ -1,6 +1,7 @@
import { defineStore } from 'pinia';
import transactionConstants from '@/consts/transaction.js';
import templateConstants from '@/consts/template.js';
import { isDefined, isObject, isArray, isEquals } from '@/lib/common.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
@@ -230,6 +231,12 @@ export const useTransactionTemplatesStore = defineStore('transactionTemplates',
submitTemplate.clientSessionId = clientSessionId;
}
if (template.templateType === templateConstants.allTemplateTypes.Schedule) {
submitTemplate.scheduledFrequencyType = template.scheduledFrequencyType;
submitTemplate.scheduledFrequency = template.scheduledFrequency;
submitTemplate.utcOffset = template.utcOffset;
}
if (template.type === transactionConstants.allTransactionTypes.Expense) {
submitTemplate.categoryId = template.expenseCategory;
} else if (template.type === transactionConstants.allTransactionTypes.Income) {
+18 -1
View File
@@ -67,6 +67,12 @@
<span class="nav-item-title">{{ $t('Transaction Templates') }}</span>
</router-link>
</li>
<li class="nav-link" v-if="isUserScheduledTransactionEnabled">
<router-link to="/schedule/list">
<v-icon class="nav-item-icon" :icon="icons.scheduledTransactions"/>
<span class="nav-item-title">{{ $t('Scheduled Transactions') }}</span>
</router-link>
</li>
<li class="nav-section-title">
<div class="title-wrapper">
<span class="title-text">{{ $t('Miscellaneous') }}</span>
@@ -168,7 +174,7 @@
</div>
<div class="layout-page-content">
<div class="page-content-container">
<router-view/>
<router-view :key="currentRoutePath" />
</div>
</div>
</div>
@@ -188,6 +194,7 @@
<script>
import { useDisplay } from 'vuetify';
import { useTheme } from 'vuetify';
import { useRoute } from 'vue-router';
import { mapStores } from 'pinia';
import { useRootStore } from '@/stores/index.js';
@@ -195,6 +202,7 @@ import { useSettingsStore } from '@/stores/setting.js';
import { useUserStore } from '@/stores/user.js';
import assetConstants from '@/consts/asset.js';
import { isUserScheduledTransactionEnabled } from '@/lib/server_settings.js';
import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui.js';
import {
@@ -205,6 +213,7 @@ import {
mdiViewDashboardOutline,
mdiTagOutline,
mdiClipboardTextOutline,
mdiClipboardTextClockOutline,
mdiChartPieOutline,
mdiSwapHorizontal,
mdiCogOutline,
@@ -235,6 +244,7 @@ export default {
categories: mdiViewDashboardOutline,
tags: mdiTagOutline,
templates: mdiClipboardTextOutline,
scheduledTransactions: mdiClipboardTextClockOutline,
statistics: mdiChartPieOutline,
exchangeRates: mdiSwapHorizontal,
settings: mdiCogOutline,
@@ -258,6 +268,10 @@ export default {
mdAndDown() {
return this.display.mdAndDown.value;
},
currentRoutePath() {
const route = useRoute();
return route.path;
},
currentNickName() {
return this.userStore.currentUserNickname || this.$t('User');
},
@@ -280,6 +294,9 @@ export default {
}
}
},
isUserScheduledTransactionEnabled() {
return isUserScheduledTransactionEnabled();
},
isEnableApplicationLock() {
return this.settingsStore.appSettings.applicationLock;
}
+22 -7
View File
@@ -4,7 +4,7 @@
<v-card>
<template #title>
<div class="title-and-toolbar d-flex align-center">
<span>{{ $t('Transaction Templates') }}</span>
<span>{{ templateType === allTemplateTypes.Schedule ? $t('Scheduled Transactions') : $t('Transaction Templates') }}</span>
<v-btn class="ml-3" color="default" variant="outlined"
:disabled="loading || updating" @click="add">{{ $t('Add') }}</v-btn>
<v-btn class="ml-3" color="primary" variant="tonal"
@@ -60,7 +60,9 @@
<tbody v-if="!loading && noAvailableTemplate">
<tr>
<td>{{ $t('No available template. Once you add templates, you can quickly add a new transaction using the dropdown menu of the Add button on the transaction list page') }}</td>
<td v-if="templateType === allTemplateTypes.Normal">{{ $t('No available template. Once you add templates, you can quickly add a new transaction using the dropdown menu of the Add button on the transaction list page') }}</td>
<td v-if="templateType === allTemplateTypes.Schedule">{{ $t('No available scheduled transactions') }}</td>
<td v-else>{{ $t('No available template') }}</td>
</tr>
</tbody>
@@ -79,9 +81,9 @@
<v-badge class="right-bottom-icon" color="secondary"
location="bottom right" offset-x="8" :icon="icons.hide"
v-if="element.hidden">
<v-icon size="20" start :icon="icons.text"/>
<v-icon size="20" start :icon="templateType === allTemplateTypes.Schedule ? icons.clock : icons.text"/>
</v-badge>
<v-icon size="20" start :icon="icons.text" v-else-if="!element.hidden"/>
<v-icon size="20" start :icon="templateType === allTemplateTypes.Schedule ? icons.clock : icons.text" v-else-if="!element.hidden"/>
<span class="transaction-template-name">{{ element.name }}</span>
</div>
@@ -162,13 +164,17 @@ import {
mdiDeleteOutline,
mdiDrag,
mdiDotsVertical,
mdiTextBoxOutline
mdiTextBoxOutline,
mdiClockTimeNineOutline
} from '@mdi/js';
export default {
components: {
EditDialog
},
props: [
'initType',
],
data() {
return {
templateType: templateConstants.allTemplateTypes.Normal,
@@ -189,7 +195,8 @@ export default {
remove: mdiDeleteOutline,
drag: mdiDrag,
more: mdiDotsVertical,
text: mdiTextBoxOutline
text: mdiTextBoxOutline,
clock: mdiClockTimeNineOutline
}
};
},
@@ -217,11 +224,15 @@ export default {
}
return count;
},
allTemplateTypes() {
return templateConstants.allTemplateTypes;
}
},
created() {
const self = this;
self.templateType = self.initType;
self.loading = true;
self.transactionTemplatesStore.loadAllTemplates({
@@ -325,6 +336,7 @@ export default {
self.$refs.editDialog.open({
id: template.id,
currentTemplate: {
templateType: template.templateType,
name: template.name,
type: template.type,
categoryId: template.categoryId,
@@ -334,7 +346,10 @@ export default {
destinationAmount: template.destinationAmount,
hideAmount: template.hideAmount,
tagIds: template.tagIds,
comment: template.comment
comment: template.comment,
scheduledFrequencyType: template.scheduledFrequencyType,
scheduledFrequency: template.scheduledFrequency,
utcOffset: template.utcOffset
}
}).then(result => {
if (result && result.message) {
@@ -195,7 +195,15 @@
v-model="transaction.time"
@error="showDateTimeError" />
</v-col>
<v-col cols="12" md="6" v-if="type === 'transaction'">
<v-col cols="12" md="6" v-if="type === 'template' && transaction.templateType === allTemplateTypes.Schedule">
<schedule-frequency-select
:readonly="mode === 'view'"
:disabled="loading || submitting"
:label="$t('Scheduled Transaction Frequency')"
v-model:type="transaction.scheduledFrequencyType"
v-model="transaction.scheduledFrequency" />
</v-col>
<v-col cols="12" md="6" v-if="type === 'transaction' || (type === 'template' && transaction.templateType === allTemplateTypes.Schedule)">
<v-autocomplete
class="transaction-edit-timezone"
item-title="displayNameWithUtcOffset"
@@ -349,6 +357,7 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
import categoryConstants from '@/consts/category.js';
import transactionConstants from '@/consts/transaction.js';
import templateConstants from '@/consts/template.js';
import logger from '@/lib/logger.js';
import {
getNameByKeyValue
@@ -423,12 +432,18 @@ export default {
} else {
return 'Transaction Detail';
}
} else if (this.type === 'template') {
} else if (this.type === 'template' && this.transaction.templateType === templateConstants.allTemplateTypes.Normal) {
if (this.mode === 'add') {
return 'Add Transaction Template';
} else if (this.mode === 'edit') {
return 'Edit Transaction Template';
}
} else if (this.type === 'template' && this.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
if (this.mode === 'add') {
return 'Add Scheduled Transaction';
} else if (this.mode === 'edit') {
return 'Edit Scheduled Transaction';
}
}
return '';
@@ -497,6 +512,9 @@ export default {
allCategoryTypes() {
return categoryConstants.allCategoryTypes;
},
allTemplateTypes() {
return templateConstants.allTemplateTypes;
},
allTimezones() {
return this.$locale.getAllTimezones(true);
},
@@ -721,11 +739,22 @@ export default {
self.transaction.templateType = options.templateType;
}
if (self.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
self.transaction.scheduledFrequencyType = templateConstants.allTemplateScheduledFrequencyTypes.Disabled.type;
self.transaction.scheduledFrequency = '';
}
if (options && options.id) {
if (options.currentTemplate) {
self.setTransaction(options.currentTemplate, options, false, false);
self.transaction.templateType = options.currentTemplate.templateType;
self.transaction.name = options.currentTemplate.name;
if (self.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
self.transaction.scheduledFrequencyType = options.currentTemplate.scheduledFrequencyType;
self.transaction.scheduledFrequency = options.currentTemplate.scheduledFrequency;
self.transaction.utcOffset = options.currentTemplate.utcOffset;
}
}
self.mode = 'edit';
@@ -772,6 +801,12 @@ export default {
self.setTransaction(template, options, false, false);
self.transaction.templateType = template.templateType;
self.transaction.name = template.name;
if (self.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
self.transaction.scheduledFrequencyType = template.scheduledFrequencyType;
self.transaction.scheduledFrequency = template.scheduledFrequency;
self.transaction.utcOffset = template.utcOffset;
}
} else {
self.setTransaction(null, options, true, true);
}
@@ -48,6 +48,12 @@
count: displayDataStatistics ? displayDataStatistics.totalTransactionTemplateCount : '-',
icon: icons.templates,
color: 'secondary-darken-1'
},
{
title: 'Scheduled Transactions',
count: displayDataStatistics ? displayDataStatistics.totalScheduledTransactionCount : '-',
icon: icons.scheduledTransactions,
color: 'success-darken-1'
}
]">
<div class="d-flex align-center">
@@ -161,6 +167,7 @@ import {
mdiViewDashboardOutline,
mdiTagOutline,
mdiClipboardTextOutline,
mdiClipboardTextClockOutline,
mdiAlert
} from '@mdi/js';
@@ -179,6 +186,7 @@ export default {
categories: mdiViewDashboardOutline,
tags: mdiTagOutline,
templates: mdiClipboardTextOutline,
scheduledTransactions: mdiClipboardTextClockOutline,
alert: mdiAlert
}
}
@@ -197,7 +205,8 @@ export default {
totalAccountCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalAccountCount),
totalTransactionCategoryCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionCategoryCount),
totalTransactionTagCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTagCount),
totalTransactionTemplateCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTemplateCount)
totalTransactionTemplateCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTemplateCount),
totalScheduledTransactionCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalScheduledTransactionCount)
};
},
isDataExportingEnabled() {
+1
View File
@@ -8,6 +8,7 @@
<f7-list-item :title="$t('Transaction Categories')" link="/category/all"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" link="/tag/list"></f7-list-item>
<f7-list-item :title="$t('Transaction Templates')" link="/template/list"></f7-list-item>
<f7-list-item :title="$t('Scheduled Transactions')" link="/schedule/list"></f7-list-item>
<f7-list-item :title="$t('Data Management')" link="/user/data/management"></f7-list-item>
<f7-list-item :title="$t('Two-Factor Authentication')" link="/user/2fa"></f7-list-item>
<f7-list-item :title="$t('Device & Sessions')" link="/user/sessions"></f7-list-item>
+17 -4
View File
@@ -2,7 +2,7 @@
<f7-page :ptr="!sortable" @ptr:refresh="reload" @page:afterin="onPageAfterIn">
<f7-navbar>
<f7-nav-left :back-link="$t('Back')"></f7-nav-left>
<f7-nav-title :title="$t('Transaction Templates')"></f7-nav-title>
<f7-nav-title :title="templateType === allTemplateTypes.Schedule ? $t('Scheduled Transactions') : $t('Transaction Templates')"></f7-nav-title>
<f7-nav-right class="navbar-compact-icons">
<f7-link icon-f7="ellipsis" :class="{ 'disabled': !templates.length }" v-if="!sortable" @click="showMoreActionSheet = true"></f7-link>
<f7-link :href="'/template/add?templateType=' + templateType" icon-f7="plus" v-if="!sortable"></f7-link>
@@ -21,7 +21,10 @@
<f7-list strong inset dividers class="margin-top" v-if="!loading && noAvailableTemplate">
<f7-list-item :title="$t('No available template')"
:footer="$t('Once you add templates, you can long press the Add button on the home page to quickly add a new transaction')"></f7-list-item>
:footer="$t('Once you add templates, you can long press the Add button on the home page to quickly add a new transaction')"
v-if="templateType === allTemplateTypes.Normal"></f7-list-item>
<f7-list-item :title="$t('No available scheduled transactions')" v-if="templateType === allTemplateTypes.Schedule"></f7-list-item>
<f7-list-item :title="$t('No available template')" v-else></f7-list-item>
</f7-list>
<f7-list strong inset dividers sortable class="margin-top template-list"
@@ -37,7 +40,7 @@
v-show="showHidden || !template.hidden"
@taphold="setSortable()">
<template #media>
<f7-icon f7="doc_plaintext">
<f7-icon :f7="templateType === allTemplateTypes.Schedule ? 'clock' : 'doc_plaintext'">
<f7-badge color="gray" class="right-bottom-icon" v-if="template.hidden">
<f7-icon f7="eye_slash_fill"></f7-icon>
</f7-badge>
@@ -91,6 +94,7 @@ import { onSwipeoutDeleted } from '@/lib/ui.mobile.js';
export default {
props: [
'f7route',
'f7router'
],
data() {
@@ -138,11 +142,20 @@ export default {
}
return true;
},
allTemplateTypes() {
return templateConstants.allTemplateTypes;
}
},
created() {
const self = this;
if (self.f7route.path === '/template/list') {
self.templateType = templateConstants.allTemplateTypes.Normal;
} else if (self.f7route.path === '/schedule/list') {
self.templateType = templateConstants.allTemplateTypes.Schedule;
}
self.loading = true;
self.transactionTemplatesStore.loadAllTemplates({
@@ -263,7 +276,7 @@ export default {
});
},
edit(template) {
this.f7router.navigate('/template/edit?id=' + template.id);
this.f7router.navigate(`/template/edit?id=${template.id}&templateType=${template.templateType}`);
},
hide(template, hidden) {
const self = this;
+78 -3
View File
@@ -37,7 +37,8 @@
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow" header="Category" title="Category Names"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Account" title="Account Name"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Transaction Time" title="YYYY/MM/DD HH:mm:ss" v-if="type === 'transaction'"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after" header="Transaction Timezone" title="(UTC XX:XX) System Default" link="#" :no-chevron="mode === 'view'" v-if="type === 'transaction'"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title" header="Scheduled Transaction Frequency" title="Every XXXXX" v-if="type === 'template' && transaction.templateType === allTemplateTypes.Schedule"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after" header="Transaction Timezone" title="(UTC XX:XX) System Default" link="#" :no-chevron="mode === 'view'" v-if="type === 'transaction' || (type === 'template' && transaction.templateType === allTemplateTypes.Schedule)"></f7-list-item>
<f7-list-item class="list-item-with-header-and-title list-item-title-hide-overflow" header="Geographic Location" title="No Location" v-if="type === 'transaction'"></f7-list-item>
<f7-list-item header="Tags">
<template #footer>
@@ -242,13 +243,28 @@
</date-time-selection-sheet>
</f7-list-item>
<f7-list-item
class="list-item-with-header-and-title"
link="#" no-chevron
:class="{ 'readonly': mode === 'view' }"
:header="$t('Scheduled Transaction Frequency')"
:title="transactionDisplayScheduledFrequency"
@click="showTransactionScheduledFrequencySheet = true"
v-if="type === 'template' && transaction.templateType === allTemplateTypes.Schedule"
>
<schedule-frequency-sheet v-model:show="showTransactionScheduledFrequencySheet"
v-model:type="transaction.scheduledFrequencyType"
v-model="transaction.scheduledFrequency">
</schedule-frequency-sheet>
</f7-list-item>
<f7-list-item
:no-chevron="mode === 'view'"
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
:class="{ 'readonly': mode === 'view' }"
:header="$t('Transaction Timezone')"
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Transaction Timezone'), popupCloseLinkText: $t('Done') }"
v-if="type === 'transaction'"
v-if="type === 'transaction' || (type === 'template' && transaction.templateType === allTemplateTypes.Schedule)"
>
<select v-model="transaction.timeZone">
<option :value="timezone.name" :key="timezone.name"
@@ -424,6 +440,7 @@ export default {
showSourceAccountSheet: false,
showDestinationAccountSheet: false,
showTransactionDateTimeSheet: false,
showTransactionScheduledFrequencySheet: false,
showGeoLocationMapSheet: false,
showTransactionTagSheet: false
};
@@ -439,12 +456,18 @@ export default {
} else {
return 'Transaction Detail';
}
} else if (this.type === 'template') {
} else if (this.type === 'template' && this.transaction.templateType === templateConstants.allTemplateTypes.Normal) {
if (this.mode === 'add') {
return 'Add Transaction Template';
} else if (this.mode === 'edit') {
return 'Edit Transaction Template';
}
} else if (this.type === 'template' && this.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
if (this.mode === 'add') {
return 'Add Scheduled Transaction';
} else if (this.mode === 'edit') {
return 'Edit Scheduled Transaction';
}
}
return '';
@@ -509,6 +532,9 @@ export default {
allCategoryTypes() {
return categoryConstants.allCategoryTypes;
},
allTemplateTypes() {
return templateConstants.allTemplateTypes;
},
allTimezones() {
return this.$locale.getAllTimezones(true);
},
@@ -581,6 +607,44 @@ export default {
return `${this.$locale.formatUnixTimeToLongDateTime(this.userStore, getActualUnixTimeForStore(this.transaction.time, this.transaction.utcOffset, getBrowserTimezoneOffsetMinutes()))} (UTC${getTimezoneOffset(this.settingsStore.appSettings.timeZone)})`;
},
transactionDisplayScheduledFrequency() {
if (this.type !== 'template') {
return '';
}
if (this.transaction.scheduledFrequencyType === templateConstants.allTemplateScheduledFrequencyTypes.Disabled.type) {
return this.$t('Disabled');
}
const items = this.transaction.scheduledFrequency.split(',');
const scheduledFrequencyValues = [];
for (let i = 0; i < items.length; i++) {
if (items[i]) {
scheduledFrequencyValues.push(parseInt(items[i]));
}
}
if (this.transaction.scheduledFrequencyType === templateConstants.allTemplateScheduledFrequencyTypes.Weekly.type) {
if (scheduledFrequencyValues.length) {
return this.$t('format.misc.everyMultiDaysOfWeek', {
days: this.$locale.getMultiWeekdayLongNames(scheduledFrequencyValues)
});
} else {
return this.$t('Weekly');
}
} else if (this.transaction.scheduledFrequencyType === templateConstants.allTemplateScheduledFrequencyTypes.Monthly.type) {
if (scheduledFrequencyValues.length) {
return this.$t('format.misc.everyMultiDaysOfMonth', {
days: this.$locale.getMultiMonthdayShortNames(scheduledFrequencyValues)
});
} else {
return this.$t('Monthly');
}
} else {
return '';
}
},
transactionDisplayTimezone() {
return `UTC${getUtcOffsetByUtcOffsetMinutes(this.transaction.utcOffset)}`;
},
@@ -750,6 +814,11 @@ export default {
self.transaction.templateType = parseInt(query.templateType);
}
if (self.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
self.transaction.scheduledFrequencyType = templateConstants.allTemplateScheduledFrequencyTypes.Disabled.type;
self.transaction.scheduledFrequency = '';
}
if (query.id) {
if (self.mode === 'edit') {
self.editId = query.id;
@@ -820,6 +889,12 @@ export default {
self.transaction.id = template.id;
self.transaction.templateType = template.templateType;
self.transaction.name = template.name;
if (self.transaction.templateType === templateConstants.allTemplateTypes.Schedule) {
self.transaction.scheduledFrequencyType = template.scheduledFrequencyType;
self.transaction.scheduledFrequency = template.scheduledFrequency;
self.transaction.utcOffset = template.utcOffset;
}
}
self.loading = false;
@@ -8,6 +8,7 @@
<f7-list-item title="Transaction Categories" after="Count"></f7-list-item>
<f7-list-item title="Transaction Tags" after="Count"></f7-list-item>
<f7-list-item title="Transaction Templates" after="Count"></f7-list-item>
<f7-list-item title="Scheduled Transactions" after="Count"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
@@ -16,6 +17,7 @@
<f7-list-item :title="$t('Transaction Categories')" :after="displayDataStatistics.totalTransactionCategoryCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Tags')" :after="displayDataStatistics.totalTransactionTagCount"></f7-list-item>
<f7-list-item :title="$t('Transaction Templates')" :after="displayDataStatistics.totalTransactionTemplateCount"></f7-list-item>
<f7-list-item :title="$t('Scheduled Transactions')" :after="displayDataStatistics.totalScheduledTransactionCount"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" :class="{ 'disabled': loading }">
@@ -109,7 +111,8 @@ export default {
totalAccountCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalAccountCount),
totalTransactionCategoryCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionCategoryCount),
totalTransactionTagCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTagCount),
totalTransactionTemplateCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTemplateCount)
totalTransactionTemplateCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalTransactionTemplateCount),
totalScheduledTransactionCount: self.$locale.appendDigitGroupingSymbol(self.userStore, self.dataStatistics.totalScheduledTransactionCount)
};
},
isDataExportingEnabled() {