2020-03-23 22:24:14 +00:00
< template >
2023-03-28 11:13:29 +00:00
< div : class = "{'is-loading': taskService.loading}" class = "task loader-container" >
2022-11-01 10:43:01 +00:00
< fancycheckbox
: disabled = "(isArchived || disabled) && !canMarkAsDone"
2023-03-07 16:59:12 +00:00
@ update : model - value = "markAsDone"
2022-11-01 10:43:01 +00:00
v - model = "task.done"
/ >
2023-03-04 16:13:31 +00:00
2022-09-15 11:56:14 +00:00
< ColorBubble
2022-11-13 21:04:57 +00:00
v - if = "showProjectColor && projectColor !== '' && currentProject.id !== task.projectId"
: color = "projectColor"
2022-09-15 11:56:14 +00:00
class = "mr-1"
/ >
2023-03-04 16:13:31 +00:00
2023-03-28 11:13:29 +00:00
< router -link
: to = "taskDetailRoute"
2022-11-13 21:04:57 +00:00
: class = "{ 'done': task.done, 'show-project': showProject && project !== null}"
2022-11-01 10:43:01 +00:00
class = "tasktext"
>
2021-01-15 20:04:48 +00:00
< span >
2020-08-01 13:17:10 +00:00
< router -link
2022-11-13 21:04:57 +00:00
v - if = "showProject && project !== null"
: to = "{ name: 'project.list', params: { projectId: task.projectId } }"
class = "task-project"
2022-09-15 11:56:14 +00:00
: class = "{'mr-2': task.hexColor !== ''}"
2022-11-13 21:04:57 +00:00
v - tooltip = "$t('task.detail.belongsToProject', {project: project.title})" >
{ { project . title } }
2020-08-01 13:17:10 +00:00
< / r o u t e r - l i n k >
2020-05-11 14:52:58 +00:00
2022-09-15 11:56:14 +00:00
< ColorBubble
v - if = "task.hexColor !== ''"
2022-11-01 10:43:01 +00:00
: color = "getHexColor(task.hexColor)"
2022-09-15 11:56:14 +00:00
class = "mr-1"
/ >
2022-11-01 10:43:01 +00:00
2021-01-17 10:36:57 +00:00
<!-- Show any parent tasks to make it clear this task is a sub task of something -- >
2020-08-01 13:17:10 +00:00
< span class = "parent-tasks" v-if ="typeof task.relatedTasks.parenttask !== 'undefined'" >
< template v-for ="(pt, i) in task.relatedTasks.parenttask" >
{ { pt . title } } < template v-if ="(i + 1) < task.relatedTasks.parenttask.length" > , & nbsp ; < / template >
< / template >
2022-12-15 21:37:02 +00:00
& rsaquo ;
2020-08-01 13:17:10 +00:00
< / span >
{ { task . title } }
2021-01-15 20:04:48 +00:00
< / span >
2020-05-11 14:52:58 +00:00
2022-11-01 10:43:01 +00:00
< labels
v - if = "task.labels.length > 0"
class = "labels ml-2 mr-1"
: labels = "task.labels"
/ >
< User
v - for = "(a, i) in task.assignees"
2020-09-05 20:35:52 +00:00
: avatar - size = "27"
: is - inline = "true"
: key = "task.id + 'assignee' + a.id + i"
: show - username = "false"
: user = "a"
2020-04-01 20:13:57 +00:00
/ >
2022-11-01 10:43:01 +00:00
<!-- FIXME : use popup -- >
2022-05-10 23:14:38 +00:00
< BaseButton
2020-09-05 20:35:52 +00:00
v - if = "+new Date(task.dueDate) > 0"
2022-05-10 23:15:08 +00:00
class = "dueDate"
2022-05-10 23:14:38 +00:00
@ click . prevent . stop = "showDefer = !showDefer"
2022-06-23 00:58:00 +00:00
v - tooltip = "formatDateLong(task.dueDate)"
2020-08-01 13:17:10 +00:00
>
2022-05-10 23:14:38 +00:00
< time
: datetime = "formatISO(task.dueDate)"
: class = "{'overdue': task.dueDate <= new Date() && !task.done}"
class = "is-italic"
: aria - expanded = "showDefer ? 'true' : 'false'"
>
2022-12-15 21:37:02 +00:00
– { { $t ( 'task.detail.due' , { at : formatDateSince ( task . dueDate ) } ) } }
2022-05-10 23:14:38 +00:00
< / time >
< / BaseButton >
2022-11-12 18:24:02 +00:00
< CustomTransition name = "fade" >
2021-01-17 10:36:57 +00:00
< defer -task v -if = " + new Date ( task.dueDate ) > 0 && showDefer " v-model=" task " ref=" deferDueDate " / >
2022-11-12 18:24:02 +00:00
< / CustomTransition >
2022-11-01 10:43:01 +00:00
2021-10-05 05:43:10 +00:00
< priority -label :priority ="task.priority" :done ="task.done" / >
2022-11-01 10:43:01 +00:00
2020-12-30 21:20:33 +00:00
< span >
2022-11-13 21:04:57 +00:00
< span class = "project-task-icon" v-if ="task.attachments.length > 0" >
2020-12-30 21:20:33 +00:00
< icon icon = "paperclip" / >
< / span >
2022-11-13 21:04:57 +00:00
< span class = "project-task-icon" v-if ="task.description" >
2020-12-30 21:20:33 +00:00
< icon icon = "align-left" / >
< / span >
2022-11-13 21:04:57 +00:00
< span class = "project-task-icon" v-if ="task.repeatAfter.amount > 0" >
2021-11-01 16:06:03 +00:00
< icon icon = "history" / >
< / span >
2020-12-30 21:20:33 +00:00
< / span >
2022-11-01 10:43:01 +00:00
2021-09-29 18:31:14 +00:00
< checklist -summary :task ="task" / >
2023-03-28 11:13:29 +00:00
< / r o u t e r - l i n k >
2022-11-01 10:43:01 +00:00
2020-12-31 15:16:07 +00:00
< progress
class = "progress is-small"
v - if = "task.percentDone > 0"
2022-11-01 10:43:01 +00:00
: value = "task.percentDone * 100" max = "100"
>
2020-12-31 15:16:07 +00:00
{ { task . percentDone * 100 } } %
< / progress >
2022-11-01 10:43:01 +00:00
2020-09-05 20:16:17 +00:00
< router -link
2022-11-13 21:04:57 +00:00
v - if = "!showProject && currentProject.id !== task.projectId && project !== null"
: to = "{ name: 'project.list', params: { projectId: task.projectId } }"
class = "task-project"
v - tooltip = "$t('task.detail.belongsToProject', {project: project.title})"
2022-11-01 10:43:01 +00:00
>
2022-11-13 21:04:57 +00:00
{ { project . title } }
2020-09-05 20:16:17 +00:00
< / r o u t e r - l i n k >
2022-11-01 10:43:01 +00:00
2022-05-10 23:14:38 +00:00
< BaseButton
2020-09-05 20:16:17 +00:00
: class = "{'is-favorite': task.isFavorite}"
2023-03-28 11:13:29 +00:00
@ click = "toggleFavorite"
2022-11-01 10:43:01 +00:00
class = "favorite"
>
2020-09-05 20:16:17 +00:00
< icon icon = "star" v -if = " task.isFavorite " / >
< icon : icon = "['far', 'star']" v -else / >
2022-05-10 23:14:38 +00:00
< / BaseButton >
2022-11-01 10:43:01 +00:00
< slot / >
2023-03-28 11:13:29 +00:00
< / div >
2020-03-23 22:24:14 +00:00
< / template >
2022-11-01 10:43:01 +00:00
< script setup lang = "ts" >
import { ref , watch , shallowReactive , toRef , type PropType , onMounted , onBeforeUnmount , computed } from 'vue'
import { useI18n } from 'vue-i18n'
2022-02-15 12:07:59 +00:00
2022-11-01 10:43:01 +00:00
import TaskModel , { getHexColor } from '@/models/task'
2022-09-06 09:36:01 +00:00
import type { ITask } from '@/modelTypes/ITask'
2022-11-01 10:43:01 +00:00
import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
import Labels from '@/components/tasks/partials//labels.vue'
import DeferTask from '@/components/tasks/partials//defer-task.vue'
import ChecklistSummary from '@/components/tasks/partials/checklist-summary.vue'
2022-07-30 15:51:09 +00:00
import User from '@/components/misc/user.vue'
2022-05-10 23:14:38 +00:00
import BaseButton from '@/components/base/BaseButton.vue'
2022-11-01 10:43:01 +00:00
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import ColorBubble from '@/components/misc/colorBubble.vue'
2022-11-12 18:24:02 +00:00
import CustomTransition from '@/components/misc/CustomTransition.vue'
2022-11-01 10:43:01 +00:00
import TaskService from '@/services/task'
2021-01-17 10:36:57 +00:00
import { closeWhenClickedOutside } from '@/helpers/closeWhenClickedOutside'
2022-06-23 00:58:00 +00:00
import { formatDateSince , formatISO , formatDateLong } from '@/helpers/time/formatDate'
2022-11-01 10:43:01 +00:00
import { success } from '@/message'
2022-11-13 21:04:57 +00:00
import { useProjectStore } from '@/stores/projects'
2022-09-24 13:20:40 +00:00
import { useBaseStore } from '@/stores/base'
2022-09-23 10:55:53 +00:00
import { useTaskStore } from '@/stores/tasks'
2020-03-23 22:24:14 +00:00
2022-11-01 10:43:01 +00:00
const props = defineProps ( {
theTask : {
type : Object as PropType < ITask > ,
required : true ,
2020-09-05 20:35:52 +00:00
} ,
2022-11-01 10:43:01 +00:00
isArchived : {
type : Boolean ,
default : false ,
2020-09-05 20:35:52 +00:00
} ,
2022-11-13 21:04:57 +00:00
showProject : {
2022-11-01 10:43:01 +00:00
type : Boolean ,
default : false ,
2020-09-05 20:35:52 +00:00
} ,
2022-11-01 10:43:01 +00:00
disabled : {
type : Boolean ,
default : false ,
2020-09-05 20:35:52 +00:00
} ,
2022-11-13 21:04:57 +00:00
showProjectColor : {
2022-11-01 10:43:01 +00:00
type : Boolean ,
default : true ,
2021-01-17 10:36:57 +00:00
} ,
2022-11-01 10:43:01 +00:00
canMarkAsDone : {
type : Boolean ,
default : true ,
2020-09-05 20:35:52 +00:00
} ,
2022-11-01 10:43:01 +00:00
} )
const emit = defineEmits ( [ 'task-updated' ] )
const { t } = useI18n ( { useScope : 'global' } )
const taskService = shallowReactive ( new TaskService ( ) )
const task = ref < ITask > ( new TaskModel ( ) )
const showDefer = ref ( false )
const theTask = toRef ( props , 'theTask' )
watch (
theTask ,
newVal => {
task . value = newVal
2020-09-05 20:35:52 +00:00
} ,
2022-11-01 10:43:01 +00:00
)
onMounted ( ( ) => {
task . value = theTask . value
document . addEventListener ( 'click' , hideDeferDueDatePopup )
} )
onBeforeUnmount ( ( ) => {
document . removeEventListener ( 'click' , hideDeferDueDatePopup )
} )
const baseStore = useBaseStore ( )
2022-11-13 21:04:57 +00:00
const projectStore = useProjectStore ( )
2022-11-01 10:43:01 +00:00
const taskStore = useTaskStore ( )
2022-11-13 21:04:57 +00:00
const project = computed ( ( ) => projectStore . getProjectById ( task . value . projectId ) )
const projectColor = computed ( ( ) => project . value !== null ? project . value . hexColor : '' )
2022-11-01 10:43:01 +00:00
2022-11-13 21:04:57 +00:00
const currentProject = computed ( ( ) => {
return typeof baseStore . currentProject === 'undefined' ? {
2022-11-01 10:43:01 +00:00
id : 0 ,
title : '' ,
2022-11-13 21:04:57 +00:00
} : baseStore . currentProject
2022-02-15 12:07:59 +00:00
} )
2022-11-01 10:43:01 +00:00
const taskDetailRoute = computed ( ( ) => ( {
name : 'task.detail' ,
params : { id : task . value . id } ,
// TODO: re-enable opening task detail in modal
// state: { backdropView: router.currentRoute.value.fullPath },
} ) )
async function markAsDone ( checked : boolean ) {
const updateFunc = async ( ) => {
const newTask = await taskStore . update ( task . value )
task . value = newTask
emit ( 'task-updated' , newTask )
success ( {
message : task . value . done ?
t ( 'task.doneSuccess' ) :
t ( 'task.undoneSuccess' ) ,
} , [ {
2023-03-08 09:43:46 +00:00
title : t ( 'task.undo' ) ,
2022-11-01 10:43:01 +00:00
callback : ( ) => undoDone ( checked ) ,
} ] )
}
if ( checked ) {
setTimeout ( updateFunc , 300 ) // Delay it to show the animation when marking a task as done
} else {
await updateFunc ( ) // Don't delay it when un-marking it as it doesn't have an animation the other way around
}
}
function undoDone ( checked : boolean ) {
task . value . done = ! task . value . done
markAsDone ( ! checked )
}
async function toggleFavorite ( ) {
task . value . isFavorite = ! task . value . isFavorite
task . value = await taskService . update ( task . value )
2023-03-28 12:03:41 +00:00
await projectStore . loadProjects ( ) // reloading the projects list so that the Favorites project shows up or is hidden when there are (or are not) favorite tasks
2022-11-01 10:43:01 +00:00
emit ( 'task-updated' , task . value )
}
const deferDueDate = ref < typeof DeferTask | null > ( null )
function hideDeferDueDatePopup ( e ) {
if ( ! showDefer . value ) {
return
}
closeWhenClickedOutside ( e , deferDueDate . value . $el , ( ) => {
showDefer . value = false
} )
}
2020-03-23 22:24:14 +00:00
< / script >
2021-10-18 12:22:47 +00:00
< style lang = "scss" scoped >
. task {
display : flex ;
flex - wrap : wrap ;
padding : .4 rem ;
transition : background - color $transition ;
align - items : center ;
cursor : pointer ;
border - radius : $radius ;
border : 2 px solid transparent ;
2023-03-04 16:13:31 +00:00
2021-10-18 12:22:47 +00:00
& : hover {
2021-11-22 21:12:54 +00:00
background - color : var ( -- grey - 100 ) ;
2021-10-18 12:22:47 +00:00
}
. tasktext ,
& . tasktext {
white - space : nowrap ;
text - overflow : ellipsis ;
overflow : hidden ;
display : inline - block ;
flex : 1 0 50 % ;
2022-05-10 23:15:08 +00:00
. dueDate {
display : inline - block ;
margin - left : 5 px ;
}
2021-10-18 12:22:47 +00:00
. overdue {
2021-11-22 21:12:54 +00:00
color : var ( -- danger ) ;
2021-10-18 12:22:47 +00:00
}
}
2022-11-13 21:04:57 +00:00
. task - project {
2021-10-18 12:22:47 +00:00
width : auto ;
2021-11-22 21:12:54 +00:00
color : var ( -- grey - 400 ) ;
2021-10-18 12:22:47 +00:00
font - size : .9 rem ;
white - space : nowrap ;
}
. avatar {
border - radius : 50 % ;
vertical - align : bottom ;
margin - left : 5 px ;
height : 27 px ;
width : 27 px ;
}
2022-11-13 21:04:57 +00:00
. project - task - icon {
2021-10-18 12:22:47 +00:00
margin - left : 6 px ;
& : not ( : first - of - type ) {
margin - left : 8 px ;
}
}
2023-03-28 11:13:29 +00:00
a {
color : var ( -- text ) ;
transition : color ease $transition - duration ;
& : hover {
color : var ( -- grey - 900 ) ;
}
}
2021-10-18 12:22:47 +00:00
. favorite {
2023-03-04 16:13:31 +00:00
opacity : 1 ;
2021-10-18 12:22:47 +00:00
text - align : center ;
width : 27 px ;
transition : opacity $transition , color $transition ;
& : hover {
2021-11-22 21:12:54 +00:00
color : var ( -- warning ) ;
2021-10-18 12:22:47 +00:00
}
& . is - favorite {
opacity : 1 ;
2021-11-22 21:12:54 +00:00
color : var ( -- warning ) ;
2021-10-18 12:22:47 +00:00
}
}
. handle {
2023-03-04 16:13:31 +00:00
opacity : 1 ;
2021-10-18 12:22:47 +00:00
transition : opacity $transition ;
margin - right : .25 rem ;
cursor : grab ;
}
2023-03-04 16:13:31 +00:00
@ media ( hover : hover ) and ( pointer : fine ) {
& . favorite ,
& . handle {
opacity : 0 ;
}
& : hover . favorite ,
& : hover . handle {
opacity : 1 ;
}
2021-10-18 12:22:47 +00:00
}
2023-03-04 16:13:31 +00:00
2021-10-20 12:33:36 +00:00
: deep ( . fancycheckbox ) {
2021-10-18 12:22:47 +00:00
height : 18 px ;
padding - top : 0 ;
padding - right : .5 rem ;
span {
display : none ;
}
}
. tasktext . done {
text - decoration : line - through ;
2021-11-22 21:12:54 +00:00
color : var ( -- grey - 500 ) ;
2021-10-18 12:22:47 +00:00
}
span . parent - tasks {
2021-11-22 21:12:54 +00:00
color : var ( -- grey - 500 ) ;
2021-10-18 12:22:47 +00:00
width : auto ;
}
2022-11-13 21:04:57 +00:00
. show - project . parent - tasks {
2022-12-02 17:05:48 +00:00
padding - left : .25 rem ;
}
2021-10-18 12:22:47 +00:00
. remove {
2021-11-22 21:12:54 +00:00
color : var ( -- danger ) ;
2021-10-18 12:22:47 +00:00
}
input [ type = "checkbox" ] {
vertical - align : middle ;
}
. settings {
float : right ;
width : 24 px ;
cursor : pointer ;
}
& . loader - container . is - loading : after {
top : calc ( 50 % - 1 rem ) ;
left : calc ( 50 % - 1 rem ) ;
width : 2 rem ;
height : 2 rem ;
2021-11-22 21:12:54 +00:00
border - left - color : var ( -- grey - 300 ) ;
border - bottom - color : var ( -- grey - 300 ) ;
2021-10-18 12:22:47 +00:00
}
2022-04-24 15:30:36 +00:00
. progress {
margin - bottom : 0 ;
}
2021-10-18 12:22:47 +00:00
}
2023-03-04 16:13:31 +00:00
< / style >