feat: task relatedTasks script setup
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Dominik Pschenitschni 2022-05-11 00:20:38 +02:00
parent 8718efbc1c
commit 4083c087d2
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
2 changed files with 172 additions and 191 deletions

View File

@ -25,7 +25,7 @@
</transition>
</label>
<div class="field" key="field-search">
<multiselect
<Multiselect
:placeholder="$t('task.relation.searchPlaceholder')"
@search="findTasks"
:loading="taskService.loading"
@ -37,37 +37,37 @@
@create="createAndRelateTask"
@select="addTaskRelation"
>
<template #searchResult="props">
<span v-if="typeof props.option !== 'string'" class="search-result">
<template #searchResult="{option: task}">
<span v-if="typeof task !== 'string'" class="search-result">
<span
class="different-list"
v-if="props.option.listId !== listId"
v-if="task.listId !== listId"
>
<span
v-if="props.option.differentNamespace !== null"
v-if="task.differentNamespace !== null"
v-tooltip="$t('task.relation.differentNamespace')">
{{ props.option.differentNamespace }} >
{{ task.differentNamespace }} >
</span>
<span
v-if="props.option.differentList !== null"
v-if="task.differentList !== null"
v-tooltip="$t('task.relation.differentList')">
{{ props.option.differentList }} >
{{ task.differentList }} >
</span>
</span>
{{ props.option.title }}
{{ task.title }}
</span>
<span class="search-result" v-else>
{{ props.option }}
{{ task }}
</span>
</template>
</multiselect>
</Multiselect>
</div>
<div class="field has-addons mb-4" key="field-kind">
<div class="control is-expanded">
<div class="select is-fullwidth has-defaults">
<select v-model="newTaskRelationKind">
<option value="unset">{{ $t('task.relation.select') }}</option>
<option :key="rk" :value="rk" v-for="rk in relationKinds">
<option :key="rk" :value="rk" v-for="rk in RELATION_KINDS">
{{ $tc(`task.relation.kinds.${rk}`, 1) }}
</option>
</select>
@ -138,184 +138,177 @@
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
<script setup lang="ts">
import {ref, reactive, shallowReactive, watch, computed, PropType} from 'vue'
import {useStore} from 'vuex'
import {useI18n} from 'vue-i18n'
import TaskService from '../../../services/task'
import TaskModel from '../../../models/task'
import TaskRelationService from '../../../services/taskRelation'
import relationKinds from '../../../models/constants/relationKinds'
import TaskRelationModel from '../../../models/taskRelation'
import TaskService from '@/services/task'
import TaskModel from '@/models/task'
import TaskRelationService from '@/services/taskRelation'
import TaskRelationModel from '@/models/taskRelation'
import BaseButton from '@/components/base/BaseButton.vue'
import Multiselect from '@/components/input/multiselect.vue'
import { error } from '@/message'
export default defineComponent({
name: 'relatedTasks',
data() {
return {
relatedTasks: {},
taskService: new TaskService(),
foundTasks: [],
relationKinds: relationKinds,
newTaskRelationTask: new TaskModel(),
newTaskRelationKind: 'related',
taskRelationService: new TaskRelationService(),
showDeleteModal: false,
relationToDelete: {},
saved: false,
showNewRelationForm: false,
query: '',
}
const RELATION_KINDS = [
'subtask',
'parenttask',
'related',
'duplicates',
'blocking',
'blocked',
'precedes',
'follows',
'copiedfrom',
'copiedto',
] as const
type RelationKind = typeof RELATION_KINDS[number]
type RelatedTasks = Record<RelationKind, TaskModel[]>
const props = defineProps({
taskId: {
type: Number,
required: true,
},
components: {
BaseButton,
Multiselect,
initialRelatedTasks: {
type: Object as PropType<RelatedTasks>,
default: () => ({}),
},
props: {
taskId: {
type: Number,
required: true,
},
initialRelatedTasks: {
type: Object,
default: () => {
},
},
showNoRelationsNotice: {
type: Boolean,
default: false,
},
listId: {
type: Number,
default: 0,
},
editEnabled: {
default: true,
},
showNoRelationsNotice: {
type: Boolean,
default: false,
},
watch: {
initialRelatedTasks: {
handler(value) {
this.relatedTasks = value
},
immediate: true,
},
listId: {
type: Number,
default: 0,
},
computed: {
showCreate() {
return Object.keys(this.relatedTasks).length === 0 || this.showNewRelationForm
},
namespace() {
return this.$store.getters['namespaces/getListAndNamespaceById'](this.listId, true)?.namespace
},
mappedRelatedTasks() {
return Object.entries(this.relatedTasks).map(([kind, tasks]) => ({
title: this.$tc(`task.relation.kinds.${kind}`, tasks.length),
tasks: this.mapRelatedTasks(tasks),
kind,
}))
},
mappedFoundTasks() {
return this.mapRelatedTasks(this.foundTasks.filter(t => t.id !== this.taskId))
},
},
methods: {
async findTasks(query) {
this.query = query
this.foundTasks = await this.taskService.getAll({}, {s: query})
},
async addTaskRelation() {
if (this.newTaskRelationTask.id === 0 && this.query !== '') {
return this.createAndRelateTask(this.query)
}
if (this.newTaskRelationTask.id === 0) {
this.$message.error({message: this.$t('task.relation.taskRequired')})
return
}
const rel = new TaskRelationModel({
taskId: this.taskId,
otherTaskId: this.newTaskRelationTask.id,
relationKind: this.newTaskRelationKind,
})
await this.taskRelationService.create(rel)
if (!this.relatedTasks[this.newTaskRelationKind]) {
this.relatedTasks[this.newTaskRelationKind] = []
}
this.relatedTasks[this.newTaskRelationKind].push(this.newTaskRelationTask)
this.newTaskRelationTask = null
this.saved = true
this.showNewRelationForm = false
setTimeout(() => {
this.saved = false
}, 2000)
},
async removeTaskRelation() {
const rel = new TaskRelationModel({
relationKind: this.relationToDelete.relationKind,
taskId: this.taskId,
otherTaskId: this.relationToDelete.otherTaskId,
})
try {
await this.taskRelationService.delete(rel)
const kind = this.relationToDelete.relationKind
for (const t in this.relatedTasks[kind]) {
if (this.relatedTasks[kind][t].id === this.relationToDelete.otherTaskId) {
this.relatedTasks[kind].splice(t, 1)
break
}
}
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
} finally {
this.showDeleteModal = false
}
},
async createAndRelateTask(title) {
const newTask = new TaskModel({title: title, listId: this.listId})
this.newTaskRelationTask = await this.taskService.create(newTask)
await this.addTaskRelation()
},
relationKindTitle(kind, length) {
return this.$tc(`task.relation.kinds.${kind}`, length)
},
mapRelatedTasks(tasks) {
return tasks
.map(task => {
// by doing this here once we can save a lot of duplicate calls in the template
const listAndNamespace = this.$store.getters['namespaces/getListAndNamespaceById'](task.listId, true)
const {
list,
namespace,
} = listAndNamespace === null ? {list: null, namespace: null} : listAndNamespace
return {
...task,
differentNamespace:
(namespace !== null &&
namespace.id !== this.namespace.id &&
namespace?.title) || null,
differentList:
(list !== null &&
task.listId !== this.listId &&
list?.title) || null,
}
})
},
editEnabled: {
default: true,
},
})
const relatedTasks = ref<RelatedTasks>({})
const taskService = shallowReactive(new TaskService())
const foundTasks = ref<TaskModel[]>([])
const newTaskRelationTask = reactive<TaskModel>(new TaskModel())
const newTaskRelationKind = ref<RelationKind>('related')
const taskRelationService = shallowReactive(new TaskRelationService())
const showDeleteModal = ref(false)
const relationToDelete = ref({})
const saved = ref(false)
const showNewRelationForm = ref(false)
const query = ref('')
watch(
() => props.initialRelatedTasks,
(value) => {
relatedTasks.value = value
},
{immediate: true},
)
const store = useStore()
const {t} = useI18n({useScope: 'global'})
const showCreate = computed(() => Object.keys(relatedTasks.value).length === 0 || showNewRelationForm.value)
const namespace = computed(() => store.getters['namespaces/getListAndNamespaceById'](props.listId, true)?.namespace)
const mappedRelatedTasks = computed(() => Object.entries(relatedTasks.value).map(([kind, tasks]) => ({
title: t(`task.relation.kinds.${kind}`, tasks.length),
tasks: mapRelatedTasks(tasks),
kind,
})))
const mappedFoundTasks = computed(() => mapRelatedTasks(foundTasks.value.filter(t => t.id !== props.taskId)))
async function findTasks(newQuery: string) {
query.value = newQuery
foundTasks.value = await taskService.getAll({}, {s: newQuery})
}
async function addTaskRelation() {
if (newTaskRelationTask.id === 0 && query.value !== '') {
return createAndRelateTask(query.value)
}
if (newTaskRelationTask.id === 0) {
error({message: t('task.relation.taskRequired')})
return
}
await taskRelationService.create(new TaskRelationModel({
taskId: props.taskId,
otherTaskId: newTaskRelationTask.id,
relationKind: newTaskRelationKind.value,
}))
if (!relatedTasks.value[newTaskRelationKind.value]) {
relatedTasks.value[newTaskRelationKind.value] = []
}
relatedTasks.value[newTaskRelationKind.value].push(newTaskRelationTask)
Object.assign(newTaskRelationTask, new TaskModel({}))
saved.value = true
showNewRelationForm.value = false
setTimeout(() => {
saved.value = false
}, 2000)
}
async function removeTaskRelation() {
try {
const relationKind = relationToDelete.value.relationKind
await taskRelationService.delete(new TaskRelationModel({
relationKind,
taskId: props.taskId,
otherTaskId: relationToDelete.value.otherTaskId,
}))
for (const t in relatedTasks.value[relationKind]) {
if (relatedTasks.value[relationKind][t].id === relationToDelete.value.otherTaskId) {
relatedTasks.value[relationKind].splice(t, 1)
break
}
}
saved.value = true
setTimeout(() => {
saved.value = false
}, 2000)
} finally {
showDeleteModal.value = false
}
}
async function createAndRelateTask(title: string) {
const newTask = await taskService.create(new TaskModel({title, listId: props.listId}))
Object.assign(newTaskRelationTask, newTask)
await addTaskRelation()
}
function mapRelatedTasks(tasks: TaskModel[]) {
return tasks.map(task => {
// by doing this here once we can save a lot of duplicate calls in the template
const listAndNamespace = store.getters['namespaces/getListAndNamespaceById'](task.listId, true)
const {
list,
namespace: taskNamespace,
} = listAndNamespace === null ? {list: null, namespace: null} : listAndNamespace
return {
...task,
differentNamespace:
(taskNamespace !== null &&
taskNamespace.id !== namespace.value.id &&
taskNamespace?.title) || null,
differentList:
(list !== null &&
task.listId !== props.listId &&
list?.title) || null,
}
})
}
</script>
<style lang="scss" scoped>

View File

@ -1,12 +0,0 @@
[
"subtask",
"parenttask",
"related",
"duplicates",
"blocking",
"blocked",
"precedes",
"follows",
"copiedfrom",
"copiedto"
]