feat(frontend): preserve Gantt date range when switching views (#2141)

Adds a `viewFilters` Pinia store that stores query params per view ID to sync Gantt filters to the store whenever they change. This persists the custom date ranges when switching between views.

Fixes #2124
This commit is contained in:
kolaente
2026-01-24 13:12:35 +01:00
committed by GitHub
parent 47bfb6a424
commit acbb06e337
4 changed files with 102 additions and 4 deletions

View File

@@ -23,7 +23,7 @@
:key="view.id"
class="switch-view-button"
:class="{'is-active': view.id === viewId}"
:to="{ name: 'project.view', params: { projectId, viewId: view.id } }"
:to="getViewRoute(view)"
>
{{ getViewTitle(view) }}
</BaseButton>
@@ -57,6 +57,7 @@ import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useViewFiltersStore} from '@/stores/viewFilters'
import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
@@ -71,6 +72,7 @@ const {t} = useI18n()
const baseStore = useBaseStore()
const projectStore = useProjectStore()
const viewFiltersStore = useViewFiltersStore()
const currentProject = computed<IProject>(() => {
return baseStore.currentProject || {
@@ -95,9 +97,18 @@ function getViewTitle(view: IProjectView) {
case 'Kanban':
return t('project.kanban.title')
}
return view.title
}
function getViewRoute(view: IProjectView) {
const storedQuery = viewFiltersStore.getViewQuery(view.id)
return {
name: 'project.view',
params: {projectId: props.projectId, viewId: view.id},
query: storedQuery,
}
}
</script>
<style lang="scss" scoped>

View File

@@ -0,0 +1,41 @@
import {describe, it, expect, beforeEach} from 'vitest'
import {setActivePinia, createPinia} from 'pinia'
import {useViewFiltersStore} from './viewFilters'
describe('viewFilters store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('should store and retrieve query params for a view', () => {
const store = useViewFiltersStore()
store.setViewQuery(18, {dateFrom: '2026-01-01', dateTo: '2026-01-31'})
expect(store.getViewQuery(18)).toEqual({dateFrom: '2026-01-01', dateTo: '2026-01-31'})
})
it('should return empty object for views without stored params', () => {
const store = useViewFiltersStore()
expect(store.getViewQuery(999)).toEqual({})
})
it('should clear query params for a view', () => {
const store = useViewFiltersStore()
store.setViewQuery(18, {dateFrom: '2026-01-01', dateTo: '2026-01-31'})
store.clearViewQuery(18)
expect(store.getViewQuery(18)).toEqual({})
})
it('should update existing query params', () => {
const store = useViewFiltersStore()
store.setViewQuery(18, {dateFrom: '2026-01-01', dateTo: '2026-01-31'})
store.setViewQuery(18, {dateFrom: '2026-02-01', dateTo: '2026-02-28'})
expect(store.getViewQuery(18)).toEqual({dateFrom: '2026-02-01', dateTo: '2026-02-28'})
})
})

View File

@@ -0,0 +1,27 @@
import {defineStore} from 'pinia'
import {ref} from 'vue'
import type {LocationQueryRaw} from 'vue-router'
import type {IProjectView} from '@/modelTypes/IProjectView'
export const useViewFiltersStore = defineStore('viewFilters', () => {
const viewQueries = ref<Record<IProjectView['id'], LocationQueryRaw>>({})
function setViewQuery(viewId: IProjectView['id'], query: LocationQueryRaw) {
viewQueries.value[viewId] = query
}
function getViewQuery(viewId: IProjectView['id']): LocationQueryRaw {
return viewQueries.value[viewId] ?? {}
}
function clearViewQuery(viewId: IProjectView['id']) {
delete viewQueries.value[viewId]
}
return {
viewQueries,
setViewQuery,
getViewQuery,
clearViewQuery,
}
})

View File

@@ -1,5 +1,7 @@
import type {Ref} from 'vue'
import type {RouteLocationNormalized, RouteLocationRaw} from 'vue-router'
import {watch, type Ref} from 'vue'
import type {RouteLocationNormalized, RouteLocationRaw, LocationQueryRaw} from 'vue-router'
import {useViewFiltersStore} from '@/stores/viewFilters'
import {isoToKebabDate} from '@/helpers/time/isoToKebabDate'
import {parseDateProp} from '@/helpers/time/parseDateProp'
@@ -98,6 +100,8 @@ export type UseGanttFiltersReturn =
UseGanttTaskListReturn
export function useGanttFilters(route: Ref<RouteLocationNormalized>, viewId: Ref<IProjectView['id']>): UseGanttFiltersReturn {
const viewFiltersStore = useViewFiltersStore()
const {
filters,
hasDefaultFilters,
@@ -110,6 +114,21 @@ export function useGanttFilters(route: Ref<RouteLocationNormalized>, viewId: Ref
['project.view'],
)
// Sync filters to store whenever they change (for view tab navigation)
watch(
filters,
(newFilters) => {
const routeLocation = ganttFiltersToRoute(newFilters)
const query = routeLocation.query as LocationQueryRaw
if (query && Object.keys(query).length > 0) {
viewFiltersStore.setViewQuery(viewId.value, query)
} else {
viewFiltersStore.clearViewQuery(viewId.value)
}
},
{immediate: true, deep: true},
)
const {
tasks,
loadTasks,