fix(kanban): make sure tasks which changed their done status are moved around in buckets
This fixes a bug where tasks which had their done status changed were not moved in the correct bucket. This affected both frontend and api. The move of the task between buckets is now correctly done in the api and frontend - with a bit of duplicated logic between the two. This could be optimized further in the future. Resolves https://kolaente.dev/vikunja/vikunja/issues/2610
This commit is contained in:
@ -119,14 +119,14 @@ watch(
|
||||
loadedProjectId.value = 0
|
||||
const projectFromStore = projectStore.projects[projectData.id]
|
||||
if (projectFromStore) {
|
||||
baseStore.handleSetCurrentProject({project: projectFromStore})
|
||||
baseStore.handleSetCurrentProject({project: projectFromStore, currentProjectViewId: props.viewId})
|
||||
}
|
||||
|
||||
// We create an extra project object instead of creating it in project.value because that would trigger a ui update which would result in bad ux.
|
||||
const project = new ProjectModel(projectData)
|
||||
try {
|
||||
const loadedProject = await projectService.value.get(project)
|
||||
baseStore.handleSetCurrentProject({project: loadedProject})
|
||||
baseStore.handleSetCurrentProject({project: loadedProject, currentProjectViewId: props.viewId})
|
||||
} finally {
|
||||
loadedProjectId.value = projectIdToLoad
|
||||
}
|
||||
@ -134,6 +134,14 @@ watch(
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.viewId,
|
||||
() => {
|
||||
baseStore.setCurrentProjectViewId(props.viewId)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function getViewTitle(view: IProjectView) {
|
||||
switch (view.title) {
|
||||
case 'List':
|
||||
|
@ -12,6 +12,7 @@ import {useMenuActive} from '@/composables/useMenuActive'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {Right} from '@/constants/rights'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
|
||||
export const useBaseStore = defineStore('base', () => {
|
||||
const loading = ref(false)
|
||||
@ -22,6 +23,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
id: 0,
|
||||
isArchived: false,
|
||||
}))
|
||||
const currentProjectViewId = ref<IProjectView['id'] | undefined>(undefined)
|
||||
const background = ref('')
|
||||
const blurHash = ref('')
|
||||
|
||||
@ -35,7 +37,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
loading.value = newLoading
|
||||
}
|
||||
|
||||
function setCurrentProject(newCurrentProject: IProject | null) {
|
||||
function setCurrentProject(newCurrentProject: IProject | null, currentViewId?: IProjectView['id']) {
|
||||
// Server updates don't return the right. Therefore, the right is reset after updating the project which is
|
||||
// confusing because all the buttons will disappear in that case. To prevent this, we're keeping the right
|
||||
// when updating the project in global state.
|
||||
@ -58,6 +60,11 @@ export const useBaseStore = defineStore('base', () => {
|
||||
...newCurrentProject,
|
||||
maxRight,
|
||||
}
|
||||
setCurrentProjectViewId(currentViewId)
|
||||
}
|
||||
|
||||
function setCurrentProjectViewId(viewId?: IProjectView['id']) {
|
||||
currentProjectViewId.value = viewId
|
||||
}
|
||||
|
||||
function setHasTasks(newHasTasks: boolean) {
|
||||
@ -93,7 +100,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
}
|
||||
|
||||
async function handleSetCurrentProject(
|
||||
{project, forceUpdate = false}: {project: IProject | null, forceUpdate?: boolean},
|
||||
{project, forceUpdate = false, currentProjectViewId = undefined}: {project: IProject | null, forceUpdate?: boolean, currentProjectViewId?: IProjectView['id']},
|
||||
) {
|
||||
if (project === null || typeof project === 'undefined') {
|
||||
setCurrentProject({})
|
||||
@ -129,7 +136,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
setBlurHash('')
|
||||
}
|
||||
|
||||
setCurrentProject(project)
|
||||
setCurrentProject(project, currentProjectViewId)
|
||||
}
|
||||
|
||||
const authStore = useAuthStore()
|
||||
@ -143,6 +150,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
loading: readonly(loading),
|
||||
ready: readonly(ready),
|
||||
currentProject: readonly(currentProject),
|
||||
currentProjectViewId: readonly(currentProjectViewId),
|
||||
background: readonly(background),
|
||||
blurHash: readonly(blurHash),
|
||||
hasTasks: readonly(hasTasks),
|
||||
@ -154,6 +162,7 @@ export const useBaseStore = defineStore('base', () => {
|
||||
setLoading,
|
||||
setReady,
|
||||
setCurrentProject,
|
||||
setCurrentProjectViewId,
|
||||
setHasTasks,
|
||||
setKeyboardShortcutsActive,
|
||||
setQuickActionsActive,
|
||||
|
@ -14,6 +14,7 @@ import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IBucket} from '@/modelTypes/IBucket'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import type {IProjectView} from '@/modelTypes/IProjectView'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
||||
const TASKS_PER_BUCKET = 25
|
||||
|
||||
@ -36,6 +37,7 @@ function getTaskIndicesById(buckets: IBucket[], taskId: ITask['id']) {
|
||||
*/
|
||||
export const useKanbanStore = defineStore('kanban', () => {
|
||||
const authStore = useAuthStore()
|
||||
const baseStore = useBaseStore()
|
||||
|
||||
const buckets = ref<IBucket[]>([])
|
||||
const projectId = ref<IProject['id']>(0)
|
||||
@ -138,7 +140,48 @@ export const useKanbanStore = defineStore('kanban', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// This function is an exact clone of the logic in the api
|
||||
function getDefaultBucketId(view: IProjectView): IBucket['id'] {
|
||||
if (view.defaultBucketId) {
|
||||
return view.defaultBucketId
|
||||
}
|
||||
|
||||
return buckets.value[0]?.id
|
||||
}
|
||||
|
||||
function ensureTaskIsInCorrectBucket(task: ITask) {
|
||||
if (buckets.value.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const {bucketIndex} = getTaskIndicesById(buckets.value, task.id)
|
||||
if (bucketIndex === null) return
|
||||
const currentTaskBucket = buckets.value[bucketIndex]
|
||||
|
||||
const currentView: IProjectView = baseStore.currentProject?.views.find(v => v.id === baseStore.currentProjectViewId)
|
||||
if(typeof currentView === 'undefined') return
|
||||
|
||||
// If the task is done, make sure it is in the done bucket
|
||||
if (task.done && currentView.doneBucketId !== 0 && currentTaskBucket.id !== currentView.doneBucketId) {
|
||||
moveTaskToBucket(task, currentView.doneBucketId)
|
||||
}
|
||||
|
||||
// If the task is not done but was in the done bucket before, move it to the default bucket
|
||||
if(!task.done && currentView.doneBucketId !== 0 && currentTaskBucket.id === currentView.doneBucketId) {
|
||||
const defaultBucketId = getDefaultBucketId(currentView)
|
||||
moveTaskToBucket(task, defaultBucketId)
|
||||
}
|
||||
|
||||
setTaskInBucket(task)
|
||||
}
|
||||
|
||||
function moveTaskToBucket(task: ITask, bucketId: IBucket['id']) {
|
||||
const {bucketIndex} = getTaskIndicesById(buckets.value, task.id)
|
||||
if (bucketIndex === null) return
|
||||
const currentTaskBucket = buckets.value[bucketIndex]
|
||||
if (typeof currentTaskBucket === 'undefined' || currentTaskBucket.id === bucketId) {
|
||||
return
|
||||
}
|
||||
removeTaskInBucket(task)
|
||||
task.bucketId = bucketId
|
||||
addTaskToBucket(task)
|
||||
@ -149,7 +192,7 @@ export const useKanbanStore = defineStore('kanban', () => {
|
||||
const oldBucket = buckets.value[bucketIndex]
|
||||
const newBucket = {
|
||||
...oldBucket,
|
||||
count: oldBucket.count + 1,
|
||||
count: (oldBucket?.count || 0) + 1,
|
||||
tasks: [
|
||||
...oldBucket.tasks,
|
||||
task,
|
||||
@ -342,6 +385,7 @@ export const useKanbanStore = defineStore('kanban', () => {
|
||||
createBucket,
|
||||
deleteBucket,
|
||||
updateBucket,
|
||||
ensureTaskIsInCorrectBucket,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -156,7 +156,7 @@ export const useTaskStore = defineStore('task', () => {
|
||||
const taskService = new TaskService()
|
||||
try {
|
||||
const updatedTask = await taskService.update(task)
|
||||
kanbanStore.setTaskInBucket(updatedTask)
|
||||
kanbanStore.ensureTaskIsInCorrectBucket(updatedTask)
|
||||
return updatedTask
|
||||
} finally {
|
||||
cancel()
|
||||
|
@ -625,7 +625,6 @@ import {scrollIntoView} from '@/helpers/scrollIntoView'
|
||||
import {useAttachmentStore} from '@/stores/attachments'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import {useKanbanStore} from '@/stores/kanban'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
|
||||
@ -651,7 +650,6 @@ const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
const baseStore = useBaseStore()
|
||||
const attachmentStore = useAttachmentStore()
|
||||
const taskStore = useTaskStore()
|
||||
const kanbanStore = useKanbanStore()
|
||||
@ -888,14 +886,6 @@ async function toggleTaskDone() {
|
||||
newTask,
|
||||
toggleTaskDone,
|
||||
)
|
||||
|
||||
if(task.value.done) {
|
||||
baseStore.currentProject?.views.forEach(v => {
|
||||
if (v.doneBucketId !== 0) {
|
||||
kanbanStore.moveTaskToBucket(task.value, v.doneBucketId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function changeProject(project: IProject) {
|
||||
|
Reference in New Issue
Block a user