mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-02-01 22:47:40 +00:00
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:
@@ -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>
|
||||
|
||||
41
frontend/src/stores/viewFilters.test.ts
Normal file
41
frontend/src/stores/viewFilters.test.ts
Normal 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'})
|
||||
})
|
||||
})
|
||||
27
frontend/src/stores/viewFilters.ts
Normal file
27
frontend/src/stores/viewFilters.ts
Normal 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,
|
||||
}
|
||||
})
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user