feat: Added the ability to delete task lists
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
@@ -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)
} }
@@ -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)
} }
@@ -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
+33
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);
+22 -1
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>