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())
+ })
})