1
0

fix(task): specify task index when creating multiple tasks at once

This change allows to specify the task index when creating a task, which will then be checked to avoid duplicates and used. This allows us to calculate the indexes for all tasks beforehand when creating them at once using quick add magic.
The method is not bulletproof, but already fixes a problem where multiple tasks would have the same index when created that way.

Resolves https://community.vikunja.io/t/add-multiple-tasks-at-once/333/16

(cherry picked from commit 55dd7d298187dcc8393ae67340117d66d45dc4ef)
This commit is contained in:
kolaente 2024-09-11 17:58:42 +02:00
parent ac87035742
commit 5bfd99dd77
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
4 changed files with 66 additions and 9 deletions

View File

@ -36,7 +36,7 @@ import {ref, watchEffect} from 'vue'
import {useDebounceFn} from '@vueuse/core'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
import {validatePassword} from '@/helpers/validatePasswort';
import {validatePassword} from '@/helpers/validatePasswort'
const props = withDefaults(defineProps<{
modelValue: string,

View File

@ -51,6 +51,7 @@
import {computed, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useElementHover} from '@vueuse/core'
import { useRouter } from 'vue-router'
import {RELATION_KIND} from '@/types/IRelationKind'
import type {ITask} from '@/modelTypes/ITask'
@ -66,6 +67,8 @@ import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
import TaskService from '@/services/task'
import TaskModel from '@/models/task'
const props = withDefaults(defineProps<{
defaultPosition?: number,
@ -81,6 +84,7 @@ const {textarea: newTaskInput} = useAutoHeightTextarea(newTaskTitle)
const {t} = useI18n({useScope: 'global'})
const authStore = useAuthStore()
const taskStore = useTaskStore()
const router = useRouter()
// enable only if we don't have a modal
// onStartTyping(() => {
@ -129,21 +133,56 @@ async function addTask() {
const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title, authStore.settings.frontendSettings.quickAddMagicMode) ?? [])
await taskStore.ensureLabelsExist(allLabels.flat())
const newTasks = tasksToCreate.map(async ({title, project}) => {
const taskCollectionService = new TaskService()
const projectIndices = new Map<number, number>()
let currentProjectId = authStore.settings.defaultProjectId
if (typeof router.currentRoute.value.params.projectId !== 'undefined') {
currentProjectId = Number(router.currentRoute.value.params.projectId)
}
// Create a map of project indices before creating tasks
if (tasksToCreate.length > 1) {
for (const {project} of tasksToCreate) {
const projectId = project !== null
? await taskStore.findProjectId({project, projectId: 0})
: currentProjectId
if (!projectIndices.has(projectId)) {
const newestTask = await taskCollectionService.getAll(new TaskModel({}), {
sort_by: ['id'],
order_by: ['desc'],
per_page: 1,
filter: `project_id = ${projectId}`,
})
projectIndices.set(projectId, newestTask[0]?.index || 0)
}
}
}
const newTasks = tasksToCreate.map(async ({title, project}, index) => {
if (title === '') {
return
}
// If the task has a project specified, make sure to use it
let projectId = null
if (project !== null) {
projectId = await taskStore.findProjectId({project, projectId: 0})
const projectId = project !== null
? await taskStore.findProjectId({project, projectId: 0})
: currentProjectId
// Calculate new index for this task per project
let taskIndex: number | undefined
if (tasksToCreate.length > 1) {
const lastIndex = projectIndices.get(projectId)
taskIndex = lastIndex + index + 1
}
console.log('many tasks to create', taskIndex)
const task = await taskStore.createNewTask({
title,
projectId: projectId || authStore.settings.defaultProjectId,
position: props.defaultPosition,
index: taskIndex,
})
createdTasks[title] = task
return task

View File

@ -414,6 +414,7 @@ export const useTaskStore = defineStore('task', () => {
bucketId,
projectId,
position,
index,
} :
Partial<ITask>,
) {
@ -429,6 +430,7 @@ export const useTaskStore = defineStore('task', () => {
projectId,
bucketId,
position,
index,
}))
} finally {
cancel()
@ -467,6 +469,7 @@ export const useTaskStore = defineStore('task', () => {
assignees,
bucketId: bucketId || 0,
position,
index,
})
task.repeatAfter = parsedTask.repeats

View File

@ -738,10 +738,25 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, setB
t.UID = uuid.NewString()
}
// Get the index for this task
t.Index, err = getNextTaskIndex(s, t.ProjectID)
if err != nil {
return err
// Check if an index was provided, otherwise calculate a new one
if t.Index == 0 {
t.Index, err = getNextTaskIndex(s, t.ProjectID)
if err != nil {
return err
}
} else {
// Check if the provided index is already taken
exists, err := s.Where("project_id = ? AND `index` = ?", t.ProjectID, t.Index).Exist(&Task{})
if err != nil {
return err
}
if exists {
// If the index is taken, calculate a new one
t.Index, err = getNextTaskIndex(s, t.ProjectID)
if err != nil {
return err
}
}
}
t.HexColor = utils.NormalizeHex(t.HexColor)