support daily and yearly intervals for scheduled transactions

This commit is contained in:
MaysWind
2026-04-13 01:34:56 +08:00
parent c828db4988
commit 63ec0e4424
27 changed files with 233 additions and 33 deletions
@@ -4,8 +4,11 @@ import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import type { TypeAndDisplayName } from '@/core/base.ts';
import { type TypeAndDisplayName, itemAndIndex } from '@/core/base.ts';
import { type DateTime } from '@/core/datetime.ts';
import { sortNumbersArray } from '@/lib/common.ts';
import { getCurrentDateTime } from '@/lib/datetime.ts';
export interface CommonScheduleFrequencySelectionProps {
type: number;
@@ -19,7 +22,8 @@ export function useScheduleFrequencySelectionBase() {
const {
getAllWeekDays,
getAvailableMonthDays,
getAllTransactionScheduledFrequencyTypes
getAllTransactionScheduledFrequencyTypes,
formatDateTimeToLongMonthDay
} = useI18n();
const userStore = useUserStore();
@@ -27,6 +31,33 @@ export function useScheduleFrequencySelectionBase() {
const allWeekDays = computed<TypeAndDisplayName[]>(() => getAllWeekDays(userStore.currentUserFirstDayOfWeek));
const allAvailableMonthDays = computed<TypeAndDisplayName[]>(() => getAvailableMonthDays(28, 3));
const allAvailableMonthAndDays = computed<TypeAndDisplayName[]>(() => {
const ret: TypeAndDisplayName[] = [];
const now: DateTime = getCurrentDateTime();
const maxDaysOfMonth: number[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
for (const [days, index] of itemAndIndex(maxDaysOfMonth)) {
const month = index + 1;
for (let day = 1; day <= days; day++) {
const dateTime = now.set({
month: month,
dayOfMonth: day,
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
ret.push({
type: month * 100 + day,
displayName: formatDateTimeToLongMonthDay(dateTime)
});
}
}
return ret;
});
function getFrequencyValues(value: string): number[] {
const values = value.split(',');
@@ -46,6 +77,7 @@ export function useScheduleFrequencySelectionBase() {
allTransactionScheduledFrequencyTypes,
allWeekDays,
allAvailableMonthDays,
allAvailableMonthAndDays,
// functions
getFrequencyValues
};
@@ -28,14 +28,18 @@
<v-list v-if="frequencyType === ScheduledTemplateFrequencyType.Disabled.type">
<v-list-item :title="tt('None')"></v-list-item>
</v-list>
<v-list v-if="frequencyType === ScheduledTemplateFrequencyType.Daily.type">
<v-list-item :title="tt('Daily')"></v-list-item>
</v-list>
<v-list select-strategy="classic" v-model:selected="frequencyValue"
v-else-if="frequencyType === ScheduledTemplateFrequencyType.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 #prepend="{ isActive }">
<v-checkbox density="compact" class="me-1" :model-value="isActive"
@update:model-value="updateFrequencyValue(weekDay.type, $event)"></v-checkbox>
<template #prepend="{ isSelected, select }">
<v-list-item-action start>
<v-checkbox-btn density="compact" :model-value="isSelected" @update:model-value="select"></v-checkbox-btn>
</v-list-item-action>
</template>
</v-list-item>
</v-list>
@@ -44,9 +48,22 @@
<v-list-item :key="monthDay.type" :value="monthDay.type" :title="monthDay.displayName"
:class="{ 'frequency-value-selected v-list-item--active text-primary': isFrequencyValueSelected(monthDay.type) }"
v-for="monthDay in allAvailableMonthDays">
<template #prepend="{ isActive }">
<v-checkbox density="compact" class="me-1" :model-value="isActive"
@update:model-value="updateFrequencyValue(monthDay.type, $event)"></v-checkbox>
<template #prepend="{ isSelected, select }">
<v-list-item-action start>
<v-checkbox-btn density="compact" :model-value="isSelected" @update:model-value="select"></v-checkbox-btn>
</v-list-item-action>
</template>
</v-list-item>
</v-list>
<v-list select-strategy="classic" v-model:selected="frequencyValue"
v-else-if="frequencyType === ScheduledTemplateFrequencyType.Yearly.type">
<v-list-item :key="monthAndDay.type" :value="monthAndDay.type" :title="monthAndDay.displayName"
:class="{ 'frequency-value-selected v-list-item--active text-primary': isFrequencyValueSelected(monthAndDay.type) }"
v-for="monthAndDay in allAvailableMonthAndDays">
<template #prepend="{ isSelected, select }">
<v-list-item-action start>
<v-checkbox-btn density="compact" :model-value="isSelected" @update:model-value="select"></v-checkbox-btn>
</v-list-item-action>
</template>
</v-list-item>
</v-list>
@@ -75,8 +92,19 @@ const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
const { tt, getMultiMonthdayShortNames, getMultiWeekdayLongNames } = useI18n();
const { allTransactionScheduledFrequencyTypes, allWeekDays, allAvailableMonthDays, getFrequencyValues } = useScheduleFrequencySelectionBase();
const {
tt,
getMultiMonthAndDayLongNames,
getMultiMonthdayShortNames,
getMultiWeekdayLongNames
} = useI18n();
const {
allTransactionScheduledFrequencyTypes,
allWeekDays,
allAvailableMonthDays,
allAvailableMonthAndDays,
getFrequencyValues
} = useScheduleFrequencySelectionBase();
const userStore = useUserStore();
@@ -92,10 +120,14 @@ const frequencyType = computed<number>({
if (props.type !== value) {
emit('update:type', value);
if (value === ScheduledTemplateFrequencyType.Weekly.type) {
if (value === ScheduledTemplateFrequencyType.Daily.type) {
frequencyValue.value = [0];
} else if (value === ScheduledTemplateFrequencyType.Weekly.type) {
frequencyValue.value = [firstDayOfWeek.value];
} else if (value === ScheduledTemplateFrequencyType.Monthly.type) {
frequencyValue.value = [1];
} else if (value === ScheduledTemplateFrequencyType.Yearly.type) {
frequencyValue.value = [101];
} else {
frequencyValue.value = [];
}
@@ -113,6 +145,8 @@ const frequencyValue = computed<number[]>({
const displayFrequency = computed<string>(() => {
if (frequencyType.value === ScheduledTemplateFrequencyType.Disabled.type) {
return tt('Disabled');
} else if (frequencyType.value === ScheduledTemplateFrequencyType.Daily.type) {
return tt('Daily');
} else if (frequencyType.value === ScheduledTemplateFrequencyType.Weekly.type) {
if (frequencyValue.value.length) {
return tt('format.misc.everyMultiDaysOfWeek', {
@@ -129,28 +163,19 @@ const displayFrequency = computed<string>(() => {
} else {
return tt('Monthly');
}
} else if (frequencyType.value === ScheduledTemplateFrequencyType.Yearly.type) {
if (frequencyValue.value.length) {
return tt('format.misc.everyMultiDaysOfYear', {
days: getMultiMonthAndDayLongNames(frequencyValue.value)
});
} else {
return tt('Yearly');
}
} else {
return '';
}
});
function updateFrequencyValue(value: number, selected: boolean | null): void {
const currentFrequencyValues = frequencyValue.value;
const newFrequencyValues: number[] = [];
for (const currentFrequencyValue of currentFrequencyValues) {
if (currentFrequencyValue !== value || selected) {
newFrequencyValues.push(currentFrequencyValue);
}
}
if (selected) {
newFrequencyValues.push(value);
}
frequencyValue.value = sortNumbersArray(newFrequencyValues);
}
function isFrequencyValueSelected(currentValue: number): boolean {
return frequencyValue.value.indexOf(currentValue) >= 0;
}
@@ -33,6 +33,10 @@
v-if="currentFrequencyType === ScheduledTemplateFrequencyType.Disabled.type">
<f7-list-item :title="tt('None')"></f7-list-item>
</f7-list>
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === ScheduledTemplateFrequencyType.Daily.type">
<f7-list-item :title="tt('Daily')"></f7-list-item>
</f7-list>
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === ScheduledTemplateFrequencyType.Weekly.type">
<f7-list-item checkbox
@@ -57,6 +61,18 @@
@change="changeFrequencyValue">
</f7-list-item>
</f7-list>
<f7-list dividers class="schedule-frequency-value-list no-margin-vertical"
v-if="currentFrequencyType === ScheduledTemplateFrequencyType.Yearly.type">
<f7-list-item checkbox
:class="isChecked(monthAndDay.type) ? 'list-item-selected' : ''"
:key="monthAndDay.type"
:value="monthAndDay.type"
:checked="isChecked(monthAndDay.type)"
:title="monthAndDay.displayName"
v-for="monthAndDay in allAvailableMonthAndDays"
@change="changeFrequencyValue">
</f7-list-item>
</f7-list>
</div>
</div>
</div>
@@ -91,7 +107,13 @@ const emit = defineEmits<{
}>();
const { tt } = useI18n();
const { allTransactionScheduledFrequencyTypes, allWeekDays, allAvailableMonthDays, getFrequencyValues } = useScheduleFrequencySelectionBase();
const {
allTransactionScheduledFrequencyTypes,
allWeekDays,
allAvailableMonthDays,
allAvailableMonthAndDays,
getFrequencyValues
} = useScheduleFrequencySelectionBase();
const userStore = useUserStore();
@@ -108,10 +130,14 @@ function changeFrequencyType(value: number): void {
if (currentFrequencyType.value !== value) {
currentFrequencyType.value = value;
if (value === ScheduledTemplateFrequencyType.Weekly.type) {
if (value === ScheduledTemplateFrequencyType.Daily.type) {
currentFrequencyValue.value = [0];
} else if (value === ScheduledTemplateFrequencyType.Weekly.type) {
currentFrequencyValue.value = [firstDayOfWeek.value];
} else if (value === ScheduledTemplateFrequencyType.Monthly.type) {
currentFrequencyValue.value = [1];
} else if (value === ScheduledTemplateFrequencyType.Yearly.type) {
currentFrequencyValue.value = [101];
} else {
currentFrequencyValue.value = [];
}