From 6914badeb7cfa2cdef1ebfb620d303ab396de32d Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 11 Sep 2025 08:56:08 +0200 Subject: [PATCH] fix: reload list view when marking recurring task done (#1457) --- .../project/views/ProjectKanban.vue | 22 ++++++++++++- .../components/project/views/ProjectList.vue | 33 ++++++++++++++++--- .../components/tasks/partials/KanbanCard.vue | 13 ++++++++ frontend/src/services/savedFilter.ts | 4 +-- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/project/views/ProjectKanban.vue b/frontend/src/components/project/views/ProjectKanban.vue index b6b0abdf0..01c957ae6 100644 --- a/frontend/src/components/project/views/ProjectKanban.vue +++ b/frontend/src/components/project/views/ProjectKanban.vue @@ -215,6 +215,7 @@ :task="task" :loading="taskUpdating[task.id] ?? false" :project-id="projectId" + @taskCompletedRecurring="handleRecurringTaskCompletion" /> @@ -303,7 +304,7 @@ import { } from '@/helpers/saveCollapsedBucketState' import {calculateItemPosition} from '@/helpers/calculateItemPosition' -import {isSavedFilter} from '@/services/savedFilter' +import {isSavedFilter, useSavedFilter} from '@/services/savedFilter' import {success} from '@/message' import {useProjectStore} from '@/stores/projects' import type {TaskFilterParams} from '@/services/taskCollection' @@ -342,6 +343,9 @@ const projectStore = useProjectStore() const taskPositionService = ref(new TaskPositionService()) const taskBucketService = ref(new TaskBucketService()) +// Saved filter composable for accessing filter data +const {filter: savedFilter} = useSavedFilter(() => props.projectId) + const taskContainerRefs = ref<{ [id: IBucket['id']]: HTMLElement }>({}) const bucketLimitInputRef = ref(null) @@ -664,6 +668,22 @@ function updateBuckets(value: IBucket[]) { kanbanStore.setBuckets(value) } +function handleRecurringTaskCompletion() { + // Only reload if we're in a saved filter and the filter contains date fields + if (!isSavedFilter(project.value)) { + return + } + + const filterContainsDateFields = savedFilter.value.filters?.filter?.includes('due_date') || + savedFilter.value.filters?.filter?.includes('start_date') || + savedFilter.value.filters?.filter?.includes('end_date') + + if (filterContainsDateFields) { + // Reload the kanban board to refresh tasks that now match/don't match the filter + kanbanStore.loadBucketsForProject(props.projectId, props.viewId, params.value) + } +} + // TODO: fix type function updateBucketPosition(e: { newIndex: number }) { // (2) bucket positon is changed diff --git a/frontend/src/components/project/views/ProjectList.vue b/frontend/src/components/project/views/ProjectList.vue index 28a046756..0e10706c2 100644 --- a/frontend/src/components/project/views/ProjectList.vue +++ b/frontend/src/components/project/views/ProjectList.vue @@ -112,7 +112,8 @@ import {useTaskList} from '@/composables/useTaskList' import {PERMISSIONS as Permissions} from '@/constants/permissions' import {calculateItemPosition} from '@/helpers/calculateItemPosition' import type {ITask} from '@/modelTypes/ITask' -import {isSavedFilter} from '@/services/savedFilter' +import {isSavedFilter, useSavedFilter} from '@/services/savedFilter' +import {TASK_REPEAT_MODES} from '@/types/IRepeatMode' import {useBaseStore} from '@/stores/base' @@ -152,6 +153,9 @@ const { const taskPositionService = ref(new TaskPositionService()) +// Saved filter composable for accessing filter data +const {filter: savedFilter} = useSavedFilter(() => props.projectId) + const tasks = ref([]) watch( allTasks, @@ -216,9 +220,30 @@ function updateTaskList(task: ITask) { function updateTasks(updatedTask: ITask) { if (props.projectId < 0) { - // In the case of a filter, we'll reload the filter in the background to avoid tasks which do - // not match the filter show up here - loadTasks(false) + const originalTask = allTasks.value.find(t => t.id === updatedTask.id) + const isRecurringTask = originalTask && (originalTask.repeatAfter.amount > 0 || originalTask.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH) + + const filterContainsDateFields = project.value && isSavedFilter(project.value) && + project.value.title && + (savedFilter.value.filters?.filter?.includes('due_date') || + savedFilter.value.filters?.filter?.includes('start_date') || + savedFilter.value.filters?.filter?.includes('end_date')) + + + if (isRecurringTask && filterContainsDateFields) { + // In the case of a filter, we'll reload the filter in the background to avoid tasks which do + // not match the filter show up here + loadTasks(false) + return + } + + // For other updates in saved filters, just update the task in place + for (const t in allTasks.value) { + if (allTasks.value[t].id === updatedTask.id) { + allTasks.value[t] = updatedTask + break + } + } return } diff --git a/frontend/src/components/tasks/partials/KanbanCard.vue b/frontend/src/components/tasks/partials/KanbanCard.vue index 16a76c47e..33ce37a6e 100644 --- a/frontend/src/components/tasks/partials/KanbanCard.vue +++ b/frontend/src/components/tasks/partials/KanbanCard.vue @@ -128,6 +128,7 @@ import AssigneeList from '@/components/tasks/partials/AssigneeList.vue' import {playPopSound} from '@/helpers/playPop' import {isEditorContentEmpty} from '@/helpers/editorContentEmpty' import {useProjectStore} from '@/stores/projects' +import {TASK_REPEAT_MODES} from '@/types/IRepeatMode' const props = withDefaults(defineProps<{ task: ITask, @@ -137,6 +138,10 @@ const props = withDefaults(defineProps<{ loading: false, }) +const emit = defineEmits<{ + 'taskCompletedRecurring': [task: ITask] +}>() + const router = useRouter() const loadingInternal = ref(false) @@ -165,6 +170,9 @@ const isOverdue = computed(() => ( )) async function toggleTaskDone(task: ITask) { + const isRecurringTask = task.repeatAfter.amount > 0 || task.repeatMode === TASK_REPEAT_MODES.REPEAT_MODE_MONTH + const wasBeingMarkedDone = !task.done + loadingInternal.value = true try { const updatedTask = await useTaskStore().update({ @@ -175,6 +183,11 @@ async function toggleTaskDone(task: ITask) { if (updatedTask.done) { playPopSound() } + + // Emit event if this was a recurring task being marked as done + if (isRecurringTask && wasBeingMarkedDone && updatedTask.done) { + emit('taskCompletedRecurring', updatedTask) + } } finally { loadingInternal.value = false } diff --git a/frontend/src/services/savedFilter.ts b/frontend/src/services/savedFilter.ts index 552f3a9aa..7daf93de1 100644 --- a/frontend/src/services/savedFilter.ts +++ b/frontend/src/services/savedFilter.ts @@ -37,8 +37,8 @@ export function getSavedFilterIdFromProjectId(projectId: IProject['id']) { return filterId } -export function isSavedFilter(project: IProject) { - return getSavedFilterIdFromProjectId(project?.id) > 0 +export function isSavedFilter(project: IProject | undefined | null) { + return getSavedFilterIdFromProjectId(project?.id || 0) > 0 } export default class SavedFilterService extends AbstractService {