From f747d5b2fcadb7459c389372dca4507b75cdd4fa Mon Sep 17 00:00:00 2001 From: ce72 Date: Sat, 11 Mar 2023 19:20:07 +0100 Subject: [PATCH 01/27] feat: Use new Reminders API instead of reminder_dates --- src/components/tasks/partials/reminders.vue | 33 +++++++-------------- src/modelTypes/ITask.ts | 3 +- src/modelTypes/ITaskReminder.ts | 8 +++++ src/models/task.ts | 7 +++-- src/models/taskReminder.ts | 17 +++++++++++ src/services/task.ts | 14 ++++----- src/types/IReminderPeriodRelativeTo.ts | 8 +++++ src/views/tasks/TaskDetailView.vue | 4 +-- 8 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 src/modelTypes/ITaskReminder.ts create mode 100644 src/models/taskReminder.ts create mode 100644 src/types/IReminderPeriodRelativeTo.ts diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index 47c842954..a0af652cd 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -3,11 +3,11 @@
@@ -30,30 +30,19 @@ import {type PropType, ref, onMounted, watch} from 'vue' import BaseButton from '@/components/base/BaseButton.vue' import Datepicker from '@/components/input/datepicker.vue' - -type Reminder = Date | string - - +import TaskReminderModel from '@/models/taskReminder' +import type { ITaskReminder } from '@/modelTypes/ITaskReminder' const props = defineProps({ modelValue: { - type: Array as PropType, + type: Array as PropType, default: () => [], validator(prop) { - // This allows arrays of Dates and strings + // This allows arrays if (!(prop instanceof Array)) { return false } - const isDate = (e: unknown) => e instanceof Date - const isString = (e: unknown) => typeof e === 'string' - - for (const e of prop) { - if (!isDate(e) && !isString(e)) { - return false - } - } - return true }, }, @@ -65,7 +54,7 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue']) -const reminders = ref([]) +const reminders = ref([]) onMounted(() => { reminders.value = [...props.modelValue] @@ -75,8 +64,8 @@ watch( () => props.modelValue, (newVal) => { for (const i in newVal) { - if (typeof newVal[i] === 'string') { - newVal[i] = new Date(newVal[i]) + if (typeof newVal[i].reminder === 'string') { + newVal[i].reminder = new Date(newVal[i].reminder) } } reminders.value = newVal @@ -95,9 +84,9 @@ function addReminderDate(index : number | null = null) { if (newReminder.value === null) { return } - reminders.value.push(new Date(newReminder.value)) + reminders.value.push(new TaskReminderModel({reminder: new Date(newReminder.value)})) newReminder.value = null - } else if(reminders.value[index] === null) { + } else if(reminders.value[index].reminder === null) { return } diff --git a/src/modelTypes/ITask.ts b/src/modelTypes/ITask.ts index e66d447ef..b4cfc4582 100644 --- a/src/modelTypes/ITask.ts +++ b/src/modelTypes/ITask.ts @@ -13,6 +13,7 @@ import type {IRepeatAfter} from '@/types/IRepeatAfter' import type {IRepeatMode} from '@/types/IRepeatMode' import type {PartialWithId} from '@/types/PartialWithId' +import type {ITaskReminder} from '@/modelTypes/ITaskReminder' export interface ITask extends IAbstract { id: number @@ -30,7 +31,7 @@ export interface ITask extends IAbstract { repeatAfter: number | IRepeatAfter repeatFromCurrentDate: boolean repeatMode: IRepeatMode - reminderDates: Date[] + reminders: ITaskReminder[] parentTaskId: ITask['id'] hexColor: string percentDone: number diff --git a/src/modelTypes/ITaskReminder.ts b/src/modelTypes/ITaskReminder.ts new file mode 100644 index 000000000..25aa493f2 --- /dev/null +++ b/src/modelTypes/ITaskReminder.ts @@ -0,0 +1,8 @@ +import type { IAbstract } from './IAbstract' +import type { IReminderPeriodRelativeTo } from '@/types/IReminderPeriodRelativeTo' + +export interface ITaskReminder extends IAbstract { + reminder: Date + relativePeriod: number + relativeTo: IReminderPeriodRelativeTo +} \ No newline at end of file diff --git a/src/models/task.ts b/src/models/task.ts index 9974f41b9..7a35d9214 100644 --- a/src/models/task.ts +++ b/src/models/task.ts @@ -20,6 +20,8 @@ import LabelModel from './label' import UserModel from './user' import AttachmentModel from './attachment' import SubscriptionModel from './subscription' +import type {ITaskReminder} from '@/modelTypes/ITaskReminder' +import TaskReminderModel from '@/models/taskReminder' export const TASK_DEFAULT_COLOR = '#1973ff' @@ -68,7 +70,8 @@ export default class TaskModel extends AbstractModel implements ITask { repeatAfter: number | IRepeatAfter = 0 repeatFromCurrentDate = false repeatMode: IRepeatMode = TASK_REPEAT_MODES.REPEAT_MODE_DEFAULT - reminderDates: Date[] = [] + reminderDates = null + reminders: ITaskReminder[] = [] parentTaskId: ITask['id'] = 0 hexColor = '' percentDone = 0 @@ -115,7 +118,7 @@ export default class TaskModel extends AbstractModel implements ITask { // Parse the repeat after into something usable this.repeatAfter = parseRepeatAfter(this.repeatAfter as number) - this.reminderDates = this.reminderDates.map(d => new Date(d)) + this.reminders = this.reminders.map(r => new TaskReminderModel(r)) if (this.hexColor !== '' && this.hexColor.substring(0, 1) !== '#') { this.hexColor = '#' + this.hexColor diff --git a/src/models/taskReminder.ts b/src/models/taskReminder.ts new file mode 100644 index 000000000..b0a52b519 --- /dev/null +++ b/src/models/taskReminder.ts @@ -0,0 +1,17 @@ +import AbstractModel from './abstractModel' +import type {ITaskReminder} from '@/modelTypes/ITaskReminder' +import {parseDateOrNull} from '@/helpers/parseDateOrNull' +import type {IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo' + +export default class TaskReminderModel extends AbstractModel implements ITaskReminder { + reminder: Date | null + relativePeriod: number = 0 + relativeTo: IReminderPeriodRelativeTo | null = null + + constructor(data: Partial = {}) { + super() + this.assignData(data) + this.reminder = parseDateOrNull(data.reminder) + } + +} \ No newline at end of file diff --git a/src/services/task.ts b/src/services/task.ts index f40642b0c..a05bdacaf 100644 --- a/src/services/task.ts +++ b/src/services/task.ts @@ -54,17 +54,17 @@ export default class TaskService extends AbstractService { model.created = new Date(model.created).toISOString() model.updated = new Date(model.updated).toISOString() + model.reminderDates = null // remove all nulls, these would create empty reminders - for (const index in model.reminderDates) { - if (model.reminderDates[index] === null) { - model.reminderDates.splice(index, 1) + for (const index in model.reminders) { + if (model.reminders[index] === null) { + model.reminders.splice(index, 1) } } - // Make normal timestamps from js dates - if (model.reminderDates.length > 0) { - model.reminderDates = model.reminderDates.map(r => { - return new Date(r).toISOString() + if (model.reminders.length > 0) { + model.reminders.forEach(r => { + r.reminder = new Date(r.reminder).toISOString() }) } diff --git a/src/types/IReminderPeriodRelativeTo.ts b/src/types/IReminderPeriodRelativeTo.ts new file mode 100644 index 000000000..4254ebf7b --- /dev/null +++ b/src/types/IReminderPeriodRelativeTo.ts @@ -0,0 +1,8 @@ +export const REMINDER_PERIOD_RELATIVE_TO_TYPES = { + DUEDATE: 'due_date', + STARTDATE: 'start_date', + ENDDATE: 'end_date', +} as const + +export type IReminderPeriodRelativeTo = typeof REMINDER_PERIOD_RELATIVE_TO_TYPES[keyof typeof REMINDER_PERIOD_RELATIVE_TO_TYPES] + diff --git a/src/views/tasks/TaskDetailView.vue b/src/views/tasks/TaskDetailView.vue index e891869d9..7bb41375e 100644 --- a/src/views/tasks/TaskDetailView.vue +++ b/src/views/tasks/TaskDetailView.vue @@ -160,7 +160,7 @@
@@ -639,7 +639,7 @@ function setActiveFields() { activeFields.percentDone = task.percentDone > 0 activeFields.priority = task.priority !== PRIORITIES.UNSET activeFields.relatedTasks = Object.keys(task.relatedTasks).length > 0 - activeFields.reminders = task.reminderDates.length > 0 + activeFields.reminders = task.reminders.length > 0 activeFields.repeatAfter = task.repeatAfter.amount > 0 activeFields.startDate = task.startDate !== null } -- 2.40.1 From 5d38b8327fc323c571fced33442bdb923d6d3baa Mon Sep 17 00:00:00 2001 From: ce72 Date: Tue, 14 Mar 2023 06:49:49 +0100 Subject: [PATCH 02/27] feat: allow to edit existing relative reminders --- .../tasks/partials/reminder-detail.vue | 211 ++++++++++++++++++ src/components/tasks/partials/reminders.vue | 18 +- src/helpers/time/period.ts | 19 ++ src/modelTypes/ITaskReminder.ts | 4 +- 4 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 src/components/tasks/partials/reminder-detail.vue create mode 100644 src/helpers/time/period.ts diff --git a/src/components/tasks/partials/reminder-detail.vue b/src/components/tasks/partials/reminder-detail.vue new file mode 100644 index 000000000..2554a9043 --- /dev/null +++ b/src/components/tasks/partials/reminder-detail.vue @@ -0,0 +1,211 @@ + + + + + \ No newline at end of file diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index a0af652cd..a8ff8afb7 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -6,7 +6,10 @@ :class="{ 'overdue': r.reminder < new Date()}" class="reminder-input" > + \ No newline at end of file diff --git a/src/components/tasks/partials/reminder-period.vue b/src/components/tasks/partials/reminder-period.vue new file mode 100644 index 000000000..27adc5522 --- /dev/null +++ b/src/components/tasks/partials/reminder-period.vue @@ -0,0 +1,218 @@ + + + + + \ No newline at end of file diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index a8ff8afb7..2293f4356 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -1,53 +1,43 @@ - - \ No newline at end of file diff --git a/src/components/tasks/partials/reminder-period.vue b/src/components/tasks/partials/reminder-period.vue index 27adc5522..8c9157d3c 100644 --- a/src/components/tasks/partials/reminder-period.vue +++ b/src/components/tasks/partials/reminder-period.vue @@ -1,78 +1,79 @@ \ No newline at end of file diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index 2293f4356..a4e540838 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -1,9 +1,8 @@ diff --git a/src/components/tasks/partials/reminder-period.vue b/src/components/tasks/partials/reminder-period.vue index 391f9b923..365183c7c 100644 --- a/src/components/tasks/partials/reminder-period.vue +++ b/src/components/tasks/partials/reminder-period.vue @@ -1,78 +1,96 @@ + + diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index a4e540838..f785d2166 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -1,32 +1,41 @@ \ No newline at end of file -- 2.40.1 From 6c2dc483a20213f1f238e6224b9ecfb87faa2461 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 31 May 2023 16:27:20 +0200 Subject: [PATCH 09/27] fix: redundant ) --- src/components/tasks/partials/reminder-period.vue | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/tasks/partials/reminder-period.vue b/src/components/tasks/partials/reminder-period.vue index 365183c7c..c0a60d3db 100644 --- a/src/components/tasks/partials/reminder-period.vue +++ b/src/components/tasks/partials/reminder-period.vue @@ -179,9 +179,7 @@ const relativeToOptions = { [REMINDER_PERIOD_RELATIVE_TO_TYPES.ENDDATE]: t('task.attributes.endDate'), } as const -const relativeTo = computed(() => relativeToOptions[periodInput.relativeTo])) - - +const relativeTo = computed(() => relativeToOptions[periodInput.relativeTo]) function formatRelativeTo(relativeTo: IReminderPeriodRelativeTo | null): string | null { switch (relativeTo) { -- 2.40.1 From 9df6950d1a4a361c075020319ce3037e19e0912d Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 5 Jun 2023 16:16:10 +0200 Subject: [PATCH 10/27] feat: start adding relative reminder picker with more options --- .../tasks/partials/reminder-detail.vue | 77 +++++++++++++++++-- .../tasks/partials/reminder-period.vue | 11 ++- .../tasks/partials/reminders.story.vue | 25 ++++-- 3 files changed, 98 insertions(+), 15 deletions(-) diff --git a/src/components/tasks/partials/reminder-detail.vue b/src/components/tasks/partials/reminder-detail.vue index 778416fa6..f65a28aa6 100644 --- a/src/components/tasks/partials/reminder-detail.vue +++ b/src/components/tasks/partials/reminder-detail.vue @@ -1,5 +1,19 @@ @@ -34,16 +35,17 @@ import {computed, ref, watch, type PropType} from 'vue' import {toRef} from '@vueuse/core' import {SECONDS_A_DAY} from '@/constants/date' -import {secondsToPeriod} from '@/helpers/time/period' +import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo' +import {secondsToPeriod} from '@/helpers/time/period' import type {ITaskReminder} from '@/modelTypes/ITaskReminder' import {formatDateShort} from '@/helpers/time/formatDate' -import Datepicker from '@/components/input/datepicker.vue' -import ReminderPeriod from '@/components/tasks/partials/reminder-period.vue' -import TaskReminderModel from '@/models/taskReminder' import BaseButton from '@/components/base/BaseButton.vue' -import {REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo' +import DatepickerInline from '@/components/input/datepickerInline.vue' +import ReminderPeriod from '@/components/tasks/partials/reminder-period.vue' + +import TaskReminderModel from '@/models/taskReminder' const props = defineProps({ modelValue: { @@ -65,20 +67,9 @@ const presets: TaskReminderModel[] = [ {relativePeriod: SECONDS_A_DAY * 7, relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE}, {relativePeriod: SECONDS_A_DAY * 30, relativeTo: REMINDER_PERIOD_RELATIVE_TO_TYPES.DUEDATE}, ] -const reminderDate = computed({ - get() { - return reminder.value?.reminder - }, - set(newReminderDate) { - if (!reminderDate.value) { - return - } - reminder.value.reminder = new Date(reminderDate.value) - }, -}) +const reminderDate = ref(null) -const showAbsoluteReminder = computed(() => !reminder.value || !reminder.value?.relativeTo) -const showRelativeReminder = computed(() => !reminder.value || reminder.value?.relativeTo) +const showFormSwitch = ref(null) const reminderText = computed(() => { @@ -103,10 +94,9 @@ watch( ) function setReminderDate() { - if (!reminderDate.value) { - return - } - reminder.value.reminder = new Date(reminderDate.value) + reminder.value.reminder = reminderDate.value === null + ? null + : new Date(reminderDate.value) emit('update:modelValue', reminder.value) } @@ -123,12 +113,12 @@ function formatReminder(reminder: TaskReminderModel) { periodHuman = period.days + ' day' } - return periodHuman + ' ' + (reminder.relativePeriod > 0 ? 'before' : 'after') + ' ' + reminder.relativeTo + return periodHuman + ' ' + (reminder.relativePeriod <= 0 ? 'before' : 'after') + ' ' + reminder.relativeTo } \ No newline at end of file diff --git a/src/components/tasks/partials/reminders.vue b/src/components/tasks/partials/reminders.vue index f785d2166..a6ff11504 100644 --- a/src/components/tasks/partials/reminders.vue +++ b/src/components/tasks/partials/reminders.vue @@ -7,11 +7,14 @@ class="reminder-input" >
- +
- - + +
@@ -31,7 +34,7 @@ diff --git a/src/components/tasks/partials/reminder-period.vue b/src/components/tasks/partials/reminder-period.vue index 3b9de3cc5..59631156f 100644 --- a/src/components/tasks/partials/reminder-period.vue +++ b/src/components/tasks/partials/reminder-period.vue @@ -12,20 +12,20 @@
diff --git a/src/i18n/lang/en.json b/src/i18n/lang/en.json index ca7f394f9..ea435ff50 100644 --- a/src/i18n/lang/en.json +++ b/src/i18n/lang/en.json @@ -721,10 +721,13 @@ } }, "reminder": { - "hoursShort": "HH", - "minutesShort": "MM", - "daysShort": "d", - "days": "days" + "before": "{amount} {unit} before {type}", + "after": "{amount} {unit} after {type}", + "beforeShort": "before", + "afterShort": "after", + "onDueDate": "On the due date", + "onStartDate": "On the start date", + "onEndDate": "On the end date" }, "repeat": { "everyDay": "Every Day", @@ -990,5 +993,16 @@ "title": "About", "frontendVersion": "Frontend Version: {version}", "apiVersion": "API Version: {version}" + }, + "time": { + "units": { + "seconds": "second|seconds", + "minutes": "minute|minutes", + "hours": "hour|hours", + "days": "day|days", + "weeks": "week|weeks", + "months": "month|months", + "years": "year|years" + } } } \ No newline at end of file -- 2.40.1 From ae177c73ea6f9f4ef90f75ce0375c2707c1532f7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Fri, 9 Jun 2023 14:23:32 +0200 Subject: [PATCH 14/27] feat(reminders): move reminder settings to a popup --- src/components/input/SimpleButton.vue | 26 +++++ src/components/input/datepicker.vue | 8 +- src/components/misc/popup.vue | 3 + .../tasks/partials/reminder-detail.vue | 102 ++++++++++++------ .../tasks/partials/reminder-period.vue | 1 - 5 files changed, 103 insertions(+), 37 deletions(-) create mode 100644 src/components/input/SimpleButton.vue diff --git a/src/components/input/SimpleButton.vue b/src/components/input/SimpleButton.vue new file mode 100644 index 000000000..ffe8c5b56 --- /dev/null +++ b/src/components/input/SimpleButton.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/src/components/input/datepicker.vue b/src/components/input/datepicker.vue index f36f6480e..b8a3f8d67 100644 --- a/src/components/input/datepicker.vue +++ b/src/components/input/datepicker.vue @@ -1,8 +1,8 @@