mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Persist table column filters
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
import { describe, expect, it, jest, beforeEach } from '@jest/globals'
|
||||
import '@testing-library/jest-dom'
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react'
|
||||
import { act, fireEvent, render, waitFor } from '@testing-library/react'
|
||||
import { Role } from '@/shared/types'
|
||||
import { TableView } from './TableView'
|
||||
import { usePageContext } from '../Page'
|
||||
import { useUser } from '@/hooks/user'
|
||||
|
||||
type CapturedTableOptions = {
|
||||
onColumnFiltersChange?: (filters: Array<{ id: string; value: unknown }>) => void
|
||||
}
|
||||
|
||||
let lastMaterialReactTableOptions: CapturedTableOptions | null = null
|
||||
|
||||
jest.mock('material-react-table', () => ({
|
||||
useMaterialReactTable: () => ({
|
||||
getPrePaginationRowModel: () => ({ rows: [] }),
|
||||
}),
|
||||
useMaterialReactTable: (options: CapturedTableOptions) => {
|
||||
lastMaterialReactTableOptions = options
|
||||
return {
|
||||
getPrePaginationRowModel: () => ({ rows: [] }),
|
||||
}
|
||||
},
|
||||
MaterialReactTable: () => <div data-testid="material-react-table" />,
|
||||
}))
|
||||
|
||||
@@ -48,6 +57,8 @@ describe('TableView table help integration', () => {
|
||||
mockLocationSearch = ''
|
||||
mockNavigate = jest.fn()
|
||||
window.sessionStorage.clear()
|
||||
window.localStorage.clear()
|
||||
lastMaterialReactTableOptions = null
|
||||
mockUsePageContext.mockReturnValue({
|
||||
editRights: {},
|
||||
idList: [],
|
||||
@@ -233,6 +244,90 @@ describe('TableView table help integration', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('restores persisted column filters for normal table visits', async () => {
|
||||
const setSqlColumnFilters = jest.fn()
|
||||
const persistedFilters = [{ id: 'name', value: 'Alpha' }]
|
||||
|
||||
window.localStorage.setItem('nowdatabase-table-columnfilters:test', JSON.stringify(persistedFilters))
|
||||
mockUsePageContext.mockReturnValue({
|
||||
editRights: {},
|
||||
idList: [],
|
||||
idFieldName: 'id',
|
||||
viewName: 'test',
|
||||
previousTableUrls: [],
|
||||
createTitle: () => '',
|
||||
createSubtitle: () => '',
|
||||
sqlLimit: 25,
|
||||
sqlOffset: 0,
|
||||
sqlColumnFilters: [],
|
||||
sqlOrderBy: [],
|
||||
setIdList: jest.fn(),
|
||||
setSqlLimit: jest.fn(),
|
||||
setSqlOffset: jest.fn(),
|
||||
setSqlColumnFilters,
|
||||
setSqlOrderBy: jest.fn(),
|
||||
setPreviousTableUrls: jest.fn(),
|
||||
})
|
||||
|
||||
render(
|
||||
<TableView<TestRow>
|
||||
title="Test Table"
|
||||
idFieldName="id"
|
||||
columns={[
|
||||
{ header: 'Name', accessorKey: 'name' },
|
||||
{ header: 'Id', accessorKey: 'id' },
|
||||
]}
|
||||
visibleColumns={{ name: true, id: true }}
|
||||
data={[
|
||||
{
|
||||
id: '1',
|
||||
name: 'Alpha',
|
||||
full_count: 1,
|
||||
},
|
||||
]}
|
||||
url="test"
|
||||
isFetching={false}
|
||||
/>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(setSqlColumnFilters).toHaveBeenLastCalledWith(persistedFilters)
|
||||
})
|
||||
})
|
||||
|
||||
it('persists changed column filters for later table visits', async () => {
|
||||
render(
|
||||
<TableView<TestRow>
|
||||
title="Test Table"
|
||||
idFieldName="id"
|
||||
columns={[
|
||||
{ header: 'Name', accessorKey: 'name' },
|
||||
{ header: 'Id', accessorKey: 'id' },
|
||||
]}
|
||||
visibleColumns={{ name: true, id: true }}
|
||||
data={[
|
||||
{
|
||||
id: '1',
|
||||
name: 'Alpha',
|
||||
full_count: 1,
|
||||
},
|
||||
]}
|
||||
url="test"
|
||||
isFetching={false}
|
||||
/>
|
||||
)
|
||||
|
||||
act(() => {
|
||||
lastMaterialReactTableOptions?.onColumnFiltersChange?.([{ id: 'name', value: 'Alpha' }])
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(window.localStorage.getItem('nowdatabase-table-columnfilters:test')).toEqual(
|
||||
JSON.stringify([{ id: 'name', value: 'Alpha' }])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows help with multi-sort guidance for regular tables', () => {
|
||||
const { getByRole, getByText } = render(
|
||||
<TableView<TestRow>
|
||||
|
||||
@@ -41,6 +41,7 @@ const TEXT_FILTER_MODE_OPTIONS = ['equals', 'contains', 'startsWith'] as const
|
||||
type TextFilterModeOption = (typeof TEXT_FILTER_MODE_OPTIONS)[number]
|
||||
const TABLE_STATE_URL_PARAM = 'tableState'
|
||||
const TABLE_STATE_STORAGE_PREFIX = 'nowdatabase-table-state'
|
||||
const TABLE_FILTERS_STORAGE_PREFIX = 'nowdatabase-table-columnfilters'
|
||||
|
||||
const isEmptyFilterValue = (value: unknown): boolean => {
|
||||
if (Array.isArray(value)) {
|
||||
@@ -341,6 +342,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
}
|
||||
|
||||
const getTableStateStorageKey = (stateId: string) => `${TABLE_STATE_STORAGE_PREFIX}:${stateId}`
|
||||
const getPersistentColumnFiltersStorageKey = () => `${TABLE_FILTERS_STORAGE_PREFIX}:${url ?? location.pathname}`
|
||||
|
||||
const loadStoredTableState = (stateId: string) => {
|
||||
const storedValue = window.sessionStorage.getItem(getTableStateStorageKey(stateId))
|
||||
@@ -350,10 +352,28 @@ export const TableView = <T extends MRT_RowData>({
|
||||
return isStoredTableState(parsed) ? parsed : undefined
|
||||
}
|
||||
|
||||
const loadPersistentColumnFilters = () => {
|
||||
const storedValue = window.localStorage.getItem(getPersistentColumnFiltersStorageKey())
|
||||
if (!storedValue) return
|
||||
|
||||
const parsed = safeJsonParse(storedValue)
|
||||
return isColumnFiltersState(parsed) ? normalizeColumnFilters(parsed) : undefined
|
||||
}
|
||||
|
||||
const saveStoredTableState = (state: StoredTableState) => {
|
||||
window.sessionStorage.setItem(getTableStateStorageKey(tableStateId), JSON.stringify(state))
|
||||
}
|
||||
|
||||
const savePersistentColumnFilters = (filters: MRT_ColumnFiltersState) => {
|
||||
const storageKey = getPersistentColumnFiltersStorageKey()
|
||||
if (filters.length === 0) {
|
||||
window.localStorage.removeItem(storageKey)
|
||||
return
|
||||
}
|
||||
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(filters))
|
||||
}
|
||||
|
||||
const buildTableStateUrl = () => `${location.pathname}?${TABLE_STATE_URL_PARAM}=${encodeURIComponent(tableStateId)}`
|
||||
|
||||
const loadStateFromUrl = <TState extends MRT_ColumnFiltersState | MRT_SortingState | MRT_PaginationState>(
|
||||
@@ -379,7 +399,13 @@ export const TableView = <T extends MRT_RowData>({
|
||||
return stateFromStorage as unknown as TState
|
||||
}
|
||||
|
||||
if (!stateFromUrl) return defaultState
|
||||
if (!stateFromUrl) {
|
||||
if (state === 'columnfilters') {
|
||||
return (loadPersistentColumnFilters() ?? defaultState) as TState
|
||||
}
|
||||
|
||||
return defaultState
|
||||
}
|
||||
const parsed = safeJsonParse(stateFromUrl)
|
||||
if (parsed === undefined) {
|
||||
return defaultState
|
||||
@@ -645,6 +671,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
if (selectorFn || !hasLoadedTableState) return
|
||||
const sanitizedFilters = sanitizeColumnFilters(columnFilters)
|
||||
saveStoredTableState({ columnfilters: sanitizedFilters, sorting, pagination })
|
||||
savePersistentColumnFilters(sanitizedFilters)
|
||||
navigate(buildTableStateUrl(), {
|
||||
replace: true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user