Merge pull request #2686 from creightonfrance/1184-feature-request-editable-completion-date
feat: allow editing completion date
This commit is contained in:
Коммит
e83edcef8a
|
@ -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: {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -166,6 +166,18 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|||
:placeholder="t('tasks', 'Select a status')"
|
||||
icon="IconPulse"
|
||||
@change-value="changeStatus" />
|
||||
<DateTimePickerItem v-show="task.completed"
|
||||
:date="task.completedDateMoment"
|
||||
:value="newCompletedDate"
|
||||
:property-string="completedString"
|
||||
:read-only="readOnly"
|
||||
:task="task"
|
||||
:check-overdue=false
|
||||
@set-value="changeCompletedDate">
|
||||
<template #icon>
|
||||
<CalendarCheck :size="20" />
|
||||
</template>
|
||||
</DateTimePickerItem>
|
||||
<SliderItem v-show="!readOnly || task.priority"
|
||||
:value="task.priority"
|
||||
:property-string="priorityString"
|
||||
|
@ -289,6 +301,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
|||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
import Calendar from 'vue-material-design-icons/Calendar.vue'
|
||||
import CalendarCheck from 'vue-material-design-icons/CalendarCheck.vue'
|
||||
import CalendarEnd from 'vue-material-design-icons/CalendarEnd.vue'
|
||||
import CalendarStart from 'vue-material-design-icons/CalendarStart.vue'
|
||||
import Delete from 'vue-material-design-icons/Delete.vue'
|
||||
|
@ -319,6 +332,7 @@ export default {
|
|||
Calendar,
|
||||
CalendarEnd,
|
||||
CalendarStart,
|
||||
CalendarCheck,
|
||||
Delete,
|
||||
Download,
|
||||
InformationOutline,
|
||||
|
@ -549,6 +563,18 @@ export default {
|
|||
}
|
||||
return reference.toDate()
|
||||
},
|
||||
/**
|
||||
* Initializes the completed date of a task
|
||||
*
|
||||
* @return {Date|null} The completed date moment
|
||||
*/
|
||||
newCompletedDate() {
|
||||
const completedDate = this.task.completedDateMoment
|
||||
if (completedDate.isValid()) {
|
||||
return completedDate.toDate()
|
||||
}
|
||||
return null
|
||||
},
|
||||
taskStatusLabel() {
|
||||
return this.loading ? t('tasks', 'Loading task from server.') : t('tasks', 'Task not found!')
|
||||
},
|
||||
|
@ -709,6 +735,7 @@ export default {
|
|||
'addTag',
|
||||
'setDue',
|
||||
'setStart',
|
||||
'setCompletedDate',
|
||||
'toggleAllDay',
|
||||
'moveTask',
|
||||
'setClassification',
|
||||
|
@ -817,6 +844,23 @@ export default {
|
|||
this.setDue({ task, due, allDay: this.allDay })
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the completed date to the given Date or to null
|
||||
*
|
||||
* @param {object} context The data object
|
||||
* @param {Task} context.task The task for which to set the date
|
||||
* @param {Date|null} context.value The new completed date
|
||||
*/
|
||||
changeCompletedDate({ task, value: completedDate }) {
|
||||
if (completedDate) {
|
||||
completedDate = moment(completedDate)
|
||||
}
|
||||
if (this.task.completedDateMoment.isSame(completedDate)) {
|
||||
return
|
||||
}
|
||||
this.setCompletedDate({ task, completedDate })
|
||||
},
|
||||
|
||||
changeClass(classification) {
|
||||
this.setClassification({ task: this.task, classification: classification.type })
|
||||
},
|
||||
|
|
|
@ -55,4 +55,39 @@ describe('AppSidebar.vue', () => {
|
|||
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())
|
||||
})
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче