diff --git a/src/components/AppSidebar/DateTimePickerItem.vue b/src/components/AppSidebar/DateTimePickerItem.vue index 54066724e..e5e7c9f8f 100644 --- a/src/components/AppSidebar/DateTimePickerItem.vue +++ b/src/components/AppSidebar/DateTimePickerItem.vue @@ -115,6 +115,13 @@ export default { type: Boolean, default: false, }, + /** + * Whether the date can be considered 'overdue' + */ + checkOverdue: { + type: Boolean, + default: true, + } }, data() { return { @@ -139,7 +146,7 @@ export default { return this.date.isValid() }, isOverdue() { - return overdue(this.date) + return this.checkOverdue && overdue(this.date) }, }, methods: { diff --git a/src/models/task.js b/src/models/task.js index bc5a20da9..f0945ff1c 100644 --- a/src/models/task.js +++ b/src/models/task.js @@ -306,12 +306,14 @@ export default class Task { } } - setCompleted(completed) { + setCompleted(completed, completedDate = null) { if (completed) { - const now = ICAL.Time.fromJSDate(new Date(), true) - this.vtodo.updatePropertyWithValue('completed', now) - this._completedDate = now - this._completedDateMoment = moment(now, 'YYYYMMDDTHHmmssZ') + if (completedDate === null) { + completedDate = ICAL.Time.fromJSDate(new Date(), true) + } + this.vtodo.updatePropertyWithValue('completed', completedDate) + this._completedDate = completedDate + this._completedDateMoment = moment(completedDate, 'YYYYMMDDTHHmmssZ') } else { this.vtodo.removeProperty('completed') this._completedDate = null @@ -325,6 +327,20 @@ export default class Task { return this._completedDate } + set completedDate(completedDate) { + if (completedDate) { + this.setCompleted(true, completedDate) + this.setComplete(100) + this.setStatus('COMPLETED') + } else { + this.setCompleted(false) + if (this.complete === 100) { + this.setComplete(99) + this.setStatus('IN-PROCESS') + } + } + } + get completedDateMoment() { return this._completedDateMoment.clone() } diff --git a/src/store/tasks.js b/src/store/tasks.js index 15603a632..0b92596b8 100644 --- a/src/store/tasks.js +++ b/src/store/tasks.js @@ -605,6 +605,29 @@ const mutations = { } }, + /** + * Sets the completed date of a task + * + * @param {object} state The store data + * @param {object} data Destructuring object + * @param {Task} data.task The task + * @param {moment|null} data.completedDate The completed date moment + */ + setCompletedDate(state, { task, completedDate }) { + if (completedDate !== null) { + // Check that the completed date is in the past. + const now = moment(ICAL.Time.fromJSDate(new Date(), true), 'YYYYMMDDTHHmmssZ') + if (completedDate.isAfter(now)) { + showError(t('tasks', 'Completion date must be in the past.')) + return + } + // Convert completed date to ICALTime first + completedDate = momentToICALTime(completedDate, false) + } + // Set the completed date + task.completedDate = completedDate + }, + /** * Toggles if the start and due dates of a task are all day * @@ -1318,6 +1341,17 @@ const actions = { context.dispatch('updateTask', task) }, + /** + * Sets the completed date of a task + * + * @param {object} context The store context + * @param {Task} task The task to update + */ + async setCompletedDate(context, { task, completedDate }) { + context.commit('setCompletedDate', { task, completedDate }) + context.dispatch('updateTask', task) + }, + /** * Sets the start or due date to the given day * diff --git a/src/views/AppSidebar.vue b/src/views/AppSidebar.vue index 5fe9bc0b7..fb24cc336 100644 --- a/src/views/AppSidebar.vue +++ b/src/views/AppSidebar.vue @@ -166,6 +166,18 @@ License along with this library. If not, see . :placeholder="t('tasks', 'Select a status')" icon="IconPulse" @change-value="changeStatus" /> + + + { actual = wrapper.vm.newDueDate expect(actual.getTime()).toBe(newDueDate.getTime()) }) + + it('Task completed date is set correctly', () => { + const wrapper = shallowMount(AppSidebar, { + global: { + plugins: [store, router], + }, + }) + + let actual = wrapper.vm.newCompletedDate + expect(actual).toBe(null) + + const newCompletedDate = new Date('2019-01-01T12:00:00') + wrapper.vm.changeCompletedDate({ task: wrapper.vm.task, value: newCompletedDate }) + + actual = wrapper.vm.newCompletedDate + expect(actual.getTime()).toBe(newCompletedDate.getTime()) + }) + + it('Setting completed date to future is ignored', () => { + const wrapper = shallowMount(AppSidebar, { + global: { + plugins: [store, router], + }, + }) + + let actual = wrapper.vm.newCompletedDate + const expected = new Date('2019-01-01T12:00:00') + expect(actual.getTime()).toBe(expected.getTime()) + + const newCompletedDate = new Date('2020-01-01T12:00:01') + wrapper.vm.changeCompletedDate({ task: wrapper.vm.task, value: newCompletedDate }) + + actual = wrapper.vm.newCompletedDate + expect(actual.getTime()).toBe(expected.getTime()) + }) })