feat(tasks): apply default reminders to quick-add tasks with due date

When a user has configured default reminders in their frontend settings,
those are cloned onto every task created via quick-add magic that has a
parsed due date. Tasks without a due date are silently skipped.

The buildDefaultRemindersForQuickAdd helper is exported as a pure
function so it can be unit-tested without stubbing the Pinia store.
This commit is contained in:
kolaente 2026-04-11 23:41:42 +02:00 committed by kolaente
parent 338d4d8b76
commit 5afd066a13
2 changed files with 69 additions and 0 deletions

View File

@ -0,0 +1,44 @@
import {describe, expect, it} from 'vitest'
import {buildDefaultRemindersForQuickAdd} from './tasks'
import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
const aDefault: ITaskReminder = {
reminder: null,
relativePeriod: -3600,
relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE,
} as ITaskReminder
describe('buildDefaultRemindersForQuickAdd', () => {
it('returns empty array when due date is null', () => {
expect(buildDefaultRemindersForQuickAdd([aDefault], null)).toEqual([])
})
it('returns empty array when defaults are undefined', () => {
expect(buildDefaultRemindersForQuickAdd(undefined, '2026-05-01T00:00:00.000Z')).toEqual([])
})
it('returns empty array when defaults are empty', () => {
expect(buildDefaultRemindersForQuickAdd([], '2026-05-01T00:00:00.000Z')).toEqual([])
})
it('clones defaults with relativeTo locked to due_date', () => {
const result = buildDefaultRemindersForQuickAdd([aDefault], '2026-05-01T00:00:00.000Z')
expect(result).toHaveLength(1)
expect(result[0].relativePeriod).toBe(-3600)
expect(result[0].relativeTo).toBe(REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE)
expect(result[0].reminder).toBeNull()
})
it('does not share references with the input array', () => {
const defaults = [aDefault]
const result = buildDefaultRemindersForQuickAdd(defaults, '2026-05-01T00:00:00.000Z')
expect(result[0]).not.toBe(defaults[0])
})
it('forces relativeTo to due_date even if a default somehow had another value', () => {
const weird = {...aDefault, relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.STARTDATE} as ITaskReminder
const result = buildDefaultRemindersForQuickAdd([weird], '2026-05-01T00:00:00.000Z')
expect(result[0].relativeTo).toBe(REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE)
})
})

View File

@ -15,13 +15,17 @@ import LabelTaskModel from '@/models/labelTask'
import LabelTask from '@/models/labelTask'
import TaskModel from '@/models/task'
import LabelModel from '@/models/label'
import TaskReminderModel from '@/models/taskReminder'
import type {ILabel} from '@/modelTypes/ILabel'
import type {ITask} from '@/modelTypes/ITask'
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
import type {IUser} from '@/modelTypes/IUser'
import type {IAttachment} from '@/modelTypes/IAttachment'
import type {IProject} from '@/modelTypes/IProject'
import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
import {setModuleLoading} from '@/stores/helper'
import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects'
@ -38,6 +42,23 @@ interface MatchedAssignee extends IUser {
match: string,
}
export function buildDefaultRemindersForQuickAdd(
defaults: readonly ITaskReminder[] | undefined,
dueDate: string | null,
): ITaskReminder[] {
if (!dueDate) {
return []
}
if (!defaults || defaults.length === 0) {
return []
}
return defaults.map(d => new TaskReminderModel({
reminder: null,
relativePeriod: d.relativePeriod,
relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE,
}))
}
// IDEA: maybe use a small fuzzy search here to prevent errors
function findPropertyByValue(object, key, value, fuzzy = false) {
return Object.values(object).find(l => {
@ -483,6 +504,10 @@ export const useTaskStore = defineStore('task', () => {
index,
})
task.repeatAfter = parsedTask.repeats
task.reminders = buildDefaultRemindersForQuickAdd(
authStore.settings.frontendSettings.quickAddDefaultReminders,
dueDate,
)
if (parsedTask.repeats?.type === REPEAT_TYPES.Months && parsedTask.repeats?.amount === 1) {
task.repeatMode = TASK_REPEAT_MODES.REPEAT_MODE_MONTH