diff --git a/frontend/src/modelTypes/ITaskPosition.ts b/frontend/src/modelTypes/ITaskPosition.ts new file mode 100644 index 0000000000..7ea71e1c0f --- /dev/null +++ b/frontend/src/modelTypes/ITaskPosition.ts @@ -0,0 +1,8 @@ +import type {IProjectView} from '@/modelTypes/IProjectView' +import type {IAbstract} from '@/modelTypes/IAbstract' + +export interface ITaskPosition extends IAbstract { + position: number + projectViewId: IProjectView['id'] + taskId: number +} \ No newline at end of file diff --git a/frontend/src/models/taskPosition.ts b/frontend/src/models/taskPosition.ts new file mode 100644 index 0000000000..e1c37ae0b0 --- /dev/null +++ b/frontend/src/models/taskPosition.ts @@ -0,0 +1,13 @@ +import AbstractModel from '@/models/abstractModel' +import type {ITaskPosition} from '@/modelTypes/ITaskPosition' + +export default class TaskPositionModel extends AbstractModel implements ITaskPosition { + position = 0 + projectViewId = 0 + taskId = 0 + + constructor(data: Partial) { + super() + this.assignData(data) + } +} diff --git a/frontend/src/services/taskPosition.ts b/frontend/src/services/taskPosition.ts new file mode 100644 index 0000000000..c74a8038a5 --- /dev/null +++ b/frontend/src/services/taskPosition.ts @@ -0,0 +1,15 @@ +import AbstractService from '@/services/abstractService' +import type {ITaskPosition} from '@/modelTypes/ITaskPosition' +import TaskPositionModel from '@/models/taskPosition' + +export default class TaskPositionService extends AbstractService { + constructor() { + super({ + update: '/tasks/{taskId}/position', + }) + } + + modelFactory(data: Partial) { + return new TaskPositionModel(data) + } +} \ No newline at end of file diff --git a/frontend/src/views/project/ProjectKanban.vue b/frontend/src/views/project/ProjectKanban.vue index 4f928bcb70..a627cf578c 100644 --- a/frontend/src/views/project/ProjectKanban.vue +++ b/frontend/src/views/project/ProjectKanban.vue @@ -302,6 +302,8 @@ import {success} from '@/message' import {useProjectStore} from '@/stores/projects' import type {TaskFilterParams} from '@/services/taskCollection' import type {IProjectView} from '@/modelTypes/IProjectView' +import TaskPositionService from '@/services/taskPosition' +import TaskPositionModel from '@/models/taskPosition' const { projectId = undefined, @@ -328,6 +330,7 @@ const baseStore = useBaseStore() const kanbanStore = useKanbanStore() const taskStore = useTaskStore() const projectStore = useProjectStore() +const taskPositionService = ref(new TaskPositionService()) const taskContainerRefs = ref<{ [id: IBucket['id']]: HTMLElement }>({}) const bucketLimitInputRef = ref(null) @@ -390,7 +393,7 @@ const project = computed(() => projectId ? projectStore.projects[projectId] : nu const buckets = computed(() => kanbanStore.buckets) const loading = computed(() => kanbanStore.isLoading) -const taskLoading = computed(() => taskStore.isLoading) +const taskLoading = computed(() => taskStore.isLoading || taskPositionService.value.loading) watch( () => ({ @@ -478,7 +481,7 @@ async function updateTaskPosition(e) { const newTask = klona(task) // cloning the task to avoid pinia store manipulation newTask.bucketId = newBucket.id - newTask.kanbanPosition = calculateItemPosition( + const position = calculateItemPosition( taskBefore !== null ? taskBefore.kanbanPosition : null, taskAfter !== null ? taskAfter.kanbanPosition : null, ) @@ -488,6 +491,8 @@ async function updateTaskPosition(e) { ) { newTask.done = project.value?.doneBucketId === newBucket.id } + + let bucketHasChanged = false if ( oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe. newBucket.id !== oldBucket.id @@ -500,10 +505,20 @@ async function updateTaskPosition(e) { ...newBucket, count: newBucket.count + 1, }) + bucketHasChanged = true } try { - await taskStore.update(newTask) + const newPosition = new TaskPositionModel({ + position, + projectViewId: view.id, + taskId: newTask.id, + }) + await taskPositionService.value.update(newPosition) + + if(bucketHasChanged) { + await taskStore.update(newTask) + } // Make sure the first and second task don't both get position 0 assigned if (newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) { diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index 11cb835675..30415cdd50 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -306,6 +306,7 @@ func GetProjectViewByIDAndProject(s *xorm.Session, id, projectID int64) (view *P } func GetProjectViewByID(s *xorm.Session, id int64) (view *ProjectView, err error) { + view = &ProjectView{} exists, err := s. Where("id = ?", id). NoAutoCondition(). diff --git a/pkg/models/task_position.go b/pkg/models/task_position.go index c56141ae9d..1994e0f3aa 100644 --- a/pkg/models/task_position.go +++ b/pkg/models/task_position.go @@ -46,7 +46,10 @@ func (tp *TaskPosition) TableName() string { } func (tp *TaskPosition) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { - pv := &ProjectView{ID: tp.ProjectViewID} + pv, err := GetProjectViewByID(s, tp.ProjectViewID) + if err != nil { + return false, err + } return pv.CanUpdate(s, a) }