feat: task relatedTasks script setup
All checks were successful
continuous-integration/drone/pr Build is passing

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

View File

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