feat(quick actions): show labels as labels and tasks with all of their details
This commit is contained in:
parent
99d8fbdfa7
commit
d57e1909c4
@ -44,10 +44,18 @@
|
|||||||
@keyup.prevent.enter="doAction(r.type, i)"
|
@keyup.prevent.enter="doAction(r.type, i)"
|
||||||
@keyup.prevent.esc="searchInput?.focus()"
|
@keyup.prevent.esc="searchInput?.focus()"
|
||||||
>
|
>
|
||||||
<span v-if="i.identifier" class="has-text-grey-light">
|
<template v-if="r.type === ACTION_TYPE.LABELS">
|
||||||
{{ i.identifier }}
|
<x-label :label="i"/>
|
||||||
</span>
|
</template>
|
||||||
{{ i.title }}
|
<template v-else-if="r.type === ACTION_TYPE.TASK">
|
||||||
|
<single-task-inline-readonly
|
||||||
|
:task="i"
|
||||||
|
:show-project="true"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ i.title }}
|
||||||
|
</template>
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,6 +77,8 @@ import ProjectModel from '@/models/project'
|
|||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||||
|
import XLabel from '@/components/tasks/partials/label.vue'
|
||||||
|
import SingleTaskInlineReadonly from '@/components/tasks/partials/singleTaskInlineReadonly.vue'
|
||||||
|
|
||||||
import {useBaseStore} from '@/stores/base'
|
import {useBaseStore} from '@/stores/base'
|
||||||
import {useProjectStore} from '@/stores/projects'
|
import {useProjectStore} from '@/stores/projects'
|
||||||
@ -83,7 +93,6 @@ import {success} from '@/message'
|
|||||||
import type {ITeam} from '@/modelTypes/ITeam'
|
import type {ITeam} from '@/modelTypes/ITeam'
|
||||||
import type {ITask} from '@/modelTypes/ITask'
|
import type {ITask} from '@/modelTypes/ITask'
|
||||||
import type {IProject} from '@/modelTypes/IProject'
|
import type {IProject} from '@/modelTypes/IProject'
|
||||||
import type {ILabel} from '@/modelTypes/ILabel'
|
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -393,10 +402,6 @@ function searchTasks() {
|
|||||||
const r = await taskService.getAll({}, params) as DoAction<ITask>[]
|
const r = await taskService.getAll({}, params) as DoAction<ITask>[]
|
||||||
foundTasks.value = r.map((t) => {
|
foundTasks.value = r.map((t) => {
|
||||||
t.type = ACTION_TYPE.TASK
|
t.type = ACTION_TYPE.TASK
|
||||||
const project = projectStore.projects[t.projectId]
|
|
||||||
if (project !== null) {
|
|
||||||
t.title = `${t.title} (${project.title})`
|
|
||||||
}
|
|
||||||
return t
|
return t
|
||||||
})
|
})
|
||||||
}, 150)
|
}, 150)
|
||||||
@ -468,7 +473,7 @@ async function doAction(type: ACTION_TYPE, item: DoAction) {
|
|||||||
searchInput.value?.focus()
|
searchInput.value?.focus()
|
||||||
break
|
break
|
||||||
case ACTION_TYPE.LABELS:
|
case ACTION_TYPE.LABELS:
|
||||||
query.value = '*'+item.title
|
query.value = '*' + item.title
|
||||||
searchInput.value?.focus()
|
searchInput.value?.focus()
|
||||||
searchTasks()
|
searchTasks()
|
||||||
break
|
break
|
||||||
|
25
src/components/tasks/partials/label.vue
Normal file
25
src/components/tasks/partials/label.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type {ILabel} from '@/modelTypes/ILabel'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
label: ILabel
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:key="label.id"
|
||||||
|
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||||
|
class="tag"
|
||||||
|
>
|
||||||
|
<span>{{ label.title }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tag {
|
||||||
|
& + & {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="label-wrapper">
|
<div class="label-wrapper">
|
||||||
<span
|
<XLabel
|
||||||
|
v-for="label in labels"
|
||||||
|
:label="label"
|
||||||
:key="label.id"
|
:key="label.id"
|
||||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
/>
|
||||||
class="tag"
|
|
||||||
v-for="label in labels">
|
|
||||||
<span>{{ label.title }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -14,6 +12,8 @@
|
|||||||
import type {PropType} from 'vue'
|
import type {PropType} from 'vue'
|
||||||
import type {ILabel} from '@/modelTypes/ILabel'
|
import type {ILabel} from '@/modelTypes/ILabel'
|
||||||
|
|
||||||
|
import XLabel from '@/components/tasks/partials/label.vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
labels: {
|
labels: {
|
||||||
type: Array as PropType<ILabel[]>,
|
type: Array as PropType<ILabel[]>,
|
||||||
@ -26,10 +26,4 @@ defineProps({
|
|||||||
.label-wrapper {
|
.label-wrapper {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
|
||||||
& + & {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
196
src/components/tasks/partials/singleTaskInlineReadonly.vue
Normal file
196
src/components/tasks/partials/singleTaskInlineReadonly.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<div class="task">
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
v-if="showProject && typeof project !== 'undefined'"
|
||||||
|
class="task-project"
|
||||||
|
:class="{'mr-2': task.hexColor !== ''}"
|
||||||
|
v-tooltip="$t('task.detail.belongsToProject', {project: project.title})"
|
||||||
|
>
|
||||||
|
{{ project.title }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ColorBubble
|
||||||
|
v-if="task.hexColor !== ''"
|
||||||
|
:color="getHexColor(task.hexColor)"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||||
|
<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">, </template>
|
||||||
|
</template>
|
||||||
|
›
|
||||||
|
</span>
|
||||||
|
{{ task.title }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<labels
|
||||||
|
v-if="task.labels.length > 0"
|
||||||
|
class="labels ml-2 mr-1"
|
||||||
|
:labels="task.labels"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<User
|
||||||
|
v-for="(a, i) in task.assignees"
|
||||||
|
:avatar-size="20"
|
||||||
|
:key="task.id + 'assignee' + a.id + i"
|
||||||
|
:show-username="false"
|
||||||
|
:user="a"
|
||||||
|
class="avatar"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="+new Date(task.dueDate) > 0"
|
||||||
|
class="dueDate"
|
||||||
|
v-tooltip="formatDateLong(task.dueDate)"
|
||||||
|
>
|
||||||
|
<time
|
||||||
|
:datetime="formatISO(task.dueDate)"
|
||||||
|
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||||
|
class="is-italic"
|
||||||
|
>
|
||||||
|
– {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||||
|
</time>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<priority-label :priority="task.priority" :done="task.done"/>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<span class="project-task-icon" v-if="task.attachments.length > 0">
|
||||||
|
<icon icon="paperclip"/>
|
||||||
|
</span>
|
||||||
|
<span class="project-task-icon" v-if="task.description">
|
||||||
|
<icon icon="align-left"/>
|
||||||
|
</span>
|
||||||
|
<span class="project-task-icon" v-if="task.repeatAfter.amount > 0">
|
||||||
|
<icon icon="history"/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<checklist-summary :task="task"/>
|
||||||
|
|
||||||
|
<progress
|
||||||
|
class="progress is-small"
|
||||||
|
v-if="task.percentDone > 0"
|
||||||
|
:value="task.percentDone * 100" max="100"
|
||||||
|
>
|
||||||
|
{{ task.percentDone * 100 }}%
|
||||||
|
</progress>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
|
import {getHexColor} from '@/models/task'
|
||||||
|
import type {ITask} from '@/modelTypes/ITask'
|
||||||
|
|
||||||
|
import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
|
||||||
|
import Labels from '@/components/tasks/partials//labels.vue'
|
||||||
|
import ChecklistSummary from '@/components/tasks/partials/checklist-summary.vue'
|
||||||
|
|
||||||
|
import User from '@/components/misc/user.vue'
|
||||||
|
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||||
|
|
||||||
|
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||||
|
|
||||||
|
import {useProjectStore} from '@/stores/projects'
|
||||||
|
|
||||||
|
const {
|
||||||
|
task,
|
||||||
|
showProject = false,
|
||||||
|
} = defineProps<{
|
||||||
|
task: ITask,
|
||||||
|
showProject?: boolean,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
|
const projectStore = useProjectStore()
|
||||||
|
|
||||||
|
const project = computed(() => projectStore.projects[task.projectId])
|
||||||
|
const projectColor = computed(() => project.value ? project.value?.hexColor : '')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.task {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
transition: background-color $transition;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: $radius;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
//display: -webkit-box;
|
||||||
|
hyphens: auto;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
//flex: 1 0 50%;
|
||||||
|
|
||||||
|
.dueDate {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overdue {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-project {
|
||||||
|
width: auto;
|
||||||
|
color: var(--grey-400);
|
||||||
|
font-size: .9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-left: .5rem;
|
||||||
|
height: 21px;
|
||||||
|
width: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-task-icon {
|
||||||
|
margin-left: 6px;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text);
|
||||||
|
transition: color ease $transition-duration;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--grey-900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasktext.done {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--grey-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.parent-tasks {
|
||||||
|
color: var(--grey-500);
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user