diff --git a/web/src/components/features/graph/ProjectGraph.vue b/web/src/components/features/graph/ProjectGraph.vue index d487968..518f075 100644 --- a/web/src/components/features/graph/ProjectGraph.vue +++ b/web/src/components/features/graph/ProjectGraph.vue @@ -166,7 +166,7 @@ watch( (id) => { if (!id) return store.fetchAllTasksAndLists(id).then(() => { - layoutGraph(layoutDirection.value) + applyFilters() }) }, { immediate: true }, diff --git a/web/src/components/features/kanban/KanbanBoard.vue b/web/src/components/features/kanban/KanbanBoard.vue index e49d0e6..5769fd3 100644 --- a/web/src/components/features/kanban/KanbanBoard.vue +++ b/web/src/components/features/kanban/KanbanBoard.vue @@ -124,7 +124,8 @@ const initLoadTasksForEachColumn = async () => { } watch(goalId, (lGoalId) => { - kanbanStore.$reset() + kanbanStore.statuses = [] + kanbanStore.tasksData = {} if (lGoalId) { kanbanStore.goalId = +lGoalId } diff --git a/web/src/composables/useFiltersFromQuery.ts b/web/src/composables/useFiltersFromQuery.ts new file mode 100644 index 0000000..f77eb40 --- /dev/null +++ b/web/src/composables/useFiltersFromQuery.ts @@ -0,0 +1,55 @@ +import { computed, ref, watch } from 'vue' +import { useRoute, useRouter } from 'vue-router' + +export const useFiltersFromQuery = () => { + const route = useRoute() + const router = useRouter() + + const parseIds = (raw: unknown): number[] => { + if (typeof raw !== 'string' || !raw) return [] + return raw + .split(',') + .map((s) => Number(s)) + .filter((n) => Number.isFinite(n)) + } + + const serializeIds = (ids: number[]): string | undefined => + ids.length ? ids.join(',') : undefined + + const sameIds = (a: number[], b: number[]) => + a.length === b.length && a.every((v, i) => v === b[i]) + + const selectedListIds = ref(parseIds(route.query.lists)) + const selectedAssigneeIds = ref(parseIds(route.query.assignees)) + + watch([selectedListIds, selectedAssigneeIds], ([lists, assignees]) => { + const next = { ...route.query } + const listsStr = serializeIds(lists) + const assigneesStr = serializeIds(assignees) + if (listsStr) next.lists = listsStr + else delete next.lists + if (assigneesStr) next.assignees = assigneesStr + else delete next.assignees + router.replace({ query: next }) + }) + + watch( + () => [route.query.lists, route.query.assignees] as const, + ([lists, assignees]) => { + const nextLists = parseIds(lists) + const nextAssignees = parseIds(assignees) + if (!sameIds(nextLists, selectedListIds.value)) { + selectedListIds.value = nextLists + } + if (!sameIds(nextAssignees, selectedAssigneeIds.value)) { + selectedAssigneeIds.value = nextAssignees + } + }, + ) + + const hasActiveFilters = computed( + () => selectedListIds.value.length > 0 || selectedAssigneeIds.value.length > 0, + ) + + return { selectedListIds, selectedAssigneeIds, hasActiveFilters } +} diff --git a/web/src/pages/user/graph.vue b/web/src/pages/user/graph.vue index 83c719a..0015a87 100644 --- a/web/src/pages/user/graph.vue +++ b/web/src/pages/user/graph.vue @@ -58,6 +58,7 @@ import TvCollapseSidebarDesktop from '@/components/features/base/TvCollapseSideb import TvTaskDetailOverlay from '@/components/features/tasks/TvTaskDetailOverlay.vue' import TvKanbanFilters from '@/components/features/base/TvKanbanFilters.vue' import { useAppRouteInfo } from '@/composables/useAppRouteInfo' +import { useFiltersFromQuery } from '@/composables/useFiltersFromQuery' import { useGoalsStore } from '@/stores/goals.store' import { useProjectDataLoader } from '@/composables/useProjectDataLoader' @@ -66,9 +67,8 @@ const { projectId } = useAppRouteInfo() const goalsStore = useGoalsStore() const projectName = computed(() => goalsStore.goalMap.get(projectId.value)?.name ?? '') -const showFilters = ref(false) -const selectedListIds = ref([]) -const selectedAssigneeIds = ref([]) +const { selectedListIds, selectedAssigneeIds, hasActiveFilters } = useFiltersFromQuery() +const showFilters = ref(hasActiveFilters.value) useProjectDataLoader(projectId) diff --git a/web/src/pages/user/kanban.vue b/web/src/pages/user/kanban.vue index 044989c..5ab14ec 100644 --- a/web/src/pages/user/kanban.vue +++ b/web/src/pages/user/kanban.vue @@ -60,6 +60,7 @@ import TvCollapseSidebarDesktop from '@/components/features/base/TvCollapseSideb import TvTaskDetailOverlay from '@/components/features/tasks/TvTaskDetailOverlay.vue' import TvKanbanFilters from '@/components/features/base/TvKanbanFilters.vue' import { useAppRouteInfo } from '@/composables/useAppRouteInfo' +import { useFiltersFromQuery } from '@/composables/useFiltersFromQuery' import { useProjectDataLoader } from '@/composables/useProjectDataLoader' import { useGoalsStore } from '@/stores/goals.store' import { useKanbanStore } from '@/stores/kanban.store' @@ -70,13 +71,12 @@ const goalsStore = useGoalsStore() const kanbanStore = useKanbanStore() const projectName = computed(() => goalsStore.goalMap.get(projectId.value)?.name ?? '') -const showFilters = ref(false) -const selectedListIds = ref([]) -const selectedAssigneeIds = ref([]) +const { selectedListIds, selectedAssigneeIds, hasActiveFilters } = useFiltersFromQuery() +const showFilters = ref(hasActiveFilters.value) watch([selectedListIds, selectedAssigneeIds], ([listIds, assigneeIds]) => { kanbanStore.setFilters({ listIds, assigneeIds }) -}) +}, { immediate: true }) useProjectDataLoader(projectId)