feat: Added the ability to delete task lists
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m48s

This commit is contained in:
domrichardson
2026-03-30 10:14:07 +01:00
parent b9ca845b9c
commit b09137eca5
5 changed files with 62 additions and 6 deletions

View File

@@ -812,13 +812,9 @@ func (s *TaskService) DeleteTaskList(ctx context.Context, spaceID, taskListID, u
return errors.New("task list not found") return errors.New("task list not found")
} }
tasks, err := s.taskRepo.ListTasks(ctx, spaceID, map[string]any{"task_list_id": taskListID}) if err := s.taskRepo.DeleteTasksByTaskListID(ctx, taskListID); err != nil {
if err != nil {
return err return err
} }
if len(tasks) > 0 {
return errors.New("cannot delete task list with tasks")
}
return s.taskListRepo.DeleteTaskList(ctx, taskListID) return s.taskListRepo.DeleteTaskList(ctx, taskListID)
} }

View File

@@ -225,6 +225,7 @@ type TaskRepository interface {
SearchTasks(ctx context.Context, spaceID bson.ObjectID, query string) ([]*entities.Task, error) SearchTasks(ctx context.Context, spaceID bson.ObjectID, query string) ([]*entities.Task, error)
UpdateTask(ctx context.Context, task *entities.Task) error UpdateTask(ctx context.Context, task *entities.Task) error
DeleteTask(ctx context.Context, id bson.ObjectID) error DeleteTask(ctx context.Context, id bson.ObjectID) error
DeleteTasksByTaskListID(ctx context.Context, taskListID bson.ObjectID) error
DeleteTasksBySpaceID(ctx context.Context, spaceID bson.ObjectID) error DeleteTasksBySpaceID(ctx context.Context, spaceID bson.ObjectID) error
CountChildren(ctx context.Context, parentTaskID bson.ObjectID) (int64, error) CountChildren(ctx context.Context, parentTaskID bson.ObjectID) (int64, error)
} }

View File

@@ -95,6 +95,11 @@ func (r *TaskRepository) DeleteTask(ctx context.Context, id bson.ObjectID) error
return err return err
} }
func (r *TaskRepository) DeleteTasksByTaskListID(ctx context.Context, taskListID bson.ObjectID) error {
_, err := r.collection.DeleteMany(ctx, bson.M{"task_list_id": taskListID})
return err
}
func (r *TaskRepository) DeleteTasksBySpaceID(ctx context.Context, spaceID bson.ObjectID) error { func (r *TaskRepository) DeleteTasksBySpaceID(ctx context.Context, spaceID bson.ObjectID) error {
_, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID}) _, err := r.collection.DeleteMany(ctx, bson.M{"space_id": spaceID})
return err return err

View File

@@ -212,6 +212,8 @@
v-if="activeView === 'tasks'" v-if="activeView === 'tasks'"
:tasks="tasks" :tasks="tasks"
:statuses="taskStatuses" :statuses="taskStatuses"
:selected-task-list="selectedTaskList"
:can-delete-task-list="canDeleteTasks"
@select-task="openTaskDetail" @select-task="openTaskDetail"
@filter-change="applyTaskFilters" @filter-change="applyTaskFilters"
@reorder-status="reorderTaskStatuses" @reorder-status="reorderTaskStatuses"
@@ -219,6 +221,7 @@
@rename-status="renameTaskStatus" @rename-status="renameTaskStatus"
@delete-status="deleteTaskStatus" @delete-status="deleteTaskStatus"
@update-task-status="updateTaskStatusFromBoard" @update-task-status="updateTaskStatusFromBoard"
@delete-task-list="removeTaskList"
/> />
<SearchResultsPage <SearchResultsPage
v-else-if="isSearchRoute" v-else-if="isSearchRoute"
@@ -1294,6 +1297,36 @@ const createTaskList = async (taskListData) => {
} }
}; };
const removeTaskList = async (taskList) => {
if (!currentSpace.value?.id || !taskList?.id || !canDeleteTasks.value) {
return;
}
if (!confirm(`Delete task list "${taskList.name}" and all associated tasks?`)) {
return;
}
try {
await spaceStore.deleteTaskList(currentSpace.value.id, taskList.id);
if (selectedTaskList.value?.id === taskList.id) {
selectedTaskList.value = null;
taskDetail.value = null;
taskModalDraft.value = null;
showTaskModal.value = false;
taskFilters.value = {
taskListId: null,
statusId: null,
parentTaskId: null,
};
await spaceStore.fetchTasks(currentSpace.value.id, taskFilters.value);
activeView.value = "notes";
}
} catch (error) {
alert(error?.response?.data || "Unable to delete task list.");
}
};
const createSpace = async (spaceData) => { const createSpace = async (spaceData) => {
showCreateSpaceModal.value = false; showCreateSpaceModal.value = false;
await spaceStore.createSpace(spaceData); await spaceStore.createSpace(spaceData);

View File

@@ -185,6 +185,12 @@
</section> </section>
</div> </div>
<section v-if="selectedTaskList && canDeleteTaskList" class="danger-zone" aria-labelledby="task-list-danger-zone-title">
<h6 id="task-list-danger-zone-title" class="danger-zone-title">Danger Zone</h6>
<p class="danger-zone-copy mb-2">Delete this task list and all associated tasks permanently.</p>
<button type="button" class="btn btn-outline-danger" @click="emitDeleteTaskList">Delete Task List</button>
</section>
<teleport to="body"> <teleport to="body">
<div v-if="showStatusModal" class="modal fade show d-block" tabindex="-1" role="dialog" aria-modal="true" @click.self="closeStatusModal"> <div v-if="showStatusModal" class="modal fade show d-block" tabindex="-1" role="dialog" aria-modal="true" @click.self="closeStatusModal">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
@@ -235,9 +241,17 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
selectedTaskList: {
type: Object,
default: null,
},
canDeleteTaskList: {
type: Boolean,
default: false,
},
}); });
const emit = defineEmits(["create-task", "select-task", "filter-change", "reorder-status", "create-status", "rename-status", "delete-status", "update-task-status"]); const emit = defineEmits(["create-task", "select-task", "filter-change", "reorder-status", "create-status", "rename-status", "delete-status", "update-task-status", "delete-task-list"]);
const filterStatus = ref(""); const filterStatus = ref("");
const filterParent = ref(""); const filterParent = ref("");
@@ -470,6 +484,13 @@ const deleteStatusFromModal = () => {
}); });
closeStatusModal(); closeStatusModal();
}; };
const emitDeleteTaskList = () => {
if (!props.selectedTaskList?.id || !props.canDeleteTaskList) {
return;
}
emit("delete-task-list", props.selectedTaskList);
};
</script> </script>
<style scoped src="../assets/styles/scoped/components/TaskBoard.css"></style> <style scoped src="../assets/styles/scoped/components/TaskBoard.css"></style>