Cleanup code & make sure it has a common code style
This commit is contained in:
@ -4,45 +4,45 @@
|
||||
<label class="label" for="tasktext">Task Text</label>
|
||||
<div class="control">
|
||||
<input
|
||||
v-focus
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:disabled="taskService.loading"
|
||||
class="input"
|
||||
type="text"
|
||||
id="tasktext"
|
||||
placeholder="The task text is here..."
|
||||
v-model="taskEditTask.title"
|
||||
@change="editTaskSubmit()"/>
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:disabled="taskService.loading"
|
||||
@change="editTaskSubmit()"
|
||||
class="input"
|
||||
id="tasktext"
|
||||
placeholder="The task text is here..."
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="taskEditTask.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="taskdescription">Description</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
placeholder="The tasks description goes here..."
|
||||
id="taskdescription"
|
||||
v-model="taskEditTask.description"
|
||||
:preview-is-default="false"
|
||||
v-if="editorActive"
|
||||
:preview-is-default="false"
|
||||
id="taskdescription"
|
||||
placeholder="The tasks description goes here..."
|
||||
v-if="editorActive"
|
||||
v-model="taskEditTask.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b>Reminder Dates</b>
|
||||
<reminders v-model="taskEditTask.reminderDates" @change="editTaskSubmit()"/>
|
||||
<reminders @change="editTaskSubmit()" v-model="taskEditTask.reminderDates"/>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="taskduedate">Due Date</label>
|
||||
<div class="control">
|
||||
<flat-pickr
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
class="input"
|
||||
:disabled="taskService.loading"
|
||||
v-model="taskEditTask.dueDate"
|
||||
:config="flatPickerConfig"
|
||||
@on-close="editTaskSubmit()"
|
||||
id="taskduedate"
|
||||
placeholder="The tasks due date is here...">
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading"
|
||||
@on-close="editTaskSubmit()"
|
||||
class="input"
|
||||
id="taskduedate"
|
||||
placeholder="The tasks due date is here..."
|
||||
v-model="taskEditTask.dueDate">
|
||||
</flat-pickr>
|
||||
</div>
|
||||
</div>
|
||||
@ -52,26 +52,26 @@
|
||||
<div class="control columns">
|
||||
<div class="column">
|
||||
<flat-pickr
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
class="input"
|
||||
:disabled="taskService.loading"
|
||||
v-model="taskEditTask.startDate"
|
||||
:config="flatPickerConfig"
|
||||
@on-close="editTaskSubmit()"
|
||||
id="taskduedate"
|
||||
placeholder="Start date">
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading"
|
||||
@on-close="editTaskSubmit()"
|
||||
class="input"
|
||||
id="taskduedate"
|
||||
placeholder="Start date"
|
||||
v-model="taskEditTask.startDate">
|
||||
</flat-pickr>
|
||||
</div>
|
||||
<div class="column">
|
||||
<flat-pickr
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
class="input"
|
||||
:disabled="taskService.loading"
|
||||
v-model="taskEditTask.endDate"
|
||||
:config="flatPickerConfig"
|
||||
@on-close="editTaskSubmit()"
|
||||
id="taskduedate"
|
||||
placeholder="End date">
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading"
|
||||
@on-close="editTaskSubmit()"
|
||||
class="input"
|
||||
id="taskduedate"
|
||||
placeholder="End date"
|
||||
v-model="taskEditTask.endDate">
|
||||
</flat-pickr>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,20 +79,20 @@
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="">Repeat after</label>
|
||||
<repeat-after v-model="taskEditTask.repeatAfter" @change="editTaskSubmit()"/>
|
||||
<repeat-after @change="editTaskSubmit()" v-model="taskEditTask.repeatAfter"/>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="">Priority</label>
|
||||
<div class="control priority-select">
|
||||
<priority-select v-model="taskEditTask.priority" @change="editTaskSubmit()"/>
|
||||
<priority-select @change="editTaskSubmit()" v-model="taskEditTask.priority"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Percent Done</label>
|
||||
<div class="control">
|
||||
<percent-done-select v-model="taskEditTask.percentDone" @change="editTaskSubmit()"/>
|
||||
<percent-done-select @change="editTaskSubmit()" v-model="taskEditTask.percentDone"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -106,8 +106,8 @@
|
||||
<div class="field">
|
||||
<label class="label" for="">Assignees</label>
|
||||
<ul class="assingees">
|
||||
<li v-for="(a, index) in taskEditTask.assignees" :key="a.id">
|
||||
{{a.username}}
|
||||
<li :key="a.id" v-for="(a, index) in taskEditTask.assignees">
|
||||
{{ a.username }}
|
||||
<a @click="deleteAssigneeByIndex(index)">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
@ -118,9 +118,9 @@
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<edit-assignees
|
||||
:task-id="taskEditTask.id"
|
||||
:list-id="taskEditTask.listId"
|
||||
:initial-assignees="taskEditTask.assignees"/>
|
||||
:initial-assignees="taskEditTask.assignees"
|
||||
:list-id="taskEditTask.listId"
|
||||
:task-id="taskEditTask.id"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -132,13 +132,13 @@
|
||||
</div>
|
||||
|
||||
<related-tasks
|
||||
class="is-narrow"
|
||||
:task-id="task.id"
|
||||
:list-id="task.listId"
|
||||
:initial-related-tasks="task.relatedTasks"
|
||||
:initial-related-tasks="task.relatedTasks"
|
||||
:list-id="task.listId"
|
||||
:task-id="task.id"
|
||||
class="is-narrow"
|
||||
/>
|
||||
|
||||
<button type="submit" class="button is-success is-fullwidth" :class="{ 'is-loading': taskService.loading}">
|
||||
<button :class="{ 'is-loading': taskService.loading}" class="button is-success is-fullwidth" type="submit">
|
||||
Save
|
||||
</button>
|
||||
|
||||
@ -146,113 +146,113 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
import ListService from '../../services/list'
|
||||
import TaskService from '../../services/task'
|
||||
import TaskModel from '../../models/task'
|
||||
import priorities from '../../models/priorities'
|
||||
import PrioritySelect from './partials/prioritySelect'
|
||||
import PercentDoneSelect from './partials/percentDoneSelect'
|
||||
import EditLabels from './partials/editLabels'
|
||||
import EditAssignees from './partials/editAssignees'
|
||||
import RelatedTasks from './partials/relatedTasks'
|
||||
import RepeatAfter from './partials/repeatAfter'
|
||||
import Reminders from './partials/reminders'
|
||||
import ColorPicker from '../input/colorPicker'
|
||||
import LoadingComponent from '../misc/loading'
|
||||
import ErrorComponent from '../misc/error'
|
||||
import ListService from '../../services/list'
|
||||
import TaskService from '../../services/task'
|
||||
import TaskModel from '../../models/task'
|
||||
import priorities from '../../models/priorities'
|
||||
import PrioritySelect from './partials/prioritySelect'
|
||||
import PercentDoneSelect from './partials/percentDoneSelect'
|
||||
import EditLabels from './partials/editLabels'
|
||||
import EditAssignees from './partials/editAssignees'
|
||||
import RelatedTasks from './partials/relatedTasks'
|
||||
import RepeatAfter from './partials/repeatAfter'
|
||||
import Reminders from './partials/reminders'
|
||||
import ColorPicker from '../input/colorPicker'
|
||||
import LoadingComponent from '../misc/loading'
|
||||
import ErrorComponent from '../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'edit-task',
|
||||
data() {
|
||||
return {
|
||||
listId: this.$route.params.id,
|
||||
listService: ListService,
|
||||
taskService: TaskService,
|
||||
export default {
|
||||
name: 'edit-task',
|
||||
data() {
|
||||
return {
|
||||
listId: this.$route.params.id,
|
||||
listService: ListService,
|
||||
taskService: TaskService,
|
||||
|
||||
priorities: priorities,
|
||||
list: {},
|
||||
editorActive: false,
|
||||
newTask: TaskModel,
|
||||
isTaskEdit: false,
|
||||
taskEditTask: TaskModel,
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
onOpen: this.updateLastReminderDate,
|
||||
onClose: this.addReminderDate,
|
||||
},
|
||||
}
|
||||
priorities: priorities,
|
||||
list: {},
|
||||
editorActive: false,
|
||||
newTask: TaskModel,
|
||||
isTaskEdit: false,
|
||||
taskEditTask: TaskModel,
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
onOpen: this.updateLastReminderDate,
|
||||
onClose: this.addReminderDate,
|
||||
},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ColorPicker,
|
||||
Reminders,
|
||||
RepeatAfter,
|
||||
RelatedTasks,
|
||||
EditAssignees,
|
||||
EditLabels,
|
||||
PercentDoneSelect,
|
||||
PrioritySelect,
|
||||
flatPickr,
|
||||
editor: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ '../../components/input/editor'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
task: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
},
|
||||
components: {
|
||||
ColorPicker,
|
||||
Reminders,
|
||||
RepeatAfter,
|
||||
RelatedTasks,
|
||||
EditAssignees,
|
||||
EditLabels,
|
||||
PercentDoneSelect,
|
||||
PrioritySelect,
|
||||
flatPickr,
|
||||
editor: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ '../../components/input/editor'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
task: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
task() {
|
||||
this.taskEditTask = this.task
|
||||
this.initTaskFields()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.taskService = new TaskService()
|
||||
this.newTask = new TaskModel()
|
||||
},
|
||||
watch: {
|
||||
task() {
|
||||
this.taskEditTask = this.task
|
||||
this.initTaskFields()
|
||||
},
|
||||
methods: {
|
||||
initTaskFields() {
|
||||
this.taskEditTask.dueDate = +new Date(this.task.dueDate) === 0 ? null : this.task.dueDate
|
||||
this.taskEditTask.startDate = +new Date(this.task.startDate) === 0 ? null : this.task.startDate
|
||||
this.taskEditTask.endDate = +new Date(this.task.endDate) === 0 ? null : this.task.endDate
|
||||
// This makes the editor trigger its mounted function again which makes it forget every input
|
||||
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
|
||||
// which made it impossible to detect change from the outside. Therefore the component would
|
||||
// not update if new content from the outside was made available.
|
||||
// See https://github.com/NikulinIlya/vue-easymde/issues/3
|
||||
this.editorActive = false
|
||||
this.$nextTick(() => this.editorActive = true)
|
||||
},
|
||||
editTaskSubmit() {
|
||||
this.taskService.update(this.taskEditTask)
|
||||
.then(r => {
|
||||
this.$set(this, 'taskEditTask', r)
|
||||
this.initTaskFields()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.listService = new ListService()
|
||||
this.taskService = new TaskService()
|
||||
this.newTask = new TaskModel()
|
||||
this.taskEditTask = this.task
|
||||
this.initTaskFields()
|
||||
},
|
||||
methods: {
|
||||
initTaskFields() {
|
||||
this.taskEditTask.dueDate = +new Date(this.task.dueDate) === 0 ? null : this.task.dueDate
|
||||
this.taskEditTask.startDate = +new Date(this.task.startDate) === 0 ? null : this.task.startDate
|
||||
this.taskEditTask.endDate = +new Date(this.task.endDate) === 0 ? null : this.task.endDate
|
||||
// This makes the editor trigger its mounted function again which makes it forget every input
|
||||
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
|
||||
// which made it impossible to detect change from the outside. Therefore the component would
|
||||
// not update if new content from the outside was made available.
|
||||
// See https://github.com/NikulinIlya/vue-easymde/issues/3
|
||||
this.editorActive = false
|
||||
this.$nextTick(() => this.editorActive = true)
|
||||
},
|
||||
}
|
||||
editTaskSubmit() {
|
||||
this.taskService.update(this.taskEditTask)
|
||||
.then(r => {
|
||||
this.$set(this, 'taskEditTask', r)
|
||||
this.initTaskFields()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
form {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
form {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
@ -2,21 +2,22 @@
|
||||
<div class="gantt-chart box">
|
||||
<div class="dates">
|
||||
<template v-for="(y, yk) in days">
|
||||
<div class="months" :key="yk + 'year'">
|
||||
<div class="month" v-for="(m, mk) in days[yk]" :key="mk + 'month'">
|
||||
{{new Date((new Date(yk)).setMonth(mk)).toLocaleString('en-us', { month: 'long' })}}, {{(new Date(yk)).getFullYear()}}
|
||||
<div :key="yk + 'year'" class="months">
|
||||
<div :key="mk + 'month'" class="month" v-for="(m, mk) in days[yk]">
|
||||
{{ new Date((new Date(yk)).setMonth(mk)).toLocaleString('en-us', {month: 'long'}) }},
|
||||
{{ (new Date(yk)).getFullYear() }}
|
||||
<div class="days">
|
||||
<div
|
||||
class="day"
|
||||
v-for="(d, dk) in days[yk][mk]"
|
||||
:key="dk + 'day'"
|
||||
:style="{'width': dayWidth + 'px'}"
|
||||
:class="{'today': d.toDateString() === now.toDateString()}">
|
||||
:class="{'today': d.toDateString() === now.toDateString()}"
|
||||
:key="dk + 'day'"
|
||||
:style="{'width': dayWidth + 'px'}"
|
||||
class="day"
|
||||
v-for="(d, dk) in days[yk][mk]">
|
||||
<span class="theday" v-if="dayWidth > 25">
|
||||
{{d.getDate()}}
|
||||
{{ d.getDate() }}
|
||||
</span>
|
||||
<span class="weekday" v-if="dayWidth > 25">
|
||||
{{d.toLocaleString('en-us', { weekday: 'short' })}}
|
||||
{{ d.toLocaleString('en-us', {weekday: 'short'}) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -24,38 +25,42 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="tasks" :style="{'width': fullWidth + 'px'}">
|
||||
<div class="row" v-for="(t, k) in theTasks" :key="t.id" :style="{background: 'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' + (k % 2 === 0 ? '#fafafa 1px, #fafafa ' : '#fff 1px, #fff ') + dayWidth + 'px)'}">
|
||||
<div :style="{'width': fullWidth + 'px'}" class="tasks">
|
||||
<div
|
||||
:key="t.id"
|
||||
:style="{background: 'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' + (k % 2 === 0 ? '#fafafa 1px, #fafafa ' : '#fff 1px, #fff ') + dayWidth + 'px)'}"
|
||||
class="row"
|
||||
v-for="(t, k) in theTasks">
|
||||
<VueDragResize
|
||||
class="task"
|
||||
:class="{
|
||||
:class="{
|
||||
'done': t.done,
|
||||
'is-current-edit': taskToEdit !== null && taskToEdit.id === t.id,
|
||||
'has-light-text': !colorIsDark(t.hexColor),
|
||||
'has-dark-text': colorIsDark(t.hexColor)
|
||||
}"
|
||||
:style="{'border-color': t.hexColor, 'background-color': t.hexColor}"
|
||||
:isActive="canWrite"
|
||||
:x="t.offsetDays * dayWidth - 6"
|
||||
:y="0"
|
||||
:w="t.durationDays * dayWidth"
|
||||
:h="31"
|
||||
:minw="dayWidth"
|
||||
:snapToGrid="true"
|
||||
:gridX="dayWidth"
|
||||
:sticks="['mr', 'ml']"
|
||||
axis="x"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
@resizestop="resizeTask"
|
||||
@dragstop="resizeTask"
|
||||
@clicked="setTaskDragged(t)"
|
||||
:gridX="dayWidth"
|
||||
:h="31"
|
||||
:isActive="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
:snapToGrid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:style="{'border-color': t.hexColor, 'background-color': t.hexColor}"
|
||||
:w="t.durationDays * dayWidth"
|
||||
:x="t.offsetDays * dayWidth - 6"
|
||||
:y="0"
|
||||
@clicked="setTaskDragged(t)"
|
||||
@dragstop="resizeTask"
|
||||
@resizestop="resizeTask"
|
||||
axis="x"
|
||||
class="task"
|
||||
>
|
||||
<span :class="{
|
||||
'has-high-priority': t.priority >= priorities.HIGH,
|
||||
'has-not-so-high-priority': t.priority === priorities.HIGH,
|
||||
'has-super-high-priority': t.priority === priorities.DO_NOW
|
||||
}">{{t.title}}</span>
|
||||
}">{{ t.title }}</span>
|
||||
<priority-label :priority="t.priority"/>
|
||||
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
|
||||
<a @click="editTask(theTasks[k])" class="edit-toggle">
|
||||
@ -64,26 +69,30 @@
|
||||
</VueDragResize>
|
||||
</div>
|
||||
<template v-if="showTaskswithoutDates">
|
||||
<div class="row" v-for="(t, k) in tasksWithoutDates" :key="t.id" :style="{background: 'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' + (k % 2 === 0 ? '#fafafa 1px, #fafafa ' : '#fff 1px, #fff ') + dayWidth + 'px)'}">
|
||||
<div
|
||||
:key="t.id"
|
||||
:style="{background: 'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' + (k % 2 === 0 ? '#fafafa 1px, #fafafa ' : '#fff 1px, #fff ') + dayWidth + 'px)'}"
|
||||
class="row"
|
||||
v-for="(t, k) in tasksWithoutDates">
|
||||
<VueDragResize
|
||||
class="task nodate"
|
||||
:isActive="canWrite"
|
||||
:x="dayOffsetUntilToday * dayWidth - 6"
|
||||
:y="0"
|
||||
:h="31"
|
||||
:minw="dayWidth"
|
||||
:snapToGrid="true"
|
||||
:gridX="dayWidth"
|
||||
:sticks="['mr', 'ml']"
|
||||
axis="x"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
@resizestop="resizeTask"
|
||||
@dragstop="resizeTask"
|
||||
@clicked="setTaskDragged(t)"
|
||||
v-tooltip="'This task has no dates set.'"
|
||||
:gridX="dayWidth"
|
||||
:h="31"
|
||||
:isActive="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
:snapToGrid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:x="dayOffsetUntilToday * dayWidth - 6"
|
||||
:y="0"
|
||||
@clicked="setTaskDragged(t)"
|
||||
@dragstop="resizeTask"
|
||||
@resizestop="resizeTask"
|
||||
axis="x"
|
||||
class="task nodate"
|
||||
v-tooltip="'This task has no dates set.'"
|
||||
>
|
||||
<span>{{t.title}}</span>
|
||||
<span>{{ t.title }}</span>
|
||||
</VueDragResize>
|
||||
</div>
|
||||
</template>
|
||||
@ -91,16 +100,16 @@
|
||||
<form @submit.prevent="addNewTask()" class="add-new-task" v-if="canWrite">
|
||||
<transition name="width">
|
||||
<input
|
||||
type="text"
|
||||
v-model="newTaskTitle"
|
||||
class="input"
|
||||
v-if="newTaskFieldActive"
|
||||
ref="newTaskTitleField"
|
||||
@keyup.esc="newTaskFieldActive = false"
|
||||
@blur="hideCrateNewTask"
|
||||
@keyup.esc="newTaskFieldActive = false"
|
||||
class="input"
|
||||
ref="newTaskTitleField"
|
||||
type="text"
|
||||
v-if="newTaskFieldActive"
|
||||
v-model="newTaskTitle"
|
||||
/>
|
||||
</transition>
|
||||
<button class="button is-primary noshadow" @click="showCreateNewTask">
|
||||
<button @click="showCreateNewTask" class="button is-primary noshadow">
|
||||
<span class="icon is-small">
|
||||
<icon icon="plus"/>
|
||||
</span>
|
||||
@ -113,7 +122,7 @@
|
||||
<p class="card-header-title">
|
||||
Edit Task
|
||||
</p>
|
||||
<a class="card-header-icon" @click="() => {isTaskEdit = false; taskToEdit = null}">
|
||||
<a @click="() => {isTaskEdit = false; taskToEdit = null}" class="card-header-icon">
|
||||
<span class="icon">
|
||||
<icon icon="times"/>
|
||||
</span>
|
||||
@ -130,258 +139,258 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueDragResize from 'vue-drag-resize'
|
||||
import EditTask from './edit-task'
|
||||
import VueDragResize from 'vue-drag-resize'
|
||||
import EditTask from './edit-task'
|
||||
|
||||
import TaskService from '../../services/task'
|
||||
import TaskModel from '../../models/task'
|
||||
import priorities from '../../models/priorities'
|
||||
import PriorityLabel from './partials/priorityLabel'
|
||||
import TaskCollectionService from '../../services/taskCollection'
|
||||
import {mapState} from 'vuex'
|
||||
import Rights from '../../models/rights.json'
|
||||
import TaskService from '../../services/task'
|
||||
import TaskModel from '../../models/task'
|
||||
import priorities from '../../models/priorities'
|
||||
import PriorityLabel from './partials/priorityLabel'
|
||||
import TaskCollectionService from '../../services/taskCollection'
|
||||
import {mapState} from 'vuex'
|
||||
import Rights from '../../models/rights.json'
|
||||
|
||||
export default {
|
||||
name: 'GanttChart',
|
||||
components: {
|
||||
PriorityLabel,
|
||||
EditTask,
|
||||
VueDragResize,
|
||||
export default {
|
||||
name: 'GanttChart',
|
||||
components: {
|
||||
PriorityLabel,
|
||||
EditTask,
|
||||
VueDragResize,
|
||||
},
|
||||
props: {
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
showTaskswithoutDates: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dateFrom: {
|
||||
default: new Date((new Date()).setDate((new Date()).getDate() - 15))
|
||||
},
|
||||
dateTo: {
|
||||
default: new Date((new Date()).setDate((new Date()).getDate() + 30))
|
||||
},
|
||||
// The width of a day in pixels, used to calculate all sorts of things.
|
||||
dayWidth: {
|
||||
type: Number,
|
||||
default: 35,
|
||||
}
|
||||
showTaskswithoutDates: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
days: [],
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
theTasks: [], // Pretty much a copy of the prop, since we cant mutate the prop directly
|
||||
tasksWithoutDates: [],
|
||||
taskService: TaskService,
|
||||
taskDragged: null, // Saves to currently dragged task to be able to update it
|
||||
fullWidth: 0,
|
||||
now: null,
|
||||
dayOffsetUntilToday: 0,
|
||||
isTaskEdit: false,
|
||||
taskToEdit: null,
|
||||
newTaskTitle: '',
|
||||
newTaskFieldActive: false,
|
||||
priorities: {},
|
||||
taskCollectionService: TaskCollectionService,
|
||||
}
|
||||
dateFrom: {
|
||||
default: new Date((new Date()).setDate((new Date()).getDate() - 15)),
|
||||
},
|
||||
watch: {
|
||||
'dateFrom': 'buildTheGanttChart',
|
||||
'dateTo': 'buildTheGanttChart',
|
||||
'listId': 'parseTasks',
|
||||
dateTo: {
|
||||
default: new Date((new Date()).setDate((new Date()).getDate() + 30)),
|
||||
},
|
||||
created() {
|
||||
this.now = new Date()
|
||||
this.taskCollectionService = new TaskCollectionService()
|
||||
this.taskService = new TaskService()
|
||||
this.priorities = priorities
|
||||
// The width of a day in pixels, used to calculate all sorts of things.
|
||||
dayWidth: {
|
||||
type: Number,
|
||||
default: 35,
|
||||
},
|
||||
mounted() {
|
||||
this.buildTheGanttChart()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
days: [],
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
theTasks: [], // Pretty much a copy of the prop, since we cant mutate the prop directly
|
||||
tasksWithoutDates: [],
|
||||
taskService: TaskService,
|
||||
taskDragged: null, // Saves to currently dragged task to be able to update it
|
||||
fullWidth: 0,
|
||||
now: null,
|
||||
dayOffsetUntilToday: 0,
|
||||
isTaskEdit: false,
|
||||
taskToEdit: null,
|
||||
newTaskTitle: '',
|
||||
newTaskFieldActive: false,
|
||||
priorities: {},
|
||||
taskCollectionService: TaskCollectionService,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'dateFrom': 'buildTheGanttChart',
|
||||
'dateTo': 'buildTheGanttChart',
|
||||
'listId': 'parseTasks',
|
||||
},
|
||||
created() {
|
||||
this.now = new Date()
|
||||
this.taskCollectionService = new TaskCollectionService()
|
||||
this.taskService = new TaskService()
|
||||
this.priorities = priorities
|
||||
},
|
||||
mounted() {
|
||||
this.buildTheGanttChart()
|
||||
},
|
||||
computed: mapState({
|
||||
canWrite: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
methods: {
|
||||
buildTheGanttChart() {
|
||||
this.setDates()
|
||||
this.prepareGanttDays()
|
||||
this.parseTasks()
|
||||
},
|
||||
computed: mapState({
|
||||
canWrite: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
methods: {
|
||||
buildTheGanttChart() {
|
||||
this.setDates()
|
||||
this.prepareGanttDays()
|
||||
this.parseTasks()
|
||||
},
|
||||
setDates() {
|
||||
this.startDate = new Date(this.dateFrom)
|
||||
this.endDate = new Date(this.dateTo)
|
||||
setDates() {
|
||||
this.startDate = new Date(this.dateFrom)
|
||||
this.endDate = new Date(this.dateTo)
|
||||
|
||||
this.dayOffsetUntilToday = Math.floor((this.now - this.startDate) / 1000 / 60 / 60 / 24) +1
|
||||
},
|
||||
prepareGanttDays() {
|
||||
// Layout: years => [months => [days]]
|
||||
let years = {};
|
||||
for (let d = this.startDate; d <= this.endDate; d.setDate(d.getDate() + 1)) {
|
||||
let date = new Date(d)
|
||||
if (years[date.getFullYear() + ''] === undefined) {
|
||||
years[date.getFullYear() + ''] = {}
|
||||
}
|
||||
if (years[date.getFullYear() + ''][date.getMonth() + ''] === undefined) {
|
||||
years[date.getFullYear() + ''][date.getMonth() + ''] = []
|
||||
}
|
||||
years[date.getFullYear() + ''][date.getMonth() + ''].push(date)
|
||||
this.fullWidth += this.dayWidth
|
||||
this.dayOffsetUntilToday = Math.floor((this.now - this.startDate) / 1000 / 60 / 60 / 24) + 1
|
||||
},
|
||||
prepareGanttDays() {
|
||||
// Layout: years => [months => [days]]
|
||||
let years = {}
|
||||
for (let d = this.startDate; d <= this.endDate; d.setDate(d.getDate() + 1)) {
|
||||
let date = new Date(d)
|
||||
if (years[date.getFullYear() + ''] === undefined) {
|
||||
years[date.getFullYear() + ''] = {}
|
||||
}
|
||||
this.$set(this, 'days', years)
|
||||
},
|
||||
parseTasks() {
|
||||
this.setDates()
|
||||
this.prepareTasks()
|
||||
},
|
||||
prepareTasks() {
|
||||
|
||||
const getAllTasks = (page = 1) => {
|
||||
return this.taskCollectionService.getAll({listId: this.listId}, {}, page)
|
||||
.then(tasks => {
|
||||
if(page < this.taskCollectionService.totalPages) {
|
||||
return getAllTasks(page + 1)
|
||||
.then(nextTasks => {
|
||||
return tasks.concat(nextTasks)
|
||||
})
|
||||
} else {
|
||||
return tasks
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
return Promise.reject(e)
|
||||
})
|
||||
if (years[date.getFullYear() + ''][date.getMonth() + ''] === undefined) {
|
||||
years[date.getFullYear() + ''][date.getMonth() + ''] = []
|
||||
}
|
||||
years[date.getFullYear() + ''][date.getMonth() + ''].push(date)
|
||||
this.fullWidth += this.dayWidth
|
||||
}
|
||||
this.$set(this, 'days', years)
|
||||
},
|
||||
parseTasks() {
|
||||
this.setDates()
|
||||
this.prepareTasks()
|
||||
},
|
||||
prepareTasks() {
|
||||
|
||||
getAllTasks()
|
||||
const getAllTasks = (page = 1) => {
|
||||
return this.taskCollectionService.getAll({listId: this.listId}, {}, page)
|
||||
.then(tasks => {
|
||||
this.theTasks = tasks
|
||||
.filter(t => {
|
||||
if(t.startDate === null && !t.done) {
|
||||
this.tasksWithoutDates.push(t)
|
||||
}
|
||||
return t.startDate >= this.startDate && t.endDate <= this.endDate
|
||||
})
|
||||
.map(t => {
|
||||
return this.addGantAttributes(t)
|
||||
})
|
||||
.sort(function(a,b) {
|
||||
if (a.startDate < b.startDate)
|
||||
return -1
|
||||
if (a.startDate > b.startDate)
|
||||
return 1
|
||||
return 0
|
||||
})
|
||||
if (page < this.taskCollectionService.totalPages) {
|
||||
return getAllTasks(page + 1)
|
||||
.then(nextTasks => {
|
||||
return tasks.concat(nextTasks)
|
||||
})
|
||||
} else {
|
||||
return tasks
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
return Promise.reject(e)
|
||||
})
|
||||
},
|
||||
addGantAttributes(t) {
|
||||
t.endDate === null ? this.endDate : t.endDate
|
||||
t.durationDays = Math.floor((t.endDate - t.startDate) / 1000 / 60 / 60 / 24) + 1
|
||||
t.offsetDays = Math.floor((t.startDate - this.startDate) / 1000 / 60 / 60 / 24) + 1
|
||||
return t
|
||||
},
|
||||
setTaskDragged(t) {
|
||||
this.taskDragged = t
|
||||
},
|
||||
resizeTask(newRect) {
|
||||
}
|
||||
|
||||
// Timeout to definitly catch if the user clicked on taskedit
|
||||
setTimeout(() => {
|
||||
|
||||
if(this.isTaskEdit) {
|
||||
return
|
||||
}
|
||||
|
||||
let didntHaveDates = this.taskDragged.startDate === null ? true : false
|
||||
|
||||
let startDate = new Date(this.startDate)
|
||||
startDate.setDate(startDate.getDate() + newRect.left / this.dayWidth)
|
||||
startDate.setUTCHours(0)
|
||||
startDate.setUTCMinutes(0)
|
||||
startDate.setUTCSeconds(0)
|
||||
startDate.setUTCMilliseconds(0)
|
||||
this.taskDragged.startDate = startDate
|
||||
let endDate = new Date(startDate)
|
||||
endDate.setDate(startDate.getDate() + newRect.width / this.dayWidth)
|
||||
this.taskDragged.startDate = startDate
|
||||
this.taskDragged.endDate = endDate
|
||||
|
||||
|
||||
// We take the task from the overall tasks array because the one in it has bad data after it was updated once.
|
||||
// FIXME: This is a workaround. We should use a better mechanism to get the task or, even better,
|
||||
// prevent it from containing outdated Data in the first place.
|
||||
for (const tt in this.theTasks) {
|
||||
if (this.theTasks[tt].id === this.taskDragged.id) {
|
||||
this.$set(this, 'taskDragged', this.theTasks[tt])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.taskService.update(this.taskDragged)
|
||||
.then(r => {
|
||||
// If the task didn't have dates before, we'll update the list
|
||||
if(didntHaveDates) {
|
||||
for (const t in this.tasksWithoutDates) {
|
||||
if (this.tasksWithoutDates[t].id === r.id) {
|
||||
this.tasksWithoutDates.splice(t, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.theTasks.push(this.addGantAttributes(r))
|
||||
} else {
|
||||
for (const tt in this.theTasks) {
|
||||
if (this.theTasks[tt].id === r.id) {
|
||||
this.$set(this.theTasks, tt, this.addGantAttributes(r))
|
||||
break
|
||||
}
|
||||
}
|
||||
getAllTasks()
|
||||
.then(tasks => {
|
||||
this.theTasks = tasks
|
||||
.filter(t => {
|
||||
if (t.startDate === null && !t.done) {
|
||||
this.tasksWithoutDates.push(t)
|
||||
}
|
||||
return t.startDate >= this.startDate && t.endDate <= this.endDate
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
.map(t => {
|
||||
return this.addGantAttributes(t)
|
||||
})
|
||||
}, 100)
|
||||
},
|
||||
editTask(task) {
|
||||
this.taskToEdit = task
|
||||
this.isTaskEdit = true
|
||||
},
|
||||
showCreateNewTask() {
|
||||
if(!this.newTaskFieldActive) {
|
||||
// Timeout to not send the form if the field isn't even shown
|
||||
setTimeout(() => {
|
||||
this.newTaskFieldActive = true
|
||||
this.$nextTick(() => this.$refs.newTaskTitleField.focus())
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
hideCrateNewTask() {
|
||||
if(this.newTaskTitle === '') {
|
||||
this.$nextTick(() => this.newTaskFieldActive = false)
|
||||
}
|
||||
},
|
||||
addNewTask() {
|
||||
if (!this.newTaskFieldActive) {
|
||||
.sort(function (a, b) {
|
||||
if (a.startDate < b.startDate)
|
||||
return -1
|
||||
if (a.startDate > b.startDate)
|
||||
return 1
|
||||
return 0
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
addGantAttributes(t) {
|
||||
t.endDate === null ? this.endDate : t.endDate
|
||||
t.durationDays = Math.floor((t.endDate - t.startDate) / 1000 / 60 / 60 / 24) + 1
|
||||
t.offsetDays = Math.floor((t.startDate - this.startDate) / 1000 / 60 / 60 / 24) + 1
|
||||
return t
|
||||
},
|
||||
setTaskDragged(t) {
|
||||
this.taskDragged = t
|
||||
},
|
||||
resizeTask(newRect) {
|
||||
|
||||
// Timeout to definitly catch if the user clicked on taskedit
|
||||
setTimeout(() => {
|
||||
|
||||
if (this.isTaskEdit) {
|
||||
return
|
||||
}
|
||||
let task = new TaskModel({title: this.newTaskTitle, listId: this.listId})
|
||||
this.taskService.create(task)
|
||||
|
||||
let didntHaveDates = this.taskDragged.startDate === null ? true : false
|
||||
|
||||
let startDate = new Date(this.startDate)
|
||||
startDate.setDate(startDate.getDate() + newRect.left / this.dayWidth)
|
||||
startDate.setUTCHours(0)
|
||||
startDate.setUTCMinutes(0)
|
||||
startDate.setUTCSeconds(0)
|
||||
startDate.setUTCMilliseconds(0)
|
||||
this.taskDragged.startDate = startDate
|
||||
let endDate = new Date(startDate)
|
||||
endDate.setDate(startDate.getDate() + newRect.width / this.dayWidth)
|
||||
this.taskDragged.startDate = startDate
|
||||
this.taskDragged.endDate = endDate
|
||||
|
||||
|
||||
// We take the task from the overall tasks array because the one in it has bad data after it was updated once.
|
||||
// FIXME: This is a workaround. We should use a better mechanism to get the task or, even better,
|
||||
// prevent it from containing outdated Data in the first place.
|
||||
for (const tt in this.theTasks) {
|
||||
if (this.theTasks[tt].id === this.taskDragged.id) {
|
||||
this.$set(this, 'taskDragged', this.theTasks[tt])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.taskService.update(this.taskDragged)
|
||||
.then(r => {
|
||||
this.tasksWithoutDates.push(this.addGantAttributes(r))
|
||||
this.newTaskTitle = ''
|
||||
this.hideCrateNewTask()
|
||||
// If the task didn't have dates before, we'll update the list
|
||||
if (didntHaveDates) {
|
||||
for (const t in this.tasksWithoutDates) {
|
||||
if (this.tasksWithoutDates[t].id === r.id) {
|
||||
this.tasksWithoutDates.splice(t, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.theTasks.push(this.addGantAttributes(r))
|
||||
} else {
|
||||
for (const tt in this.theTasks) {
|
||||
if (this.theTasks[tt].id === r.id) {
|
||||
this.$set(this.theTasks, tt, this.addGantAttributes(r))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}, 100)
|
||||
},
|
||||
}
|
||||
editTask(task) {
|
||||
this.taskToEdit = task
|
||||
this.isTaskEdit = true
|
||||
},
|
||||
showCreateNewTask() {
|
||||
if (!this.newTaskFieldActive) {
|
||||
// Timeout to not send the form if the field isn't even shown
|
||||
setTimeout(() => {
|
||||
this.newTaskFieldActive = true
|
||||
this.$nextTick(() => this.$refs.newTaskTitleField.focus())
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
hideCrateNewTask() {
|
||||
if (this.newTaskTitle === '') {
|
||||
this.$nextTick(() => this.newTaskFieldActive = false)
|
||||
}
|
||||
},
|
||||
addNewTask() {
|
||||
if (!this.newTaskFieldActive) {
|
||||
return
|
||||
}
|
||||
let task = new TaskModel({title: this.newTaskTitle, listId: this.listId})
|
||||
this.taskService.create(task)
|
||||
.then(r => {
|
||||
this.tasksWithoutDates.push(this.addGantAttributes(r))
|
||||
this.newTaskTitle = ''
|
||||
this.hideCrateNewTask()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -28,5 +28,5 @@ export default {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
@ -137,7 +137,7 @@ export default {
|
||||
|
||||
this.$router.push({
|
||||
name: 'list.list',
|
||||
query: {search: this.searchTerm}
|
||||
query: {search: this.searchTerm},
|
||||
})
|
||||
},
|
||||
hideSearchBar() {
|
||||
@ -154,12 +154,12 @@ export default {
|
||||
return {
|
||||
name: 'list.' + type,
|
||||
params: {
|
||||
type: type
|
||||
type: type,
|
||||
},
|
||||
query: {
|
||||
page: page,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
@ -6,17 +6,30 @@
|
||||
</span>
|
||||
Attachments
|
||||
<a
|
||||
v-if="editEnabled"
|
||||
class="button is-primary is-outlined is-small noshadow"
|
||||
@click="$refs.files.click()"
|
||||
:disabled="attachmentService.loading">
|
||||
:disabled="attachmentService.loading"
|
||||
@click="$refs.files.click()"
|
||||
class="button is-primary is-outlined is-small noshadow"
|
||||
v-if="editEnabled">
|
||||
<span class="icon is-small"><icon icon="cloud-upload-alt"/></span>
|
||||
Upload attachment
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<input type="file" id="files" ref="files" multiple @change="uploadNewAttachment()" :disabled="attachmentService.loading" v-if="editEnabled"/>
|
||||
<progress v-if="attachmentService.uploadProgress > 0" class="progress is-primary" :value="attachmentService.uploadProgress" max="100">{{ attachmentService.uploadProgress }}%</progress>
|
||||
<input
|
||||
:disabled="attachmentService.loading"
|
||||
@change="uploadNewAttachment()"
|
||||
id="files"
|
||||
multiple
|
||||
ref="files"
|
||||
type="file"
|
||||
v-if="editEnabled"/>
|
||||
<progress
|
||||
:value="attachmentService.uploadProgress"
|
||||
class="progress is-primary"
|
||||
max="100"
|
||||
v-if="attachmentService.uploadProgress > 0">
|
||||
{{ attachmentService.uploadProgress }}%
|
||||
</progress>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@ -27,22 +40,30 @@
|
||||
<th>Created By</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<tr class="attachment" v-for="a in attachments" :key="a.id">
|
||||
<tr :key="a.id" class="attachment" v-for="a in attachments">
|
||||
<td>
|
||||
{{ a.file.name }}
|
||||
</td>
|
||||
<td>{{ a.file.getHumanSize() }}</td>
|
||||
<td>{{ a.file.mime }}</td>
|
||||
<td v-tooltip="formatDate(a.created)">{{ formatDateSince(a.created) }}</td>
|
||||
<td><user :user="a.createdBy" :avatar-size="30"/></td>
|
||||
<td>
|
||||
<user :avatar-size="30" :user="a.createdBy"/>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons has-addons">
|
||||
<a class="button is-primary noshadow" @click="downloadAttachment(a)" v-tooltip="'Download this attachment'">
|
||||
<a
|
||||
@click="downloadAttachment(a)"
|
||||
class="button is-primary noshadow"
|
||||
v-tooltip="'Download this attachment'">
|
||||
<span class="icon">
|
||||
<icon icon="cloud-download-alt"/>
|
||||
</span>
|
||||
</a>
|
||||
<a v-if="editEnabled" class="button is-danger noshadow" v-tooltip="'Delete this attachment'" @click="() => {attachmentToDelete = a; showDeleteModal = true}">
|
||||
<a
|
||||
@click="() => {attachmentToDelete = a; showDeleteModal = true}"
|
||||
class="button is-danger noshadow" v-if="editEnabled"
|
||||
v-tooltip="'Delete this attachment'">
|
||||
<span class="icon">
|
||||
<icon icon="trash-alt"/>
|
||||
</span>
|
||||
@ -53,7 +74,7 @@
|
||||
</table>
|
||||
|
||||
<!-- Dropzone -->
|
||||
<div class="dropzone" :class="{ 'hidden': !showDropzone }" v-if="editEnabled">
|
||||
<div :class="{ 'hidden': !showDropzone }" class="dropzone" v-if="editEnabled">
|
||||
<div class="drop-hint">
|
||||
<div class="icon">
|
||||
<icon icon="cloud-upload-alt"/>
|
||||
@ -66,9 +87,9 @@
|
||||
|
||||
<!-- Delete modal -->
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
v-on:submit="deleteAttachment()">
|
||||
@close="showDeleteModal = false"
|
||||
v-if="showDeleteModal"
|
||||
v-on:submit="deleteAttachment()">
|
||||
<span slot="header">Delete attachment</span>
|
||||
<p slot="text">Are you sure you want to delete the attachment {{ attachmentToDelete.file.name }}?<br/>
|
||||
<b>This CANNOT BE UNDONE!</b></p>
|
||||
@ -77,115 +98,115 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AttachmentService from '../../../services/attachment'
|
||||
import AttachmentModel from '../../../models/attachment'
|
||||
import User from '../../misc/user'
|
||||
import {mapState} from 'vuex'
|
||||
import AttachmentService from '../../../services/attachment'
|
||||
import AttachmentModel from '../../../models/attachment'
|
||||
import User from '../../misc/user'
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'attachments',
|
||||
components: {
|
||||
User,
|
||||
export default {
|
||||
name: 'attachments',
|
||||
components: {
|
||||
User,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachmentService: AttachmentService,
|
||||
showDropzone: false,
|
||||
|
||||
showDeleteModal: false,
|
||||
attachmentToDelete: AttachmentModel,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachmentService: AttachmentService,
|
||||
showDropzone: false,
|
||||
initialAttachments: {
|
||||
type: Array,
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.attachmentService = new AttachmentService()
|
||||
},
|
||||
computed: mapState({
|
||||
attachments: state => state.attachments.attachments,
|
||||
}),
|
||||
mounted() {
|
||||
document.addEventListener('dragenter', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = true
|
||||
})
|
||||
|
||||
showDeleteModal: false,
|
||||
attachmentToDelete: AttachmentModel,
|
||||
window.addEventListener('dragleave', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = false
|
||||
})
|
||||
|
||||
document.addEventListener('dragover', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = true
|
||||
})
|
||||
|
||||
document.addEventListener('drop', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
let files = e.dataTransfer.files
|
||||
this.uploadFiles(files)
|
||||
this.showDropzone = false
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
downloadAttachment(attachment) {
|
||||
this.attachmentService.download(attachment)
|
||||
},
|
||||
uploadNewAttachment() {
|
||||
if (this.$refs.files.files.length === 0) {
|
||||
return
|
||||
}
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
initialAttachments: {
|
||||
type: Array,
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.attachmentService = new AttachmentService()
|
||||
},
|
||||
computed: mapState({
|
||||
attachments: state => state.attachments.attachments
|
||||
}),
|
||||
mounted() {
|
||||
document.addEventListener('dragenter', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = true
|
||||
});
|
||||
|
||||
window.addEventListener('dragleave', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = false
|
||||
});
|
||||
|
||||
document.addEventListener('dragover', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.showDropzone = true
|
||||
});
|
||||
|
||||
document.addEventListener('drop', e => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
let files = e.dataTransfer.files
|
||||
this.uploadFiles(files)
|
||||
this.showDropzone = false
|
||||
})
|
||||
this.uploadFiles(this.$refs.files.files)
|
||||
},
|
||||
methods: {
|
||||
downloadAttachment(attachment) {
|
||||
this.attachmentService.download(attachment)
|
||||
},
|
||||
uploadNewAttachment() {
|
||||
if(this.$refs.files.files.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.uploadFiles(this.$refs.files.files)
|
||||
},
|
||||
uploadFiles(files) {
|
||||
const attachmentModel = new AttachmentModel({taskId: this.taskId})
|
||||
this.attachmentService.create(attachmentModel, files)
|
||||
.then(r => {
|
||||
if(r.success !== null) {
|
||||
r.success.forEach(a => {
|
||||
this.$store.commit('attachments/add', a)
|
||||
this.$store.dispatch('tasks/addTaskAttachment', {taskId: this.taskId, attachment: a})
|
||||
})
|
||||
}
|
||||
if(r.errors !== null) {
|
||||
r.errors.forEach(m => {
|
||||
this.error(m)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
deleteAttachment() {
|
||||
this.attachmentService.delete(this.attachmentToDelete)
|
||||
.then(r => {
|
||||
this.$store.commit('attachments/removeById', this.attachmentToDelete.id)
|
||||
this.success(r, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
uploadFiles(files) {
|
||||
const attachmentModel = new AttachmentModel({taskId: this.taskId})
|
||||
this.attachmentService.create(attachmentModel, files)
|
||||
.then(r => {
|
||||
if (r.success !== null) {
|
||||
r.success.forEach(a => {
|
||||
this.$store.commit('attachments/add', a)
|
||||
this.$store.dispatch('tasks/addTaskAttachment', {taskId: this.taskId, attachment: a})
|
||||
})
|
||||
}
|
||||
if (r.errors !== null) {
|
||||
r.errors.forEach(m => {
|
||||
this.error(m)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
deleteAttachment() {
|
||||
this.attachmentService.delete(this.attachmentToDelete)
|
||||
.then(r => {
|
||||
this.$store.commit('attachments/removeById', this.attachmentToDelete.id)
|
||||
this.success(r, this)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="content details" :class="{'has-top-border': canWrite || comments.length > 0}">
|
||||
<div :class="{'has-top-border': canWrite || comments.length > 0}" class="content details">
|
||||
<h1 v-if="canWrite || comments.length > 0">
|
||||
<span class="icon is-grey">
|
||||
<icon :icon="['far', 'comments']"/>
|
||||
@ -10,24 +10,24 @@
|
||||
<progress class="progress is-small is-info" max="100" v-if="taskCommentService.loading">Loading
|
||||
comments...
|
||||
</progress>
|
||||
<div class="media comment" v-for="c in comments" :key="c.id">
|
||||
<div :key="c.id" class="media comment" v-for="c in comments">
|
||||
<figure class="media-left">
|
||||
<img class="image is-avatar" :src="c.author.getAvatarUrl(48)" alt="" width="48" height="48"/>
|
||||
<img :src="c.author.getAvatarUrl(48)" alt="" class="image is-avatar" height="48" width="48"/>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="comment-info" :class="{'is-pulled-up': canWrite}">
|
||||
<div :class="{'is-pulled-up': canWrite}" class="comment-info">
|
||||
<strong>{{ c.author.username }}</strong>
|
||||
<small v-tooltip="formatDate(c.created)">{{ formatDateSince(c.created) }}</small>
|
||||
<small v-if="+new Date(c.created) !== +new Date(c.updated)" v-tooltip="formatDate(c.updated)"> ·
|
||||
edited {{ formatDateSince(c.updated) }}</small>
|
||||
</div>
|
||||
<editor
|
||||
v-model="c.comment"
|
||||
:has-preview="true"
|
||||
@change="() => {toggleEdit(c);editComment()}"
|
||||
:upload-enabled="true"
|
||||
:upload-callback="attachmentUpload"
|
||||
:is-edit-enabled="canWrite"
|
||||
:has-preview="true"
|
||||
:is-edit-enabled="canWrite"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
@change="() => {toggleEdit(c);editComment()}"
|
||||
v-model="c.comment"
|
||||
/>
|
||||
<div class="comment-actions" v-if="canWrite">
|
||||
<a @click="toggleDelete(c.id)">Remove</a>
|
||||
@ -36,25 +36,25 @@
|
||||
</div>
|
||||
<div class="media comment" v-if="canWrite">
|
||||
<figure class="media-left">
|
||||
<img class="image is-avatar" :src="userAvatar" alt="" width="48" height="48"/>
|
||||
<img :src="userAvatar" alt="" class="image is-avatar" height="48" width="48"/>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="form">
|
||||
<div class="field">
|
||||
<editor
|
||||
placeholder="Add your comment..."
|
||||
:class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
|
||||
v-model="newComment.comment"
|
||||
:has-preview="false"
|
||||
:upload-enabled="true"
|
||||
:upload-callback="attachmentUpload"
|
||||
v-if="editorActive"
|
||||
:class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
|
||||
:has-preview="false"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
placeholder="Add your comment..."
|
||||
v-if="editorActive"
|
||||
v-model="newComment.comment"
|
||||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="button is-primary"
|
||||
:class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
|
||||
@click="addComment()" :disabled="newComment.comment === ''">Comment
|
||||
<button :class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
|
||||
:disabled="newComment.comment === ''"
|
||||
@click="addComment()" class="button is-primary">Comment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -62,9 +62,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteComment()">
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteComment()"
|
||||
v-if="showDeleteModal">
|
||||
<span slot="header">Delete this comment</span>
|
||||
<p slot="text">Are you sure you want to delete this comment?
|
||||
<br/>This <b>CANNOT BE UNDONE!</b></p>
|
||||
@ -73,145 +73,145 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskCommentService from '../../../services/taskComment'
|
||||
import TaskCommentModel from '../../../models/taskComment'
|
||||
import attachmentUpload from '../mixins/attachmentUpload'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
import TaskCommentService from '../../../services/taskComment'
|
||||
import TaskCommentModel from '../../../models/taskComment'
|
||||
import attachmentUpload from '../mixins/attachmentUpload'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'comments',
|
||||
components: {
|
||||
editor: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ '../../input/editor'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
export default {
|
||||
name: 'comments',
|
||||
components: {
|
||||
editor: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ '../../input/editor'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
mixins: [
|
||||
attachmentUpload,
|
||||
],
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
mixins: [
|
||||
attachmentUpload,
|
||||
],
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
canWrite: {
|
||||
default: true,
|
||||
},
|
||||
canWrite: {
|
||||
default: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
comments: [],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
comments: [],
|
||||
|
||||
showDeleteModal: false,
|
||||
commentToDelete: TaskCommentModel,
|
||||
showDeleteModal: false,
|
||||
commentToDelete: TaskCommentModel,
|
||||
|
||||
isCommentEdit: false,
|
||||
commentEdit: TaskCommentModel,
|
||||
isCommentEdit: false,
|
||||
commentEdit: TaskCommentModel,
|
||||
|
||||
taskCommentService: TaskCommentService,
|
||||
newComment: TaskCommentModel,
|
||||
editorActive: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskCommentService = new TaskCommentService()
|
||||
this.newComment = new TaskCommentModel({taskId: this.taskId})
|
||||
this.commentEdit = new TaskCommentModel({taskId: this.taskId})
|
||||
this.commentToDelete = new TaskCommentModel({taskId: this.taskId})
|
||||
this.comments = []
|
||||
},
|
||||
mounted() {
|
||||
taskCommentService: TaskCommentService,
|
||||
newComment: TaskCommentModel,
|
||||
editorActive: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskCommentService = new TaskCommentService()
|
||||
this.newComment = new TaskCommentModel({taskId: this.taskId})
|
||||
this.commentEdit = new TaskCommentModel({taskId: this.taskId})
|
||||
this.commentToDelete = new TaskCommentModel({taskId: this.taskId})
|
||||
this.comments = []
|
||||
},
|
||||
mounted() {
|
||||
this.loadComments()
|
||||
},
|
||||
watch: {
|
||||
taskId() {
|
||||
this.loadComments()
|
||||
},
|
||||
watch: {
|
||||
taskId() {
|
||||
this.loadComments()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
userAvatar() {
|
||||
return this.$store.state.auth.info.getAvatarUrl(48)
|
||||
},
|
||||
computed: {
|
||||
userAvatar() {
|
||||
return this.$store.state.auth.info.getAvatarUrl(48)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadComments() {
|
||||
this.taskCommentService.getAll({taskId: this.taskId})
|
||||
.then(r => {
|
||||
this.$set(this, 'comments', r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
loadComments() {
|
||||
this.taskCommentService.getAll({taskId: this.taskId})
|
||||
.then(r => {
|
||||
this.$set(this, 'comments', r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
addComment() {
|
||||
if (this.newComment.comment === '') {
|
||||
return
|
||||
}
|
||||
addComment() {
|
||||
if (this.newComment.comment === '') {
|
||||
return
|
||||
}
|
||||
|
||||
// This makes the editor trigger its mounted function again which makes it forget every input
|
||||
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
|
||||
// which made it impossible to detect change from the outside. Therefore the component would
|
||||
// not update if new content from the outside was made available.
|
||||
// See https://github.com/NikulinIlya/vue-easymde/issues/3
|
||||
this.editorActive = false
|
||||
this.$nextTick(() => this.editorActive = true)
|
||||
// This makes the editor trigger its mounted function again which makes it forget every input
|
||||
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
|
||||
// which made it impossible to detect change from the outside. Therefore the component would
|
||||
// not update if new content from the outside was made available.
|
||||
// See https://github.com/NikulinIlya/vue-easymde/issues/3
|
||||
this.editorActive = false
|
||||
this.$nextTick(() => this.editorActive = true)
|
||||
|
||||
this.taskCommentService.create(this.newComment)
|
||||
.then(r => {
|
||||
this.comments.push(r)
|
||||
this.newComment.comment = ''
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
toggleEdit(comment) {
|
||||
this.isCommentEdit = !this.isCommentEdit
|
||||
this.commentEdit = comment
|
||||
},
|
||||
toggleDelete(commentId) {
|
||||
this.showDeleteModal = !this.showDeleteModal
|
||||
this.commentToDelete.id = commentId
|
||||
},
|
||||
editComment() {
|
||||
if (this.commentEdit.comment === '') {
|
||||
return
|
||||
}
|
||||
this.commentEdit.taskId = this.taskId
|
||||
this.taskCommentService.update(this.commentEdit)
|
||||
.then(r => {
|
||||
for (const c in this.comments) {
|
||||
if (this.comments[c].id === this.commentEdit.id) {
|
||||
this.$set(this.comments, c, r)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.isCommentEdit = false
|
||||
})
|
||||
},
|
||||
deleteComment() {
|
||||
this.taskCommentService.delete(this.commentToDelete)
|
||||
.then(() => {
|
||||
for (const a in this.comments) {
|
||||
if (this.comments[a].id === this.commentToDelete.id) {
|
||||
this.comments.splice(a, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
this.taskCommentService.create(this.newComment)
|
||||
.then(r => {
|
||||
this.comments.push(r)
|
||||
this.newComment.comment = ''
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
toggleEdit(comment) {
|
||||
this.isCommentEdit = !this.isCommentEdit
|
||||
this.commentEdit = comment
|
||||
},
|
||||
toggleDelete(commentId) {
|
||||
this.showDeleteModal = !this.showDeleteModal
|
||||
this.commentToDelete.id = commentId
|
||||
},
|
||||
editComment() {
|
||||
if (this.commentEdit.comment === '') {
|
||||
return
|
||||
}
|
||||
this.commentEdit.taskId = this.taskId
|
||||
this.taskCommentService.update(this.commentEdit)
|
||||
.then(r => {
|
||||
for (const c in this.comments) {
|
||||
if (this.comments[c].id === this.commentEdit.id) {
|
||||
this.$set(this.comments, c, r)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.isCommentEdit = false
|
||||
})
|
||||
},
|
||||
deleteComment() {
|
||||
this.taskCommentService.delete(this.commentToDelete)
|
||||
.then(() => {
|
||||
for (const a in this.comments) {
|
||||
if (this.comments[a].id === this.commentToDelete.id) {
|
||||
this.comments.splice(a, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -5,13 +5,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'date-table-cell',
|
||||
props: {
|
||||
date: {
|
||||
type: Date,
|
||||
default: 0,
|
||||
}
|
||||
export default {
|
||||
name: 'date-table-cell',
|
||||
props: {
|
||||
date: {
|
||||
type: Date,
|
||||
default: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,112 +1,112 @@
|
||||
<template>
|
||||
<div class="defer-task loading-container" :class="{'is-loading': taskService.loading}">
|
||||
<div :class="{'is-loading': taskService.loading}" class="defer-task loading-container">
|
||||
<label class="label">Defer due date</label>
|
||||
<div class="defer-days">
|
||||
<button class="button is-outlined is-primary has-no-shadow" @click="() => deferDays(1)">1 day</button>
|
||||
<button class="button is-outlined is-primary has-no-shadow" @click="() => deferDays(3)">3 days</button>
|
||||
<button class="button is-outlined is-primary has-no-shadow" @click="() => deferDays(7)">1 week</button>
|
||||
<button @click="() => deferDays(1)" class="button is-outlined is-primary has-no-shadow">1 day</button>
|
||||
<button @click="() => deferDays(3)" class="button is-outlined is-primary has-no-shadow">3 days</button>
|
||||
<button @click="() => deferDays(7)" class="button is-outlined is-primary has-no-shadow">1 week</button>
|
||||
</div>
|
||||
<flat-pickr
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
class="input"
|
||||
:disabled="taskService.loading"
|
||||
v-model="dueDate"
|
||||
:config="flatPickerConfig"
|
||||
:class="{ 'disabled': taskService.loading}"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading"
|
||||
class="input"
|
||||
v-model="dueDate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskService from '../../../services/task'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import TaskService from '../../../services/task'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
|
||||
export default {
|
||||
name: 'defer-task',
|
||||
data() {
|
||||
return {
|
||||
taskService: TaskService,
|
||||
task: null,
|
||||
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
|
||||
dueDate: null,
|
||||
lastValue: null,
|
||||
changeInterval: null,
|
||||
export default {
|
||||
name: 'defer-task',
|
||||
data() {
|
||||
return {
|
||||
taskService: TaskService,
|
||||
task: null,
|
||||
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
|
||||
dueDate: null,
|
||||
lastValue: null,
|
||||
changeInterval: null,
|
||||
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
inline: true,
|
||||
},
|
||||
}
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
time_24hr: true,
|
||||
inline: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
flatPickr,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: true,
|
||||
},
|
||||
components: {
|
||||
flatPickr,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.value
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.value
|
||||
this.dueDate = this.task.dueDate
|
||||
this.lastValue = this.dueDate
|
||||
|
||||
// Because we don't really have other ways of handling change since if we let flatpickr
|
||||
// change events trigger updates, it would trigger a flatpickr change event which would trigger
|
||||
// an update which would trigger a change event and so on...
|
||||
// This is either a bug in flatpickr or in the vue component of it.
|
||||
// To work around that, we're only updating if something changed and check each second and when closing the popup.
|
||||
if (this.changeInterval) {
|
||||
clearInterval(this.changeInterval)
|
||||
}
|
||||
|
||||
this.changeInterval = setInterval(this.updateDueDate, 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.changeInterval) {
|
||||
clearInterval(this.changeInterval)
|
||||
}
|
||||
this.updateDueDate()
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.task = newVal
|
||||
this.dueDate = this.task.dueDate
|
||||
this.lastValue = this.dueDate
|
||||
|
||||
// Because we don't really have other ways of handling change since if we let flatpickr
|
||||
// change events trigger updates, it would trigger a flatpickr change event which would trigger
|
||||
// an update which would trigger a change event and so on...
|
||||
// This is either a bug in flatpickr or in the vue component of it.
|
||||
// To work around that, we're only updating if something changed and check each second and when closing the popup.
|
||||
if (this.changeInterval) {
|
||||
clearInterval(this.changeInterval)
|
||||
}
|
||||
|
||||
this.changeInterval = setInterval(this.updateDueDate, 1000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.changeInterval) {
|
||||
clearInterval(this.changeInterval)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deferDays(days) {
|
||||
this.dueDate = new Date(this.dueDate)
|
||||
this.dueDate = this.dueDate.setDate(this.dueDate.getDate() + days)
|
||||
this.updateDueDate()
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.task = newVal
|
||||
this.dueDate = this.task.dueDate
|
||||
this.lastValue = this.dueDate
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deferDays(days) {
|
||||
this.dueDate = new Date(this.dueDate)
|
||||
this.dueDate = this.dueDate.setDate(this.dueDate.getDate() + days)
|
||||
this.updateDueDate()
|
||||
},
|
||||
updateDueDate() {
|
||||
if (!this.dueDate) {
|
||||
return
|
||||
}
|
||||
updateDueDate() {
|
||||
if (!this.dueDate) {
|
||||
return
|
||||
}
|
||||
|
||||
if (+new Date(this.dueDate) === +this.lastValue) {
|
||||
return
|
||||
}
|
||||
if (+new Date(this.dueDate) === +this.lastValue) {
|
||||
return
|
||||
}
|
||||
|
||||
this.task.dueDate = new Date(this.dueDate)
|
||||
this.taskService.update(this.task)
|
||||
.then(r => {
|
||||
this.lastValue = r.dueDate
|
||||
this.task = r
|
||||
this.$emit('input', r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
this.task.dueDate = new Date(this.dueDate)
|
||||
this.taskService.update(this.task)
|
||||
.then(r => {
|
||||
this.lastValue = r.dueDate
|
||||
this.task = r
|
||||
this.$emit('input', r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,137 +1,139 @@
|
||||
<template>
|
||||
<multiselect
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:clear-on-select="true"
|
||||
:options-limit="300"
|
||||
:hide-selected="true"
|
||||
v-model="assignees"
|
||||
:options="foundUsers"
|
||||
:searchable="true"
|
||||
:loading="listUserService.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findUser"
|
||||
@select="addAssignee"
|
||||
placeholder="Type to assign a user..."
|
||||
label="username"
|
||||
track-by="id"
|
||||
select-label="Assign this user"
|
||||
:showNoOptions="false"
|
||||
:disabled="disabled"
|
||||
>
|
||||
:clear-on-select="true"
|
||||
:close-on-select="false"
|
||||
:disabled="disabled"
|
||||
:hide-selected="true"
|
||||
:internal-search="true"
|
||||
:loading="listUserService.loading"
|
||||
:multiple="true"
|
||||
:options="foundUsers"
|
||||
:options-limit="300"
|
||||
:searchable="true"
|
||||
:showNoOptions="false"
|
||||
@search-change="findUser"
|
||||
@select="addAssignee"
|
||||
label="username"
|
||||
placeholder="Type to assign a user..."
|
||||
select-label="Assign this user"
|
||||
track-by="id"
|
||||
v-model="assignees"
|
||||
>
|
||||
<template slot="tag" slot-scope="{ option }">
|
||||
<user :user="option" :show-username="false" :avatar-size="30"/>
|
||||
<user :avatar-size="30" :show-username="false" :user="option"/>
|
||||
<a @click="removeAssignee(option)" class="remove-assignee" v-if="!disabled">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
</template>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div class="multiselect__clear" v-if="newAssignee !== null && newAssignee.id !== 0"
|
||||
@mousedown.prevent.stop="clearAllFoundUsers(props.search)"></div>
|
||||
<div
|
||||
@mousedown.prevent.stop="clearAllFoundUsers(props.search)"
|
||||
class="multiselect__clear"
|
||||
v-if="newAssignee !== null && newAssignee.id !== 0"></div>
|
||||
</template>
|
||||
<span slot="noResult">No user found. Consider changing the search query.</span>
|
||||
</multiselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {differenceWith} from 'lodash'
|
||||
import {differenceWith} from 'lodash'
|
||||
|
||||
import UserModel from '../../../models/user'
|
||||
import ListUserService from '../../../services/listUsers'
|
||||
import TaskAssigneeService from '../../../services/taskAssignee'
|
||||
import User from '../../misc/user'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
import UserModel from '../../../models/user'
|
||||
import ListUserService from '../../../services/listUsers'
|
||||
import TaskAssigneeService from '../../../services/taskAssignee'
|
||||
import User from '../../misc/user'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'editAssignees',
|
||||
components: {
|
||||
User,
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
export default {
|
||||
name: 'editAssignees',
|
||||
components: {
|
||||
User,
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
initialAssignees: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newAssignee: UserModel,
|
||||
listUserService: ListUserService,
|
||||
foundUsers: [],
|
||||
assignees: [],
|
||||
taskAssigneeService: TaskAssigneeService,
|
||||
}
|
||||
initialAssignees: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
created() {
|
||||
this.assignees = this.initialAssignees
|
||||
this.listUserService = new ListUserService()
|
||||
this.newAssignee = new UserModel()
|
||||
this.taskAssigneeService = new TaskAssigneeService()
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
initialAssignees(newVal) {
|
||||
this.assignees = newVal
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newAssignee: UserModel,
|
||||
listUserService: ListUserService,
|
||||
foundUsers: [],
|
||||
assignees: [],
|
||||
taskAssigneeService: TaskAssigneeService,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.assignees = this.initialAssignees
|
||||
this.listUserService = new ListUserService()
|
||||
this.newAssignee = new UserModel()
|
||||
this.taskAssigneeService = new TaskAssigneeService()
|
||||
},
|
||||
watch: {
|
||||
initialAssignees(newVal) {
|
||||
this.assignees = newVal
|
||||
},
|
||||
methods: {
|
||||
addAssignee(user) {
|
||||
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeAssignee(user) {
|
||||
this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
|
||||
.then(() => {
|
||||
// Remove the assignee from the list
|
||||
for (const a in this.assignees) {
|
||||
if (this.assignees[a].id === user.id) {
|
||||
this.assignees.splice(a, 1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAssignee(user) {
|
||||
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeAssignee(user) {
|
||||
this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
|
||||
.then(() => {
|
||||
// Remove the assignee from the list
|
||||
for (const a in this.assignees) {
|
||||
if (this.assignees[a].id === user.id) {
|
||||
this.assignees.splice(a, 1)
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
findUser(query) {
|
||||
if (query === '') {
|
||||
this.clearAllFoundUsers()
|
||||
return
|
||||
}
|
||||
|
||||
this.listUserService.getAll({listId: this.listId}, {s: query})
|
||||
.then(response => {
|
||||
// Filter the results to not include users who are already assigned
|
||||
this.$set(this, 'foundUsers', differenceWith(response, this.assignees, (first, second) => {
|
||||
return first.id === second.id
|
||||
}))
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
clearAllFoundUsers() {
|
||||
this.$set(this, 'foundUsers', [])
|
||||
},
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
findUser(query) {
|
||||
if (query === '') {
|
||||
this.clearAllFoundUsers()
|
||||
return
|
||||
}
|
||||
|
||||
this.listUserService.getAll({listId: this.listId}, {s: query})
|
||||
.then(response => {
|
||||
// Filter the results to not include users who are already assigned
|
||||
this.$set(this, 'foundUsers', differenceWith(response, this.assignees, (first, second) => {
|
||||
return first.id === second.id
|
||||
}))
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
clearAllFoundUsers() {
|
||||
this.$set(this, 'foundUsers', [])
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,159 +1,164 @@
|
||||
<template>
|
||||
<multiselect
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:clear-on-select="true"
|
||||
:options-limit="300"
|
||||
:hide-selected="true"
|
||||
v-model="labels"
|
||||
:options="foundLabels"
|
||||
:searchable="true"
|
||||
:loading="labelService.loading || labelTaskService.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findLabel"
|
||||
@select="addLabel"
|
||||
placeholder="Type to add a new label..."
|
||||
label="title"
|
||||
track-by="id"
|
||||
:taggable="true"
|
||||
:showNoOptions="false"
|
||||
@tag="createAndAddLabel"
|
||||
tag-placeholder="Add this as new label"
|
||||
:disabled="disabled"
|
||||
:clear-on-select="true"
|
||||
:close-on-select="false"
|
||||
:disabled="disabled"
|
||||
:hide-selected="true"
|
||||
:internal-search="true"
|
||||
:loading="labelService.loading || labelTaskService.loading"
|
||||
:multiple="true"
|
||||
:options="foundLabels"
|
||||
:options-limit="300"
|
||||
:searchable="true"
|
||||
:showNoOptions="false"
|
||||
:taggable="true"
|
||||
@search-change="findLabel"
|
||||
@select="addLabel"
|
||||
@tag="createAndAddLabel"
|
||||
label="title"
|
||||
placeholder="Type to add a new label..."
|
||||
tag-placeholder="Add this as new label"
|
||||
track-by="id"
|
||||
v-model="labels"
|
||||
>
|
||||
<template slot="tag" slot-scope="{ option }">
|
||||
<span class="tag"
|
||||
:style="{'background': option.hexColor, 'color': option.textColor}">
|
||||
<span>{{ option.title }}</span>
|
||||
<a class="delete is-small" @click="removeLabel(option)"></a>
|
||||
</span>
|
||||
<template
|
||||
slot="tag"
|
||||
slot-scope="{ option }">
|
||||
<span
|
||||
:style="{'background': option.hexColor, 'color': option.textColor}"
|
||||
class="tag">
|
||||
<span>{{ option.title }}</span>
|
||||
<a @click="removeLabel(option)" class="delete is-small"></a>
|
||||
</span>
|
||||
</template>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div class="multiselect__clear" v-if="labels.length"
|
||||
@mousedown.prevent.stop="clearAllLabels(props.search)"></div>
|
||||
<div
|
||||
@mousedown.prevent.stop="clearAllLabels(props.search)"
|
||||
class="multiselect__clear"
|
||||
v-if="labels.length"></div>
|
||||
</template>
|
||||
</multiselect>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { differenceWith } from 'lodash'
|
||||
import {differenceWith} from 'lodash'
|
||||
|
||||
import LabelService from '../../../services/label'
|
||||
import LabelModel from '../../../models/label'
|
||||
import LabelTaskService from '../../../services/labelTask'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
import LabelService from '../../../services/label'
|
||||
import LabelModel from '../../../models/label'
|
||||
import LabelTaskService from '../../../services/labelTask'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'edit-labels',
|
||||
props: {
|
||||
value: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
export default {
|
||||
name: 'edit-labels',
|
||||
props: {
|
||||
value: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labelService: LabelService,
|
||||
labelTaskService: LabelTaskService,
|
||||
foundLabels: [],
|
||||
labelTimeout: null,
|
||||
labels: [],
|
||||
searchQuery: '',
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labelService: LabelService,
|
||||
labelTaskService: LabelTaskService,
|
||||
foundLabels: [],
|
||||
labelTimeout: null,
|
||||
labels: [],
|
||||
searchQuery: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
value(newLabels) {
|
||||
this.labels = newLabels
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.labelService = new LabelService()
|
||||
this.labelTaskService = new LabelTaskService()
|
||||
this.labels = this.value
|
||||
},
|
||||
methods: {
|
||||
findLabel(query) {
|
||||
this.searchQuery = query
|
||||
if (query === '') {
|
||||
this.clearAllLabels()
|
||||
return
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
value(newLabels) {
|
||||
this.labels = newLabels
|
||||
|
||||
if (this.labelTimeout !== null) {
|
||||
clearTimeout(this.labelTimeout)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.labelService = new LabelService()
|
||||
this.labelTaskService = new LabelTaskService()
|
||||
this.labels = this.value
|
||||
},
|
||||
methods: {
|
||||
findLabel(query) {
|
||||
this.searchQuery = query
|
||||
if (query === '') {
|
||||
this.clearAllLabels()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.labelTimeout !== null) {
|
||||
clearTimeout(this.labelTimeout)
|
||||
}
|
||||
|
||||
// Delay the search 300ms to not send a request on every keystroke
|
||||
this.labelTimeout = setTimeout(() => {
|
||||
this.labelService.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundLabels', differenceWith(response, this.labels, (first, second) => {
|
||||
return first.id === second.id
|
||||
}))
|
||||
this.labelTimeout = null
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
clearAllLabels() {
|
||||
this.$set(this, 'foundLabels', [])
|
||||
},
|
||||
addLabel(label) {
|
||||
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
this.$emit('input', this.labels)
|
||||
// Delay the search 300ms to not send a request on every keystroke
|
||||
this.labelTimeout = setTimeout(() => {
|
||||
this.labelService.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundLabels', differenceWith(response, this.labels, (first, second) => {
|
||||
return first.id === second.id
|
||||
}))
|
||||
this.labelTimeout = null
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeLabel(label) {
|
||||
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
// Remove the label from the list
|
||||
for (const l in this.labels) {
|
||||
if (this.labels[l].id === label.id) {
|
||||
this.labels.splice(l, 1)
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
clearAllLabels() {
|
||||
this.$set(this, 'foundLabels', [])
|
||||
},
|
||||
addLabel(label) {
|
||||
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
this.$emit('input', this.labels)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeLabel(label) {
|
||||
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
|
||||
.then(() => {
|
||||
// Remove the label from the list
|
||||
for (const l in this.labels) {
|
||||
if (this.labels[l].id === label.id) {
|
||||
this.labels.splice(l, 1)
|
||||
}
|
||||
this.$emit('input', this.labels)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
createAndAddLabel(title) {
|
||||
let newLabel = new LabelModel({title: title})
|
||||
this.labelService.create(newLabel)
|
||||
.then(r => {
|
||||
this.addLabel(r)
|
||||
this.labels.push(r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
this.$emit('input', this.labels)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
createAndAddLabel(title) {
|
||||
let newLabel = new LabelModel({title: title})
|
||||
this.labelService.create(newLabel)
|
||||
.then(r => {
|
||||
this.addLabel(r)
|
||||
this.labels.push(r)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -1,24 +1,28 @@
|
||||
<template>
|
||||
<div class="label-wrapper">
|
||||
<span class="tag" v-for="label in labels" :style="{'background': label.hexColor, 'color': label.textColor}" :key="label.id">
|
||||
<span
|
||||
:key="label.id"
|
||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||
class="tag"
|
||||
v-for="label in labels">
|
||||
<span>{{ label.title }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'labels',
|
||||
props: {
|
||||
labels: {
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
export default {
|
||||
name: 'labels',
|
||||
props: {
|
||||
labels: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.label-wrapper {
|
||||
display: inline;
|
||||
}
|
||||
.label-wrapper {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<multiselect
|
||||
v-model="list"
|
||||
:options="foundLists"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:loading="listSerivce.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findLists"
|
||||
@select="select"
|
||||
placeholder="Type to search for a list..."
|
||||
label="title"
|
||||
track-by="id"
|
||||
:showNoOptions="false"
|
||||
class="control is-expanded"
|
||||
v-focus
|
||||
:internal-search="true"
|
||||
:loading="listSerivce.loading"
|
||||
:multiple="false"
|
||||
:options="foundLists"
|
||||
:searchable="true"
|
||||
:showNoOptions="false"
|
||||
@search-change="findLists"
|
||||
@select="select"
|
||||
class="control is-expanded"
|
||||
label="title"
|
||||
placeholder="Type to search for a list..."
|
||||
track-by="id"
|
||||
v-focus
|
||||
v-model="list"
|
||||
>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div class="multiselect__clear" v-if="list !== null && list.id !== 0" @mousedown.prevent.stop="clearAll(props.search)"></div>
|
||||
<div
|
||||
@mousedown.prevent.stop="clearAll(props.search)"
|
||||
class="multiselect__clear"
|
||||
v-if="list !== null && list.id !== 0"></div>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span class="list-namespace-title">{{ namespace(props.option.namespaceId) }} ></span>
|
||||
@ -27,60 +30,60 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '../../../services/list'
|
||||
import ListModel from '../../../models/list'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
import ListService from '../../../services/list'
|
||||
import ListModel from '../../../models/list'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'listSearch',
|
||||
data() {
|
||||
return {
|
||||
listSerivce: ListService,
|
||||
list: ListModel,
|
||||
foundLists: [],
|
||||
export default {
|
||||
name: 'listSearch',
|
||||
data() {
|
||||
return {
|
||||
listSerivce: ListService,
|
||||
list: ListModel,
|
||||
foundLists: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
beforeMount() {
|
||||
this.listSerivce = new ListService()
|
||||
this.list = new ListModel()
|
||||
},
|
||||
methods: {
|
||||
findLists(query) {
|
||||
if (query === '') {
|
||||
this.clearAll()
|
||||
return
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
beforeMount() {
|
||||
this.listSerivce = new ListService()
|
||||
this.list = new ListModel()
|
||||
},
|
||||
methods: {
|
||||
findLists(query) {
|
||||
if (query === '') {
|
||||
this.clearAll()
|
||||
return
|
||||
}
|
||||
|
||||
this.listSerivce.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundLists', response)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
clearAll() {
|
||||
this.$set(this, 'foundLists', [])
|
||||
},
|
||||
select(list) {
|
||||
this.$emit('selected', list)
|
||||
},
|
||||
namespace(namespaceId) {
|
||||
const namespace = this.$store.getters['namespaces/getNamespaceById'](namespaceId)
|
||||
if (namespace !== null) {
|
||||
return namespace.title
|
||||
}
|
||||
return 'Shared Lists'
|
||||
},
|
||||
this.listSerivce.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundLists', response)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
clearAll() {
|
||||
this.$set(this, 'foundLists', [])
|
||||
},
|
||||
select(list) {
|
||||
this.$emit('selected', list)
|
||||
},
|
||||
namespace(namespaceId) {
|
||||
const namespace = this.$store.getters['namespaces/getNamespaceById'](namespaceId)
|
||||
if (namespace !== null) {
|
||||
return namespace.title
|
||||
}
|
||||
return 'Shared Lists'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="select">
|
||||
<select v-model.number="percentDone" @change="updateData" :disabled="disabled">
|
||||
<select :disabled="disabled" @change="updateData" v-model.number="percentDone">
|
||||
<option value="0">0%</option>
|
||||
<option value="0.1">10%</option>
|
||||
<option value="0.2">20%</option>
|
||||
@ -17,36 +17,36 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'percentDoneSelect',
|
||||
data() {
|
||||
return {
|
||||
percentDone: 0,
|
||||
}
|
||||
export default {
|
||||
name: 'percentDoneSelect',
|
||||
data() {
|
||||
return {
|
||||
percentDone: 0,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
value(newVal) {
|
||||
this.percentDone = newVal
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
value(newVal) {
|
||||
this.percentDone = newVal
|
||||
},
|
||||
mounted() {
|
||||
this.percentDone = this.value
|
||||
},
|
||||
mounted() {
|
||||
this.percentDone = this.value
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.percentDone)
|
||||
this.$emit('change')
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.percentDone)
|
||||
this.$emit('change')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<span v-if="showAll || priority >= priorities.HIGH" :class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}">
|
||||
<span
|
||||
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
|
||||
v-if="showAll || priority >= priorities.HIGH">
|
||||
<span class="icon" v-if="priority >= priorities.HIGH">
|
||||
<icon icon="exclamation"/>
|
||||
</span>
|
||||
@ -16,43 +18,43 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import priorites from '../../../models/priorities'
|
||||
import priorites from '../../../models/priorities'
|
||||
|
||||
export default {
|
||||
name: 'priorityLabel',
|
||||
data() {
|
||||
return {
|
||||
priorities: priorites,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
priority: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
showAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
export default {
|
||||
name: 'priorityLabel',
|
||||
data() {
|
||||
return {
|
||||
priorities: priorites,
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
priority: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
showAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../../styles/theme/variables';
|
||||
@import '../../../styles/theme/variables';
|
||||
|
||||
span.high-priority{
|
||||
color: $red;
|
||||
width: auto !important; // To override the width set in tasks
|
||||
span.high-priority {
|
||||
color: $red;
|
||||
width: auto !important; // To override the width set in tasks
|
||||
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
width: auto !important;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
&.not-so-high {
|
||||
color: $orange;
|
||||
}
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
width: auto !important;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
&.not-so-high {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="select">
|
||||
<select v-model="priority" @change="updateData" :disabled="disabled">
|
||||
<select :disabled="disabled" @change="updateData" v-model="priority">
|
||||
<option :value="priorities.UNSET">Unset</option>
|
||||
<option :value="priorities.LOW">Low</option>
|
||||
<option :value="priorities.MEDIUM">Medium</option>
|
||||
@ -12,39 +12,39 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import priorites from '../../../models/priorities'
|
||||
import priorites from '../../../models/priorities'
|
||||
|
||||
export default {
|
||||
name: 'prioritySelect',
|
||||
data() {
|
||||
return {
|
||||
priorities: priorites,
|
||||
priority: 0,
|
||||
}
|
||||
export default {
|
||||
name: 'prioritySelect',
|
||||
data() {
|
||||
return {
|
||||
priorities: priorites,
|
||||
priority: 0,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
value(newVal) {
|
||||
this.priority = newVal
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// Set the priority to the :value every time it changes from the outside
|
||||
value(newVal) {
|
||||
this.priority = newVal
|
||||
},
|
||||
mounted() {
|
||||
this.priority = this.value
|
||||
},
|
||||
mounted() {
|
||||
this.priority = this.value
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.priority)
|
||||
this.$emit('change')
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.priority)
|
||||
this.$emit('change')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -4,26 +4,26 @@
|
||||
<label class="label">New Task Relation</label>
|
||||
<div class="field">
|
||||
<multiselect
|
||||
v-model="newTaskRelationTask"
|
||||
:options="foundTasks"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:loading="taskService.loading"
|
||||
:internal-search="true"
|
||||
@search-change="findTasks"
|
||||
placeholder="Type search for a new task to add as related..."
|
||||
label="title"
|
||||
track-by="id"
|
||||
:taggable="true"
|
||||
:showNoOptions="false"
|
||||
@tag="createAndRelateTask"
|
||||
tag-placeholder="Add this as new related task"
|
||||
:internal-search="true"
|
||||
:loading="taskService.loading"
|
||||
:multiple="false"
|
||||
:options="foundTasks"
|
||||
:searchable="true"
|
||||
:showNoOptions="false"
|
||||
:taggable="true"
|
||||
@search-change="findTasks"
|
||||
@tag="createAndRelateTask"
|
||||
label="title"
|
||||
placeholder="Type search for a new task to add as related..."
|
||||
tag-placeholder="Add this as new related task"
|
||||
track-by="id"
|
||||
v-model="newTaskRelationTask"
|
||||
>
|
||||
<template slot="clear" slot-scope="props">
|
||||
<div
|
||||
class="multiselect__clear"
|
||||
v-if="newTaskRelationTask !== null && newTaskRelationTask.id !== 0"
|
||||
@mousedown.prevent.stop="clearAllFoundTasks(props.search)"></div>
|
||||
@mousedown.prevent.stop="clearAllFoundTasks(props.search)"
|
||||
class="multiselect__clear"
|
||||
v-if="newTaskRelationTask !== null && newTaskRelationTask.id !== 0"></div>
|
||||
</template>
|
||||
<span slot="noResult">No task found. Consider changing the search query.</span>
|
||||
</multiselect>
|
||||
@ -33,53 +33,55 @@
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelationKind">
|
||||
<option value="unset">Select a relation kind</option>
|
||||
<option v-for="(label, rk) in relationKinds" :key="rk" :value="rk">
|
||||
<option :key="rk" :value="rk" v-for="(label, rk) in relationKinds">
|
||||
{{ label[0] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-primary" @click="addTaskRelation()">Add task Relation</a>
|
||||
<a @click="addTaskRelation()" class="button is-primary">Add task Relation</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="related-tasks" v-for="(rts, kind ) in relatedTasks" :key="kind">
|
||||
<div :key="kind" class="related-tasks" v-for="(rts, kind ) in relatedTasks">
|
||||
<template v-if="rts.length > 0">
|
||||
<span class="title">{{ relationKindTitle(kind, rts.length) }}</span>
|
||||
<div class="tasks noborder">
|
||||
<div class="task" v-for="t in rts" :key="t.id">
|
||||
<div :key="t.id" class="task" v-for="t in rts">
|
||||
<router-link :to="{ name: $route.name, params: { id: t.id } }">
|
||||
<span class="tasktext" :class="{ 'done': t.done}">
|
||||
<span :class="{ 'done': t.done}" class="tasktext">
|
||||
<span
|
||||
v-if="t.listId !== listId"
|
||||
class="different-list"
|
||||
v-tooltip="'This task belongs to a different list.'">
|
||||
{{ $store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title }} >
|
||||
class="different-list"
|
||||
v-if="t.listId !== listId"
|
||||
v-tooltip="'This task belongs to a different list.'">
|
||||
{{
|
||||
$store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title
|
||||
}} >
|
||||
</span>
|
||||
{{t.title}}
|
||||
{{ t.title }}
|
||||
</span>
|
||||
</router-link>
|
||||
<a
|
||||
v-if="editEnabled"
|
||||
class="remove"
|
||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: kind, otherTaskId: t.id}}">
|
||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: kind, otherTaskId: t.id}}"
|
||||
class="remove"
|
||||
v-if="editEnabled">
|
||||
<icon icon="trash-alt"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0" class="none">
|
||||
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
|
||||
No task relations yet.
|
||||
</p>
|
||||
|
||||
<!-- Delete modal -->
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="removeTaskRelation()">
|
||||
@close="showDeleteModal = false"
|
||||
@submit="removeTaskRelation()"
|
||||
v-if="showDeleteModal">
|
||||
<span slot="header">Delete Task Relation</span>
|
||||
<p slot="text">Are you sure you want to delete this task relation?<br/>
|
||||
<b>This CANNOT BE UNDONE!</b></p>
|
||||
@ -88,150 +90,150 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskService from '../../../services/task'
|
||||
import TaskModel from '../../../models/task'
|
||||
import TaskRelationService from '../../../services/taskRelation'
|
||||
import relationKinds from '../../../models/relationKinds'
|
||||
import TaskRelationModel from '../../../models/taskRelation'
|
||||
import TaskService from '../../../services/task'
|
||||
import TaskModel from '../../../models/task'
|
||||
import TaskRelationService from '../../../services/taskRelation'
|
||||
import relationKinds from '../../../models/relationKinds'
|
||||
import TaskRelationModel from '../../../models/taskRelation'
|
||||
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
import LoadingComponent from '../../misc/loading'
|
||||
import ErrorComponent from '../../misc/error'
|
||||
|
||||
export default {
|
||||
name: 'relatedTasks',
|
||||
data() {
|
||||
return {
|
||||
relatedTasks: {},
|
||||
taskService: TaskService,
|
||||
foundTasks: [],
|
||||
relationKinds: relationKinds,
|
||||
newTaskRelationTask: TaskModel,
|
||||
newTaskRelationKind: 'related',
|
||||
taskRelationService: TaskRelationService,
|
||||
showDeleteModal: false,
|
||||
relationToDelete: {},
|
||||
export default {
|
||||
name: 'relatedTasks',
|
||||
data() {
|
||||
return {
|
||||
relatedTasks: {},
|
||||
taskService: TaskService,
|
||||
foundTasks: [],
|
||||
relationKinds: relationKinds,
|
||||
newTaskRelationTask: TaskModel,
|
||||
newTaskRelationKind: 'related',
|
||||
taskRelationService: TaskRelationService,
|
||||
showDeleteModal: false,
|
||||
relationToDelete: {},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
initialRelatedTasks: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
showNoRelationsNotice: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
listId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
this.taskRelationService = new TaskRelationService()
|
||||
this.newTaskRelationTask = new TaskModel()
|
||||
},
|
||||
watch: {
|
||||
initialRelatedTasks(newVal) {
|
||||
this.relatedTasks = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.relatedTasks = this.initialRelatedTasks
|
||||
},
|
||||
methods: {
|
||||
findTasks(query) {
|
||||
if (query === '') {
|
||||
this.clearAllFoundTasks()
|
||||
return
|
||||
}
|
||||
},
|
||||
components: {
|
||||
multiselect: () => ({
|
||||
component: import(/* webpackPrefetch: true *//* webpackChunkName: "multiselect" */ 'vue-multiselect'),
|
||||
loading: LoadingComponent,
|
||||
error: ErrorComponent,
|
||||
timeout: 60000,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
initialRelatedTasks: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
showNoRelationsNotice: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
listId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
editEnabled: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.taskService = new TaskService()
|
||||
this.taskRelationService = new TaskRelationService()
|
||||
this.newTaskRelationTask = new TaskModel()
|
||||
},
|
||||
watch: {
|
||||
initialRelatedTasks(newVal) {
|
||||
this.relatedTasks = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.relatedTasks = this.initialRelatedTasks
|
||||
},
|
||||
methods: {
|
||||
findTasks(query) {
|
||||
if (query === '') {
|
||||
this.clearAllFoundTasks()
|
||||
return
|
||||
}
|
||||
|
||||
this.taskService.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundTasks', response)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
clearAllFoundTasks() {
|
||||
this.$set(this, 'foundTasks', [])
|
||||
},
|
||||
addTaskRelation() {
|
||||
let rel = new TaskRelationModel({
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.newTaskRelationTask.id,
|
||||
relationKind: this.newTaskRelationKind,
|
||||
this.taskService.getAll({}, {s: query})
|
||||
.then(response => {
|
||||
this.$set(this, 'foundTasks', response)
|
||||
})
|
||||
this.taskRelationService.create(rel)
|
||||
.then(() => {
|
||||
if (!this.relatedTasks[this.newTaskRelationKind]) {
|
||||
this.$set(this.relatedTasks, this.newTaskRelationKind, [])
|
||||
}
|
||||
this.relatedTasks[this.newTaskRelationKind].push(this.newTaskRelationTask)
|
||||
this.newTaskRelationKind = 'unset'
|
||||
this.newTaskRelationTask = new TaskModel()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeTaskRelation() {
|
||||
let rel = new TaskRelationModel({
|
||||
relationKind: this.relationToDelete.relationKind,
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.relationToDelete.otherTaskId,
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
this.taskRelationService.delete(rel)
|
||||
.then(() => {
|
||||
Object.keys(this.relatedTasks).forEach(relationKind => {
|
||||
for (const t in this.relatedTasks[relationKind]) {
|
||||
if (this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId && relationKind === this.relationToDelete.relationKind) {
|
||||
this.relatedTasks[relationKind].splice(t, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
createAndRelateTask(title) {
|
||||
const newTask = new TaskModel({title: title, listId: this.listId})
|
||||
this.taskService.create(newTask)
|
||||
.then(r => {
|
||||
this.newTaskRelationTask = r
|
||||
this.addTaskRelation()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
relationKindTitle(kind, length) {
|
||||
if (length > 1) {
|
||||
return relationKinds[kind][1]
|
||||
}
|
||||
return relationKinds[kind][0]
|
||||
},
|
||||
},
|
||||
}
|
||||
clearAllFoundTasks() {
|
||||
this.$set(this, 'foundTasks', [])
|
||||
},
|
||||
addTaskRelation() {
|
||||
let rel = new TaskRelationModel({
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.newTaskRelationTask.id,
|
||||
relationKind: this.newTaskRelationKind,
|
||||
})
|
||||
this.taskRelationService.create(rel)
|
||||
.then(() => {
|
||||
if (!this.relatedTasks[this.newTaskRelationKind]) {
|
||||
this.$set(this.relatedTasks, this.newTaskRelationKind, [])
|
||||
}
|
||||
this.relatedTasks[this.newTaskRelationKind].push(this.newTaskRelationTask)
|
||||
this.newTaskRelationKind = 'unset'
|
||||
this.newTaskRelationTask = new TaskModel()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
removeTaskRelation() {
|
||||
let rel = new TaskRelationModel({
|
||||
relationKind: this.relationToDelete.relationKind,
|
||||
taskId: this.taskId,
|
||||
otherTaskId: this.relationToDelete.otherTaskId,
|
||||
})
|
||||
this.taskRelationService.delete(rel)
|
||||
.then(() => {
|
||||
Object.keys(this.relatedTasks).forEach(relationKind => {
|
||||
for (const t in this.relatedTasks[relationKind]) {
|
||||
if (this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId && relationKind === this.relationToDelete.relationKind) {
|
||||
this.relatedTasks[relationKind].splice(t, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDeleteModal = false
|
||||
})
|
||||
},
|
||||
createAndRelateTask(title) {
|
||||
const newTask = new TaskModel({title: title, listId: this.listId})
|
||||
this.taskService.create(newTask)
|
||||
.then(r => {
|
||||
this.newTaskRelationTask = r
|
||||
this.addTaskRelation()
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
relationKindTitle(kind, length) {
|
||||
if (length > 1) {
|
||||
return relationKinds[kind][1]
|
||||
}
|
||||
return relationKinds[kind][0]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,19 +1,21 @@
|
||||
<template>
|
||||
<div class="reminders">
|
||||
<div class="reminder-input"
|
||||
<div
|
||||
:class="{ 'overdue': (r < nowUnix && index !== (reminders.length - 1))}"
|
||||
v-for="(r, index) in reminders" :key="index">
|
||||
:key="index"
|
||||
class="reminder-input"
|
||||
v-for="(r, index) in reminders">
|
||||
<flat-pickr
|
||||
:v-model="reminders"
|
||||
:config="flatPickerConfig"
|
||||
:id="'taskreminderdate' + index"
|
||||
:value="r"
|
||||
:data-index="index"
|
||||
placeholder="Add a new reminder..."
|
||||
:disabled="disabled"
|
||||
:config="flatPickerConfig"
|
||||
:data-index="index"
|
||||
:disabled="disabled"
|
||||
:id="'taskreminderdate' + index"
|
||||
:v-model="reminders"
|
||||
:value="r"
|
||||
placeholder="Add a new reminder..."
|
||||
>
|
||||
</flat-pickr>
|
||||
<a v-if="index !== (reminders.length - 1) && !disabled" @click="removeReminderByIndex(index)">
|
||||
<a @click="removeReminderByIndex(index)" v-if="index !== (reminders.length - 1) && !disabled">
|
||||
<icon icon="times"></icon>
|
||||
</a>
|
||||
</div>
|
||||
@ -21,80 +23,80 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
export default {
|
||||
name: 'reminders',
|
||||
data() {
|
||||
return {
|
||||
reminders: [],
|
||||
lastReminder: 0,
|
||||
nowUnix: new Date(),
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
onOpen: this.updateLastReminderDate,
|
||||
onClose: this.addReminderDate,
|
||||
},
|
||||
export default {
|
||||
name: 'reminders',
|
||||
data() {
|
||||
return {
|
||||
reminders: [],
|
||||
lastReminder: 0,
|
||||
nowUnix: new Date(),
|
||||
flatPickerConfig: {
|
||||
altFormat: 'j M Y H:i',
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d H:i',
|
||||
enableTime: true,
|
||||
onOpen: this.updateLastReminderDate,
|
||||
onClose: this.addReminderDate,
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
flatPickr,
|
||||
},
|
||||
mounted() {
|
||||
this.reminders = this.value
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.reminders = newVal
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.reminders)
|
||||
this.$emit('change')
|
||||
},
|
||||
updateLastReminderDate(selectedDates) {
|
||||
this.lastReminder = +new Date(selectedDates[0])
|
||||
},
|
||||
addReminderDate(selectedDates, dateStr, instance) {
|
||||
let newDate = +new Date(selectedDates[0])
|
||||
|
||||
// Don't update if nothing changed
|
||||
if (newDate === this.lastReminder) {
|
||||
return
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
flatPickr,
|
||||
},
|
||||
mounted() {
|
||||
this.reminders = this.value
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.reminders = newVal
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.$emit('input', this.reminders)
|
||||
this.$emit('change')
|
||||
},
|
||||
updateLastReminderDate(selectedDates) {
|
||||
this.lastReminder = +new Date(selectedDates[0])
|
||||
},
|
||||
addReminderDate(selectedDates, dateStr, instance) {
|
||||
let newDate = +new Date(selectedDates[0])
|
||||
|
||||
// Don't update if nothing changed
|
||||
if (newDate === this.lastReminder) {
|
||||
return
|
||||
}
|
||||
let index = parseInt(instance.input.dataset.index)
|
||||
this.reminders[index] = newDate
|
||||
|
||||
let index = parseInt(instance.input.dataset.index)
|
||||
this.reminders[index] = newDate
|
||||
let lastIndex = this.reminders.length - 1
|
||||
// put a new null at the end if we changed something
|
||||
if (lastIndex === index && !isNaN(newDate)) {
|
||||
this.reminders.push(null)
|
||||
}
|
||||
|
||||
let lastIndex = this.reminders.length - 1
|
||||
// put a new null at the end if we changed something
|
||||
if (lastIndex === index && !isNaN(newDate)) {
|
||||
this.reminders.push(null)
|
||||
}
|
||||
|
||||
this.updateData()
|
||||
},
|
||||
removeReminderByIndex(index) {
|
||||
this.reminders.splice(index, 1)
|
||||
// Reset the last to 0 to have the "add reminder" button
|
||||
this.reminders[this.reminders.length - 1] = null
|
||||
|
||||
this.updateData()
|
||||
},
|
||||
this.updateData()
|
||||
},
|
||||
}
|
||||
removeReminderByIndex(index) {
|
||||
this.reminders.splice(index, 1)
|
||||
// Reset the last to 0 to have the "add reminder" button
|
||||
this.reminders[this.reminders.length - 1] = null
|
||||
|
||||
this.updateData()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -6,15 +6,15 @@
|
||||
<div class="column is-7 field has-addons">
|
||||
<div class="control">
|
||||
<input
|
||||
:disabled="disabled"
|
||||
class="input"
|
||||
placeholder="Specify an amount..."
|
||||
v-model="repeatAfter.amount"
|
||||
@change="updateData"/>
|
||||
:disabled="disabled"
|
||||
@change="updateData"
|
||||
class="input"
|
||||
placeholder="Specify an amount..."
|
||||
v-model="repeatAfter.amount"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="repeatAfter.type" @change="updateData" :disabled="disabled">
|
||||
<select :disabled="disabled" @change="updateData" v-model="repeatAfter.type">
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="weeks">Weeks</option>
|
||||
@ -25,11 +25,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<fancycheckbox
|
||||
:disabled="disabled"
|
||||
class="column"
|
||||
@change="updateData"
|
||||
v-model="task.repeatFromCurrentDate"
|
||||
v-tooltip="'When marking the task as done, all dates will be set relative to the current date rather than the date they had before.'"
|
||||
:disabled="disabled"
|
||||
@change="updateData"
|
||||
class="column"
|
||||
v-model="task.repeatFromCurrentDate"
|
||||
v-tooltip="'When marking the task as done, all dates will be set relative to the current date rather than the date they had before.'"
|
||||
>
|
||||
Repeat from current date
|
||||
</fancycheckbox>
|
||||
@ -37,69 +37,69 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Fancycheckbox from '../../input/fancycheckbox'
|
||||
import Fancycheckbox from '../../input/fancycheckbox'
|
||||
|
||||
export default {
|
||||
name: 'repeatAfter',
|
||||
components: {Fancycheckbox},
|
||||
data() {
|
||||
return {
|
||||
task: {},
|
||||
repeatAfter: {
|
||||
amount: 0,
|
||||
type: '',
|
||||
},
|
||||
export default {
|
||||
name: 'repeatAfter',
|
||||
components: {Fancycheckbox},
|
||||
data() {
|
||||
return {
|
||||
task: {},
|
||||
repeatAfter: {
|
||||
amount: 0,
|
||||
type: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: () => {
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.task = newVal
|
||||
if (typeof newVal.repeatAfter !== 'undefined') {
|
||||
this.repeatAfter = newVal.repeatAfter
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
default: () => {
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.value
|
||||
if (typeof this.value.repeatAfter !== 'undefined') {
|
||||
this.repeatAfter = this.value.repeatAfter
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.task.repeatAfter = this.repeatAfter
|
||||
this.$emit('input', this.task)
|
||||
this.$emit('change')
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.task = newVal
|
||||
if (typeof newVal.repeatAfter !== 'undefined') {
|
||||
this.repeatAfter = newVal.repeatAfter
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.value
|
||||
if (typeof this.value.repeatAfter !== 'undefined') {
|
||||
this.repeatAfter = this.value.repeatAfter
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.task.repeatAfter = this.repeatAfter
|
||||
this.$emit('input', this.task)
|
||||
this.$emit('change')
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
p {
|
||||
padding-top: 6px;
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.field.has-addons {
|
||||
|
||||
margin-bottom: .5rem;
|
||||
|
||||
.control .select select {
|
||||
height: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.field.has-addons {
|
||||
|
||||
margin-bottom: .5rem;
|
||||
|
||||
.control .select select {
|
||||
height: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.columns {
|
||||
align-items: center;
|
||||
}
|
||||
.columns {
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="task loader-container" :class="{'is-loading': taskService.loading}">
|
||||
<fancycheckbox v-model="task.done" @change="markAsDone" :disabled="isArchived || disabled"/>
|
||||
<span class="tasktext" :class="{ 'done': task.done}">
|
||||
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
|
||||
<fancycheckbox :disabled="isArchived || disabled" @change="markAsDone" v-model="task.done"/>
|
||||
<span :class="{ 'done': task.done}" class="tasktext">
|
||||
<router-link :to="{ name: taskDetailRoute, params: { id: task.id } }">
|
||||
<router-link
|
||||
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`"
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list">
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`">
|
||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||
</router-link>
|
||||
|
||||
@ -23,37 +23,37 @@
|
||||
|
||||
<labels :labels="task.labels"/>
|
||||
<user
|
||||
:user="a"
|
||||
:avatar-size="27"
|
||||
:show-username="false"
|
||||
:is-inline="true"
|
||||
v-for="(a, i) in task.assignees"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
v-for="(a, i) in task.assignees"
|
||||
/>
|
||||
<i
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
v-tooltip="formatDate(task.dueDate)"
|
||||
@click.stop="showDefer = !showDefer"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
@click.stop="showDefer = !showDefer"
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
v-tooltip="formatDate(task.dueDate)"
|
||||
>
|
||||
- Due {{formatDateSince(task.dueDate)}}
|
||||
- Due {{ formatDateSince(task.dueDate) }}
|
||||
</i>
|
||||
<transition name="fade">
|
||||
<defer-task v-model="task" v-if="+new Date(task.dueDate) > 0 && showDefer"/>
|
||||
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task"/>
|
||||
</transition>
|
||||
<priority-label :priority="task.priority"/>
|
||||
</span>
|
||||
<router-link
|
||||
v-if="currentList.id !== task.listId && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`"
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list">
|
||||
class="task-list"
|
||||
v-if="currentList.id !== task.listId && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`">
|
||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||
</router-link>
|
||||
<a
|
||||
class="favorite"
|
||||
:class="{'is-favorite': task.isFavorite}"
|
||||
@click="toggleFavorite">
|
||||
@click="toggleFavorite"
|
||||
class="favorite">
|
||||
<icon icon="star" v-if="task.isFavorite"/>
|
||||
<icon :icon="['far', 'star']" v-else/>
|
||||
</a>
|
||||
@ -62,112 +62,115 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TaskModel from '../../../models/task'
|
||||
import PriorityLabel from './priorityLabel'
|
||||
import TaskService from '../../../services/task'
|
||||
import Labels from './labels'
|
||||
import User from '../../misc/user'
|
||||
import Fancycheckbox from '../../input/fancycheckbox'
|
||||
import DeferTask from './defer-task'
|
||||
import TaskModel from '../../../models/task'
|
||||
import PriorityLabel from './priorityLabel'
|
||||
import TaskService from '../../../services/task'
|
||||
import Labels from './labels'
|
||||
import User from '../../misc/user'
|
||||
import Fancycheckbox from '../../input/fancycheckbox'
|
||||
import DeferTask from './defer-task'
|
||||
|
||||
export default {
|
||||
name: 'singleTaskInList',
|
||||
data() {
|
||||
return {
|
||||
taskService: TaskService,
|
||||
task: TaskModel,
|
||||
showDefer: false,
|
||||
}
|
||||
export default {
|
||||
name: 'singleTaskInList',
|
||||
data() {
|
||||
return {
|
||||
taskService: TaskService,
|
||||
task: TaskModel,
|
||||
showDefer: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DeferTask,
|
||||
Fancycheckbox,
|
||||
User,
|
||||
Labels,
|
||||
PriorityLabel,
|
||||
},
|
||||
props: {
|
||||
theTask: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
},
|
||||
components: {
|
||||
DeferTask,
|
||||
Fancycheckbox,
|
||||
User,
|
||||
Labels,
|
||||
PriorityLabel,
|
||||
isArchived: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
props: {
|
||||
theTask: {
|
||||
type: TaskModel,
|
||||
required: true,
|
||||
},
|
||||
isArchived: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
taskDetailRoute: {
|
||||
type: String,
|
||||
default: 'task.list.detail'
|
||||
},
|
||||
showList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
taskDetailRoute: {
|
||||
type: String,
|
||||
default: 'task.list.detail',
|
||||
},
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
this.task = newVal
|
||||
},
|
||||
showList: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.theTask
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
created() {
|
||||
this.task = new TaskModel()
|
||||
this.taskService = new TaskService()
|
||||
},
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
this.task = newVal
|
||||
},
|
||||
computed: {
|
||||
currentList() {
|
||||
return typeof this.$store.state.currentList === 'undefined' ? {id: 0, title: ''} : this.$store.state.currentList
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.task = this.theTask
|
||||
},
|
||||
created() {
|
||||
this.task = new TaskModel()
|
||||
this.taskService = new TaskService()
|
||||
},
|
||||
computed: {
|
||||
currentList() {
|
||||
return typeof this.$store.state.currentList === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
} : this.$store.state.currentList
|
||||
},
|
||||
methods: {
|
||||
markAsDone(checked) {
|
||||
const updateFunc = () => {
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.$emit('taskUpdated', t)
|
||||
this.success(
|
||||
{message: 'The task was successfully ' + (this.task.done ? '' : 'un-') + 'marked as done.'},
|
||||
this,
|
||||
[{
|
||||
title: 'Undo',
|
||||
callback: () => this.markAsDone({
|
||||
target: {
|
||||
checked: !checked
|
||||
}
|
||||
}),
|
||||
}]
|
||||
)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
setTimeout(updateFunc, 300); // Delay it to show the animation when marking a task as done
|
||||
} else {
|
||||
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
}
|
||||
},
|
||||
toggleFavorite() {
|
||||
this.task.isFavorite = !this.task.isFavorite
|
||||
},
|
||||
methods: {
|
||||
markAsDone(checked) {
|
||||
const updateFunc = () => {
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.$emit('taskUpdated', t)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
this.success(
|
||||
{message: 'The task was successfully ' + (this.task.done ? '' : 'un-') + 'marked as done.'},
|
||||
this,
|
||||
[{
|
||||
title: 'Undo',
|
||||
callback: () => this.markAsDone({
|
||||
target: {
|
||||
checked: !checked,
|
||||
},
|
||||
}),
|
||||
}],
|
||||
)
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
setTimeout(updateFunc, 300) // Delay it to show the animation when marking a task as done
|
||||
} else {
|
||||
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||
}
|
||||
},
|
||||
}
|
||||
toggleFavorite() {
|
||||
this.task.isFavorite = !this.task.isFavorite
|
||||
this.taskService.update(this.task)
|
||||
.then(t => {
|
||||
this.task = t
|
||||
this.$emit('taskUpdated', t)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
})
|
||||
.catch(e => {
|
||||
this.error(e, this)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<a @click="click">
|
||||
<icon icon="sort-up" v-if="order === 'asc'"/>
|
||||
<icon icon="sort-up" v-else-if="order === 'desc'" rotation="180"/>
|
||||
<icon icon="sort-up" rotation="180" v-else-if="order === 'desc'"/>
|
||||
<icon icon="sort" v-else/>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'sort',
|
||||
props: {
|
||||
order: {
|
||||
type: String,
|
||||
default: 'none',
|
||||
},
|
||||
export default {
|
||||
name: 'sort',
|
||||
props: {
|
||||
order: {
|
||||
type: String,
|
||||
default: 'none',
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click')
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user