fix(filters): persist url filter query across views (#1482)

This commit is contained in:
kolaente
2025-09-12 00:12:33 +02:00
committed by GitHub
parent fcc204dc88
commit b99ea2deb0
3 changed files with 100 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
import {createFakeUserAndLogin} from '../../support/authenticateUser'
import {TaskFactory} from '../../factories/task'
import {ProjectFactory} from '../../factories/project'
import { createProjects } from './prepareProjects'
describe('Filter Persistence Across Views', () => {
createFakeUserAndLogin()
const openAndSetFilters = () => {
cy.get('.filter-container button')
.contains('Filters')
.click()
cy.get('.filter-popup')
.should('be.visible')
cy.get('.filter-popup .filter-input')
.type('done = true')
cy.get('.filter-popup button')
.contains('Show results')
.click()
}
beforeEach(() => {
createProjects()
TaskFactory.create(5, {
id: '{increment}',
project_id: 1,
title: 'Test Task {increment}'
})
cy.visit('/projects/1/1')
})
it('should persist filters in List view after page refresh', () => {
openAndSetFilters()
cy.url().should('include', 'filter=')
cy.reload()
cy.url().should('include', 'filter=')
})
it('should persist filters in Table view after page refresh', () => {
cy.visit('/projects/1/3')
openAndSetFilters()
cy.url().should('include', 'filter=')
cy.reload()
cy.url().should('include', 'filter=')
})
it('should persist filters in Kanban view after page refresh', () => {
cy.visit('/projects/1/4')
openAndSetFilters()
cy.url().should('include', 'filter=')
cy.reload()
cy.url().should('include', 'filter=')
})
it('should handle URL sharing with filters', () => {
// Visit URL with pre-existing filter parameters
cy.visit('/projects/1/4?filter=done%3Dtrue&s=Test')
// Verify URL parameters are preserved
cy.url().should('include', 'filter=done%3Dtrue')
cy.url().should('include', 's=Test')
// Switch views and verify parameters persist
cy.visit('/projects/1/3?filter=done%3Dtrue&s=Test')
cy.url().should('include', 'filter=done%3Dtrue')
cy.url().should('include', 's=Test')
})
})

View File

@@ -12,6 +12,7 @@
v-model="params"
:view-id="viewId"
:project-id="projectId"
@update:modelValue="updateFilters"
/>
</div>
</template>
@@ -277,6 +278,7 @@
<script setup lang="ts">
import {computed, nextTick, ref, watch, toRef} from 'vue'
import {useRouteQuery} from '@vueuse/router'
import {useI18n} from 'vue-i18n'
import draggable from 'zhyswan-vuedraggable'
import {klona} from 'klona/lite'
@@ -374,6 +376,10 @@ const collapsedBuckets = ref<CollapsedBuckets>({})
const taskUpdating = ref<{ [id: ITask['id']]: boolean }>({})
const oneTaskUpdating = ref(false)
// URL-synchronized filter parameters
const filter = useRouteQuery('filter')
const s = useRouteQuery('s')
const params = ref<TaskFilterParams>({
sort_by: [],
order_by: [],
@@ -382,6 +388,20 @@ const params = ref<TaskFilterParams>({
s: '',
})
watch([filter, s], ([filterValue, sValue]) => {
params.value.filter = filterValue ?? ''
params.value.s = sValue ?? ''
}, { immediate: true })
function updateFilters(newParams: TaskFilterParams) {
// Update all params
params.value = { ...newParams }
// Sync only filter and s to URL
filter.value = newParams.filter || undefined
s.value = newParams.s || undefined
}
const getTaskDraggableTaskComponentData = computed(() => (bucket: IBucket) => {
return {
ref: (el: HTMLElement) => setTaskContainerRef(bucket.id, el),

View File

@@ -73,6 +73,7 @@
v-model="params"
:view-id="viewId"
:project-id="projectId"
@update:modelValue="taskList.loadTasks()"
/>
</div>
</template>