2019-04-29 23:41:39 +02:00
< template >
2019-12-03 19:09:12 +01:00
< div class = "loader-container" : class = "{ 'is-loading': listService.loading || taskCollectionService.loading}" >
2020-01-31 11:05:53 +01:00
< div class = "search" >
< div class = "field has-addons" : class = "{ 'hidden': !showTaskSearch }" >
< div class = "control has-icons-left has-icons-right" >
< input
class = "input"
type = "text"
placeholder = "Search"
v - focus
v - model = "searchTerm"
@ keyup . enter = "searchTasks"
@ blur = "hideSearchBar()" / >
< span class = "icon is-left" >
< icon icon = "search" / >
< / span >
< / div >
< div class = "control" >
< button
class = "button noshadow is-primary"
@ click = "searchTasks"
: class = "{'is-loading': taskCollectionService.loading}"
: disabled = "searchTerm === ''" >
Search
< / button >
< / div >
< / div >
< button class = "button" @ click = "showTaskSearch = !showTaskSearch" v-if = "!showTaskSearch" >
< span class = "icon" >
< icon icon = "search" / >
< / span >
< / button >
< / div >
2020-03-04 20:27:27 +01:00
< div class = "field task-add" >
< div class = "field is-grouped" >
2019-04-29 23:41:39 +02:00
< p class = "control has-icons-left is-expanded" : class = "{ 'is-loading': taskService.loading}" >
2020-03-04 20:27:27 +01:00
< input v -focus class = "input" : class = "{ 'disabled': taskService.loading}" v-model = "newTaskText" type="text" placeholder="Add a new task..." @keyup.enter ="addTask()" / >
2019-04-29 23:41:39 +02:00
< span class = "icon is-small is-left" >
< icon icon = "tasks" / >
< / span >
< / p >
< p class = "control" >
2020-03-04 20:27:27 +01:00
< button class = "button is-success" : disabled = "newTaskText.length < 3" @click ="addTask()" >
< span class = "icon is-small" >
< icon icon = "plus" / >
< / span >
2019-04-29 23:41:39 +02:00
Add
< / button >
< / p >
< / div >
2020-03-04 20:27:27 +01:00
< p class = "help is-danger" v-if = "showError && newTaskText.length < 3" >
Please specify at least three characters .
< / p >
< / div >
2019-04-29 23:41:39 +02:00
< div class = "columns" >
< div class = "column" >
2019-12-03 19:09:12 +01:00
< div class = "tasks" v-if = "tasks && tasks.length > 0" :class="{'short': isTaskEdit}" >
< div class = "task" v-for = "l in tasks" :key="l.id" >
2019-11-24 14:16:24 +01:00
< span >
2019-04-29 23:41:39 +02:00
< div class = "fancycheckbox" >
< input @change ="markAsDone" type = "checkbox" :id = "l.id" :checked = "l.done" style = "display: none;" >
< label :for = "l.id" class = "check" >
< svg width = "18px" height = "18px" viewBox = "0 0 18 18" >
< path d = "M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z" > < / path >
< polyline points = "1 9 7 14 15 4" > < / polyline >
< / svg >
< / label >
< / div >
2019-11-24 14:16:24 +01:00
< router-link : to = "{ name: 'taskDetailView', params: { id: l.id } }" class = "tasktext" : class = "{ 'done': l.done}" >
2019-12-18 19:55:28 +01:00
<!-- Show any parent tasks to make it clear this task is a sub task of something -- >
< span class = "parent-tasks" v-if = "typeof l.related_tasks.parenttask !== 'undefined'" >
< template v-for = "(pt, i) in l.related_tasks.parenttask" >
{ { pt . text } } < template v-if = "(i + 1) < l.related_tasks.parenttask.length" > , & nbsp ; < / template >
< / template >
>
< / span >
2019-04-29 23:41:39 +02:00
{ { l . text } }
< span class = "tag" v-for = "label in l.labels" :style="{'background': label.hex_color, 'color': label.textColor}" :key="label.id" >
< span > { { label . title } } < / span >
< / span >
2020-03-01 21:58:58 +01:00
< img
: src = "a.getAvatarUrl(27)"
: alt = "a.username"
class = "avatar"
width = "27"
height = "27"
v - for = "(a, i) in l.assignees"
: key = "l.id + 'assignee' + a.id + i" / >
2020-01-30 21:49:00 +01:00
< i v-if = "l.dueDate > 0" :class="{'overdue': l.dueDate <= new Date() && !l.done}" v-tooltip="formatDate(l.dueDate)" > - Due {{ formatDateSince ( l.dueDate ) }} < / i >
2019-11-24 14:16:24 +01:00
< priority-label :priority = "l.priority" / >
< / router-link >
< / span >
2019-04-29 23:41:39 +02:00
< div @click ="editTask(l.id)" class = "icon settings" >
2019-10-19 21:41:23 +02:00
< icon icon = "pencil-alt" / >
2019-04-29 23:41:39 +02:00
< / div >
< / div >
< / div >
< / div >
< div class = "column is-4" v-if = "isTaskEdit" >
< div class = "card taskedit" >
2019-11-03 13:44:40 +01:00
< header class = "card-header" >
< p class = "card-header-title" >
Edit Task
< / p >
< a class = "card-header-icon" @ click = "isTaskEdit = false" >
< span class = "icon" >
< icon icon = "angle-right" / >
< / span >
< / a >
< / header >
< div class = "card-content" >
< div class = "content" >
< edit-task :task = "taskEditTask" / >
< / div >
< / div >
2019-04-29 23:41:39 +02:00
< / div >
< / div >
< / div >
2019-12-03 19:09:12 +01:00
< nav class = "pagination is-centered" role = "navigation" aria -label = " pagination " v-if = "taskCollectionService.totalPages > 1" >
2019-12-03 19:14:05 +01:00
< router-link class = "pagination-previous" : to = "{name: 'showList', query: { page: currentPage - 1 }}" tag = "button" : disabled = "currentPage === 1" > Previous < / router-link >
< router-link class = "pagination-next" : to = "{name: 'showList', query: { page: currentPage + 1 }}" tag = "button" : disabled = "currentPage === taskCollectionService.totalPages" > Next page < / router-link >
2019-12-03 19:09:12 +01:00
< ul class = "pagination-list" >
< template v-for = "(p, i) in pages" >
< li :key = "'page'+i" v-if = "p.isEllipsis"><span class="pagination-ellipsis" > & hellip ; < / span > < / li >
< li :key = "'page'+i" v-else >
< router-link : to = "{name: 'showList', query: { page: p.number }}" : class = "{'is-current': p.number === currentPage}" class = "pagination-link" : aria -label = " ' Goto page ' + p.number " > { { p . number } } < / router-link >
< / li >
< / template >
< / ul >
< / nav >
2019-04-29 23:41:39 +02:00
< / div >
< / template >
< script >
import ListService from '../../services/list'
import TaskService from '../../services/task'
import ListModel from '../../models/list'
2019-11-03 13:44:40 +01:00
import EditTask from './edit-task'
import TaskModel from '../../models/task'
2019-11-24 14:16:24 +01:00
import PriorityLabel from './reusable/priorityLabel'
2019-12-03 19:09:12 +01:00
import TaskCollectionService from '../../services/taskCollection'
2019-04-29 23:41:39 +02:00
export default {
data ( ) {
return {
listID : this . $route . params . id ,
listService : ListService ,
taskService : TaskService ,
2019-12-03 19:09:12 +01:00
taskCollectionService : TaskCollectionService ,
pages : [ ] ,
currentPage : 0 ,
2019-11-03 13:44:40 +01:00
list : { } ,
2019-12-03 19:09:12 +01:00
tasks : [ ] ,
2019-11-03 13:44:40 +01:00
isTaskEdit : false ,
2019-04-29 23:41:39 +02:00
taskEditTask : TaskModel ,
newTaskText : '' ,
2020-01-31 11:05:53 +01:00
2020-03-04 20:27:27 +01:00
showError : false ,
2020-01-31 11:05:53 +01:00
showTaskSearch : false ,
searchTerm : '' ,
2019-04-29 23:41:39 +02:00
}
} ,
2019-11-03 13:44:40 +01:00
components : {
2019-11-24 14:16:24 +01:00
PriorityLabel ,
2019-04-29 23:41:39 +02:00
EditTask ,
2019-11-03 13:44:40 +01:00
} ,
2019-04-29 23:41:39 +02:00
props : {
theList : {
type : ListModel ,
required : true ,
}
} ,
watch : {
theList ( ) {
this . list = this . theList
2019-12-03 19:09:12 +01:00
} ,
'$route.query' : 'loadTasksForPage' , // Only listen for query path changes
2019-04-29 23:41:39 +02:00
} ,
created ( ) {
this . listService = new ListService ( )
this . taskService = new TaskService ( )
2019-12-03 19:09:12 +01:00
this . taskCollectionService = new TaskCollectionService ( )
2019-12-17 23:03:55 +01:00
this . initTasks ( 1 )
2019-04-29 23:41:39 +02:00
} ,
methods : {
2019-12-17 23:03:55 +01:00
// This function initializes the tasks page and loads the first page of tasks
initTasks ( page ) {
this . taskEditTask = null
this . isTaskEdit = false
this . loadTasks ( page )
} ,
2019-04-29 23:41:39 +02:00
addTask ( ) {
2020-03-04 20:27:27 +01:00
if ( this . newTaskText . length < 3 ) {
this . showError = true
return
}
this . showError = false
2019-04-29 23:41:39 +02:00
let task = new TaskModel ( { text : this . newTaskText , listID : this . $route . params . id } )
this . taskService . create ( task )
. then ( r => {
2019-12-07 17:35:42 +01:00
this . tasks . push ( r )
this . sortTasks ( )
2019-05-21 19:28:52 +02:00
this . newTaskText = ''
2020-01-30 22:47:08 +01:00
this . success ( { message : 'The task was successfully created.' } , this )
2019-04-29 23:41:39 +02:00
} )
. catch ( e => {
2020-01-30 22:47:08 +01:00
this . error ( e , this )
2019-04-29 23:41:39 +02:00
} )
} ,
2020-01-31 11:05:53 +01:00
loadTasks ( page , search = '' ) {
2019-12-07 17:35:42 +01:00
const params = { sort _by : [ 'done' , 'id' ] , order _by : [ 'asc' , 'desc' ] }
2020-01-31 11:05:53 +01:00
if ( search !== '' ) {
params . s = search
}
2019-12-07 17:35:42 +01:00
this . taskCollectionService . getAll ( { listID : this . $route . params . id } , params , page )
2019-12-03 19:09:12 +01:00
. then ( r => {
this . $set ( this , 'tasks' , r )
this . $set ( this , 'pages' , [ ] )
this . currentPage = page
for ( let i = 0 ; i < this . taskCollectionService . totalPages ; i ++ ) {
// Show ellipsis instead of all pages
if (
i > 0 && // Always at least the first page
( i + 1 ) < this . taskCollectionService . totalPages && // And the last page
(
// And the current with current + 1 and current - 1
( i + 1 ) > this . currentPage + 1 ||
( i + 1 ) < this . currentPage - 1
)
) {
// Only add an ellipsis if the last page isn't already one
if ( this . pages [ i - 1 ] && ! this . pages [ i - 1 ] . isEllipsis ) {
this . pages . push ( {
number : 0 ,
isEllipsis : true ,
} )
}
continue
}
this . pages . push ( {
number : i + 1 ,
isEllipsis : false ,
} )
}
} )
. catch ( e => {
2020-01-30 22:47:08 +01:00
this . error ( e , this )
2019-12-03 19:09:12 +01:00
} )
} ,
loadTasksForPage ( e ) {
2019-12-17 23:03:55 +01:00
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
let page = e . page
if ( typeof e . page === 'undefined' ) {
page = 1
}
this . initTasks ( page )
2019-12-03 19:09:12 +01:00
} ,
2019-04-29 23:41:39 +02:00
markAsDone ( e ) {
let updateFunc = ( ) => {
// We get the task, update the 'done' property and then push it to the api.
2019-12-07 17:35:42 +01:00
let task = this . getTaskByID ( e . target . id )
2019-04-29 23:41:39 +02:00
task . done = e . target . checked
this . taskService . update ( task )
. then ( ( ) => {
2019-12-07 17:35:42 +01:00
this . sortTasks ( )
2020-03-02 21:19:26 +01:00
this . success (
{ message : 'The task was successfully ' + ( task . done ? '' : 'un-' ) + 'marked as done.' } ,
this ,
[ {
title : 'Undo' ,
callback : ( ) => this . markAsDone ( { target : { id : e . target . id , checked : ! e . target . checked } } ) ,
} ]
)
2019-04-29 23:41:39 +02:00
} )
. catch ( e => {
2020-01-30 22:47:08 +01:00
this . error ( e , this )
2019-04-29 23:41:39 +02:00
} )
}
if ( e . target . 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
}
} ,
editTask ( id ) {
// Find the selected task and set it to the current object
2019-12-07 17:35:42 +01:00
let theTask = this . getTaskByID ( id ) // Somehow this does not work if we directly assign this to this.taskEditTask
2019-11-03 13:44:40 +01:00
this . taskEditTask = theTask
2019-04-29 23:41:39 +02:00
this . isTaskEdit = true
} ,
2019-12-07 17:35:42 +01:00
getTaskByID ( id ) {
for ( const t in this . tasks ) {
if ( this . tasks [ t ] . id === parseInt ( id ) ) {
return this . tasks [ t ]
}
}
return { } // FIXME: This should probably throw something to make it clear to the user noting was found
} ,
sortTasks ( ) {
if ( this . tasks === null || this . tasks === [ ] ) {
return
}
return this . tasks . sort ( function ( a , b ) {
if ( a . done < b . done )
return - 1
if ( a . done > b . done )
return 1
if ( a . id > b . id )
return - 1
if ( a . id < b . id )
return 1
return 0
} )
} ,
2020-01-31 11:05:53 +01:00
searchTasks ( ) {
if ( this . searchTerm === '' ) {
return
}
this . loadTasks ( 1 , this . searchTerm )
} ,
hideSearchBar ( ) {
// This is a workaround.
// When clicking on the search button, @blur from the input is fired. If we
// would then directly hide the whole search bar directly, no click event
// from the button gets fired. To prevent this, we wait 200ms until we hide
// everything so the button has a chance of firering the search event.
setTimeout ( ( ) => {
this . showTaskSearch = false
} , 200 )
} ,
2019-04-29 23:41:39 +02:00
}
}
< / script >