feat: allow marking a related task done directly from the list
This commit is contained in:
parent
943d5f7975
commit
ce0f58c783
5 changed files with 60 additions and 33 deletions
|
@ -68,7 +68,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, type PropType} from 'vue'
|
import {defineComponent, type PropType} from 'vue'
|
||||||
|
|
||||||
import {playPop} from '../../../helpers/playPop'
|
|
||||||
import PriorityLabel from '../../../components/tasks/partials/priorityLabel.vue'
|
import PriorityLabel from '../../../components/tasks/partials/priorityLabel.vue'
|
||||||
import User from '../../../components/misc/user.vue'
|
import User from '../../../components/misc/user.vue'
|
||||||
import Done from '@/components/misc/Done.vue'
|
import Done from '@/components/misc/Done.vue'
|
||||||
|
@ -126,9 +125,6 @@ export default defineComponent({
|
||||||
...task,
|
...task,
|
||||||
done,
|
done,
|
||||||
})
|
})
|
||||||
if (done) {
|
|
||||||
playPop()
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingInternal = false
|
this.loadingInternal = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,26 +83,34 @@
|
||||||
<span class="title">{{ rts.title }}</span>
|
<span class="title">{{ rts.title }}</span>
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
<div :key="t.id" class="task" v-for="t in rts.tasks">
|
<div :key="t.id" class="task" v-for="t in rts.tasks">
|
||||||
<router-link
|
<div class="is-flex is-align-items-center">
|
||||||
:to="{ name: route.name as string, params: { id: t.id } }"
|
<Fancycheckbox
|
||||||
:class="{ 'is-strikethrough': t.done}">
|
class="task-done-checkbox"
|
||||||
<span
|
v-model="t.done"
|
||||||
class="different-list"
|
@update:model-value="toggleTaskDone(t)"
|
||||||
v-if="t.listId !== listId"
|
/>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: route.name as string, params: { id: t.id } }"
|
||||||
|
:class="{ 'is-strikethrough': t.done}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="t.differentNamespace !== null"
|
class="different-list"
|
||||||
v-tooltip="$t('task.relation.differentNamespace')">
|
v-if="t.listId !== listId"
|
||||||
{{ t.differentNamespace }} >
|
>
|
||||||
|
<span
|
||||||
|
v-if="t.differentNamespace !== null"
|
||||||
|
v-tooltip="$t('task.relation.differentNamespace')">
|
||||||
|
{{ t.differentNamespace }} >
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="t.differentList !== null"
|
||||||
|
v-tooltip="$t('task.relation.differentList')">
|
||||||
|
{{ t.differentList }} >
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
{{ t.title }}
|
||||||
v-if="t.differentList !== null"
|
</router-link>
|
||||||
v-tooltip="$t('task.relation.differentList')">
|
</div>
|
||||||
{{ t.differentList }} >
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{{ t.title }}
|
|
||||||
</router-link>
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="editEnabled"
|
v-if="editEnabled"
|
||||||
@click="setRelationToDelete({
|
@click="setRelationToDelete({
|
||||||
|
@ -154,7 +162,9 @@ 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'
|
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||||
|
|
||||||
|
import {error, success} from '@/message'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
taskId: {
|
taskId: {
|
||||||
|
@ -330,6 +340,23 @@ async function createAndRelateTask(title: string) {
|
||||||
newTaskRelation.task = newTask
|
newTaskRelation.task = newTask
|
||||||
await addTaskRelation()
|
await addTaskRelation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleTaskDone(task: ITask) {
|
||||||
|
await store.dispatch('tasks/update', task)
|
||||||
|
|
||||||
|
// Find the task in the list and update it so that it is correctly strike through
|
||||||
|
Object.entries(relatedTasks.value).some(([kind, tasks]) => {
|
||||||
|
return tasks.some((t, key) => {
|
||||||
|
const found = t.id === task.id
|
||||||
|
if (found) {
|
||||||
|
relatedTasks.value[kind as IRelationKind]![key] = task
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
success({message: t('task.detail.updateSuccess')})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -402,5 +429,13 @@ async function createAndRelateTask(title: string) {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: The height of the actual checkbox in the <Fancycheckbox/> component is too much resulting in a
|
||||||
|
// weired positioning of the checkbox. Setting the height here is a workaround until we fix the styling
|
||||||
|
// of the component.
|
||||||
|
.task-done-checkbox {
|
||||||
|
padding: 0;
|
||||||
|
height: 18px; // The exact height of the checkbox in the container
|
||||||
|
}
|
||||||
|
|
||||||
@include modal-transition();
|
@include modal-transition();
|
||||||
</style>
|
</style>
|
|
@ -114,7 +114,6 @@ import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import Fancycheckbox from '../../input/fancycheckbox.vue'
|
import Fancycheckbox from '../../input/fancycheckbox.vue'
|
||||||
import DeferTask from './defer-task.vue'
|
import DeferTask from './defer-task.vue'
|
||||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||||
import {playPop} from '@/helpers/playPop'
|
|
||||||
import ChecklistSummary from './checklist-summary.vue'
|
import ChecklistSummary from './checklist-summary.vue'
|
||||||
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||||
|
@ -208,10 +207,7 @@ export default defineComponent({
|
||||||
|
|
||||||
async markAsDone(checked: boolean) {
|
async markAsDone(checked: boolean) {
|
||||||
const updateFunc = async () => {
|
const updateFunc = async () => {
|
||||||
const task = await this.taskService.update(this.task)
|
const task = await this.$store.dispatch('tasks/update', this.task)
|
||||||
if (this.task.done) {
|
|
||||||
playPop()
|
|
||||||
}
|
|
||||||
this.task = task
|
this.task = task
|
||||||
this.$emit('task-updated', task)
|
this.$emit('task-updated', task)
|
||||||
this.$message.success({
|
this.$message.success({
|
||||||
|
|
|
@ -25,8 +25,9 @@ import type { IAttachment } from '@/modelTypes/IAttachment'
|
||||||
import type { IList } from '@/modelTypes/IList'
|
import type { IList } from '@/modelTypes/IList'
|
||||||
|
|
||||||
import type { RootStoreState, TaskState } from '@/store/types'
|
import type { RootStoreState, TaskState } from '@/store/types'
|
||||||
import { useLabelStore } from '@/stores/labels'
|
import {useLabelStore} from '@/stores/labels'
|
||||||
import { useListStore } from '@/stores/lists'
|
import {useListStore} from '@/stores/lists'
|
||||||
|
import {playPop} from '@/helpers/playPop'
|
||||||
|
|
||||||
// IDEA: maybe use a small fuzzy search here to prevent errors
|
// IDEA: maybe use a small fuzzy search here to prevent errors
|
||||||
function findPropertyByValue(object, key, value) {
|
function findPropertyByValue(object, key, value) {
|
||||||
|
@ -96,6 +97,9 @@ const tasksStore : Module<TaskState, RootStoreState>= {
|
||||||
try {
|
try {
|
||||||
const updatedTask = await taskService.update(task)
|
const updatedTask = await taskService.update(task)
|
||||||
ctx.commit('kanban/setTaskInBucket', updatedTask, {root: true})
|
ctx.commit('kanban/setTaskInBucket', updatedTask, {root: true})
|
||||||
|
if (task.done) {
|
||||||
|
playPop()
|
||||||
|
}
|
||||||
return updatedTask
|
return updatedTask
|
||||||
} finally {
|
} finally {
|
||||||
cancel()
|
cancel()
|
||||||
|
|
|
@ -451,7 +451,6 @@ import ColorPicker from '../../components/input/colorPicker.vue'
|
||||||
import heading from '@/components/tasks/partials/heading.vue'
|
import heading from '@/components/tasks/partials/heading.vue'
|
||||||
import Datepicker from '@/components/input/datepicker.vue'
|
import Datepicker from '@/components/input/datepicker.vue'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import {playPop} from '@/helpers/playPop'
|
|
||||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||||
|
|
||||||
|
@ -733,9 +732,6 @@ export default defineComponent({
|
||||||
...this.task,
|
...this.task,
|
||||||
done: !this.task.done,
|
done: !this.task.done,
|
||||||
}
|
}
|
||||||
if (newTask.done) {
|
|
||||||
playPop()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveTask({
|
this.saveTask({
|
||||||
task: newTask,
|
task: newTask,
|
||||||
|
|
Loading…
Reference in a new issue